You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

60829 lines
1.4 MiB

/*!
* bpmn-js - bpmn-modeler v9.4.0
*
* Copyright (c) 2014-present, camunda Services GmbH
*
* Released under the bpmn.io license
* http://bpmn.io/license
*
* Source Code: https://github.com/bpmn-io/bpmn-js
*
* Date: 2022-08-22
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BpmnJS = factory());
})(this, (function () { 'use strict';
function e(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}));}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var hat_1 = createCommonjsModule(function (module) {
var hat = module.exports = function (bits, base) {
if (!base) base = 16;
if (bits === undefined) bits = 128;
if (bits <= 0) return '0';
var digits = Math.log(Math.pow(2, bits)) / Math.log(base);
for (var i = 2; digits === Infinity; i *= 2) {
digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
}
var rem = digits - Math.floor(digits);
var res = '';
for (var i = 0; i < Math.floor(digits); i++) {
var x = Math.floor(Math.random() * base).toString(base);
res = x + res;
}
if (rem) {
var b = Math.pow(base, rem);
var x = Math.floor(Math.random() * b).toString(base);
res = x + res;
}
var parsed = parseInt(res, base);
if (parsed !== Infinity && parsed >= Math.pow(2, bits)) {
return hat(bits, base)
}
else return res;
};
hat.rack = function (bits, base, expandBy) {
var fn = function (data) {
var iters = 0;
do {
if (iters ++ > 10) {
if (expandBy) bits += expandBy;
else throw new Error('too many ID collisions, use more bits')
}
var id = hat(bits, base);
} while (Object.hasOwnProperty.call(hats, id));
hats[id] = data;
return id;
};
var hats = fn.hats = {};
fn.get = function (id) {
return fn.hats[id];
};
fn.set = function (id, value) {
fn.hats[id] = value;
return fn;
};
fn.bits = bits || 128;
fn.base = base || 16;
return fn;
};
});
/**
* Create a new id generator / cache instance.
*
* You may optionally provide a seed that is used internally.
*
* @param {Seed} seed
*/
function Ids(seed) {
if (!(this instanceof Ids)) {
return new Ids(seed);
}
seed = seed || [128, 36, 1];
this._seed = seed.length ? hat_1.rack(seed[0], seed[1], seed[2]) : seed;
}
/**
* Generate a next id.
*
* @param {Object} [element] element to bind the id to
*
* @return {String} id
*/
Ids.prototype.next = function (element) {
return this._seed(element || true);
};
/**
* Generate a next id with a given prefix.
*
* @param {Object} [element] element to bind the id to
*
* @return {String} id
*/
Ids.prototype.nextPrefixed = function (prefix, element) {
var id;
do {
id = prefix + this.next(true);
} while (this.assigned(id)); // claim {prefix}{random}
this.claim(id, element); // return
return id;
};
/**
* Manually claim an existing id.
*
* @param {String} id
* @param {String} [element] element the id is claimed by
*/
Ids.prototype.claim = function (id, element) {
this._seed.set(id, element || true);
};
/**
* Returns true if the given id has already been assigned.
*
* @param {String} id
* @return {Boolean}
*/
Ids.prototype.assigned = function (id) {
return this._seed.get(id) || false;
};
/**
* Unclaim an id.
*
* @param {String} id the id to unclaim
*/
Ids.prototype.unclaim = function (id) {
delete this._seed.hats[id];
};
/**
* Clear all claimed ids.
*/
Ids.prototype.clear = function () {
var hats = this._seed.hats,
id;
for (id in hats) {
this.unclaim(id);
}
};
/**
* Flatten array, one level deep.
*
* @param {Array<?>} arr
*
* @return {Array<?>}
*/
function flatten(arr) {
return Array.prototype.concat.apply([], arr);
}
var nativeToString$1 = Object.prototype.toString;
var nativeHasOwnProperty$1 = Object.prototype.hasOwnProperty;
function isUndefined$2(obj) {
return obj === undefined;
}
function isDefined(obj) {
return obj !== undefined;
}
function isNil(obj) {
return obj == null;
}
function isArray$3(obj) {
return nativeToString$1.call(obj) === '[object Array]';
}
function isObject(obj) {
return nativeToString$1.call(obj) === '[object Object]';
}
function isNumber(obj) {
return nativeToString$1.call(obj) === '[object Number]';
}
function isFunction(obj) {
var tag = nativeToString$1.call(obj);
return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]';
}
function isString(obj) {
return nativeToString$1.call(obj) === '[object String]';
}
/**
* Ensure collection is an array.
*
* @param {Object} obj
*/
function ensureArray(obj) {
if (isArray$3(obj)) {
return;
}
throw new Error('must supply array');
}
/**
* Return true, if target owns a property with the given key.
*
* @param {Object} target
* @param {String} key
*
* @return {Boolean}
*/
function has$1(target, key) {
return nativeHasOwnProperty$1.call(target, key);
}
/**
* Find element in collection.
*
* @param {Array|Object} collection
* @param {Function|Object} matcher
*
* @return {Object}
*/
function find(collection, matcher) {
matcher = toMatcher(matcher);
var match;
forEach$1(collection, function (val, key) {
if (matcher(val, key)) {
match = val;
return false;
}
});
return match;
}
/**
* Find element index in collection.
*
* @param {Array|Object} collection
* @param {Function} matcher
*
* @return {Object}
*/
function findIndex(collection, matcher) {
matcher = toMatcher(matcher);
var idx = isArray$3(collection) ? -1 : undefined;
forEach$1(collection, function (val, key) {
if (matcher(val, key)) {
idx = key;
return false;
}
});
return idx;
}
/**
* Find element in collection.
*
* @param {Array|Object} collection
* @param {Function} matcher
*
* @return {Array} result
*/
function filter(collection, matcher) {
var result = [];
forEach$1(collection, function (val, key) {
if (matcher(val, key)) {
result.push(val);
}
});
return result;
}
/**
* Iterate over collection; returning something
* (non-undefined) will stop iteration.
*
* @param {Array|Object} collection
* @param {Function} iterator
*
* @return {Object} return result that stopped the iteration
*/
function forEach$1(collection, iterator) {
var val, result;
if (isUndefined$2(collection)) {
return;
}
var convertKey = isArray$3(collection) ? toNum$1 : identity$1;
for (var key in collection) {
if (has$1(collection, key)) {
val = collection[key];
result = iterator(val, convertKey(key));
if (result === false) {
return val;
}
}
}
}
/**
* Return collection without element.
*
* @param {Array} arr
* @param {Function} matcher
*
* @return {Array}
*/
function without(arr, matcher) {
if (isUndefined$2(arr)) {
return [];
}
ensureArray(arr);
matcher = toMatcher(matcher);
return arr.filter(function (el, idx) {
return !matcher(el, idx);
});
}
/**
* Reduce collection, returning a single result.
*
* @param {Object|Array} collection
* @param {Function} iterator
* @param {Any} result
*
* @return {Any} result returned from last iterator
*/
function reduce(collection, iterator, result) {
forEach$1(collection, function (value, idx) {
result = iterator(result, value, idx);
});
return result;
}
/**
* Return true if every element in the collection
* matches the criteria.
*
* @param {Object|Array} collection
* @param {Function} matcher
*
* @return {Boolean}
*/
function every(collection, matcher) {
return !!reduce(collection, function (matches, val, key) {
return matches && matcher(val, key);
}, true);
}
/**
* Return true if some elements in the collection
* match the criteria.
*
* @param {Object|Array} collection
* @param {Function} matcher
*
* @return {Boolean}
*/
function some(collection, matcher) {
return !!find(collection, matcher);
}
/**
* Transform a collection into another collection
* by piping each member through the given fn.
*
* @param {Object|Array} collection
* @param {Function} fn
*
* @return {Array} transformed collection
*/
function map(collection, fn) {
var result = [];
forEach$1(collection, function (val, key) {
result.push(fn(val, key));
});
return result;
}
/**
* Get the collections keys.
*
* @param {Object|Array} collection
*
* @return {Array}
*/
function keys(collection) {
return collection && Object.keys(collection) || [];
}
/**
* Shorthand for `keys(o).length`.
*
* @param {Object|Array} collection
*
* @return {Number}
*/
function size(collection) {
return keys(collection).length;
}
/**
* Get the values in the collection.
*
* @param {Object|Array} collection
*
* @return {Array}
*/
function values(collection) {
return map(collection, function (val) {
return val;
});
}
/**
* Group collection members by attribute.
*
* @param {Object|Array} collection
* @param {Function} extractor
*
* @return {Object} map with { attrValue => [ a, b, c ] }
*/
function groupBy(collection, extractor) {
var grouped = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
extractor = toExtractor(extractor);
forEach$1(collection, function (val) {
var discriminator = extractor(val) || '_';
var group = grouped[discriminator];
if (!group) {
group = grouped[discriminator] = [];
}
group.push(val);
});
return grouped;
}
function uniqueBy(extractor) {
extractor = toExtractor(extractor);
var grouped = {};
for (var _len = arguments.length, collections = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
collections[_key - 1] = arguments[_key];
}
forEach$1(collections, function (c) {
return groupBy(c, extractor, grouped);
});
var result = map(grouped, function (val, key) {
return val[0];
});
return result;
}
var unionBy = uniqueBy;
/**
* Sort collection by criteria.
*
* @param {Object|Array} collection
* @param {String|Function} extractor
*
* @return {Array}
*/
function sortBy(collection, extractor) {
extractor = toExtractor(extractor);
var sorted = [];
forEach$1(collection, function (value, key) {
var disc = extractor(value, key);
var entry = {
d: disc,
v: value
};
for (var idx = 0; idx < sorted.length; idx++) {
var d = sorted[idx].d;
if (disc < d) {
sorted.splice(idx, 0, entry);
return;
}
} // not inserted, append (!)
sorted.push(entry);
});
return map(sorted, function (e) {
return e.v;
});
}
/**
* Create an object pattern matcher.
*
* @example
*
* const matcher = matchPattern({ id: 1 });
*
* let element = find(elements, matcher);
*
* @param {Object} pattern
*
* @return {Function} matcherFn
*/
function matchPattern(pattern) {
return function (el) {
return every(pattern, function (val, key) {
return el[key] === val;
});
};
}
function toExtractor(extractor) {
return isFunction(extractor) ? extractor : function (e) {
return e[extractor];
};
}
function toMatcher(matcher) {
return isFunction(matcher) ? matcher : function (e) {
return e === matcher;
};
}
function identity$1(arg) {
return arg;
}
function toNum$1(arg) {
return Number(arg);
}
/**
* Debounce fn, calling it only once if the given time
* elapsed between calls.
*
* Lodash-style the function exposes methods to `#clear`
* and `#flush` to control internal behavior.
*
* @param {Function} fn
* @param {Number} timeout
*
* @return {Function} debounced function
*/
function debounce(fn, timeout) {
var timer;
var lastArgs;
var lastThis;
var lastNow;
function fire(force) {
var now = Date.now();
var scheduledDiff = force ? 0 : lastNow + timeout - now;
if (scheduledDiff > 0) {
return schedule(scheduledDiff);
}
fn.apply(lastThis, lastArgs);
clear();
}
function schedule(timeout) {
timer = setTimeout(fire, timeout);
}
function clear() {
if (timer) {
clearTimeout(timer);
}
timer = lastNow = lastArgs = lastThis = undefined;
}
function flush() {
if (timer) {
fire(true);
}
clear();
}
function callback() {
lastNow = Date.now();
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
lastArgs = args;
lastThis = this; // ensure an execution is scheduled
if (!timer) {
schedule(timeout);
}
}
callback.flush = flush;
callback.cancel = clear;
return callback;
}
/**
* Bind function against target <this>.
*
* @param {Function} fn
* @param {Object} target
*
* @return {Function} bound function
*/
function bind(fn, target) {
return fn.bind(target);
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
/**
* Convenience wrapper for `Object.assign`.
*
* @param {Object} target
* @param {...Object} others
*
* @return {Object} the target
*/
function assign(target) {
for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
others[_key - 1] = arguments[_key];
}
return _extends.apply(void 0, [target].concat(others));
}
/**
* Pick given properties from the target object.
*
* @param {Object} target
* @param {Array} properties
*
* @return {Object} target
*/
function pick(target, properties) {
var result = {};
var obj = Object(target);
forEach$1(properties, function (prop) {
if (prop in obj) {
result[prop] = target[prop];
}
});
return result;
}
/**
* Pick all target properties, excluding the given ones.
*
* @param {Object} target
* @param {Array} properties
*
* @return {Object} target
*/
function omit(target, properties) {
var result = {};
var obj = Object(target);
forEach$1(obj, function (prop, key) {
if (properties.indexOf(key) === -1) {
result[key] = prop;
}
});
return result;
}
/**
* Flatten array, one level deep.
*
* @param {Array<?>} arr
*
* @return {Array<?>}
*/
var nativeToString = Object.prototype.toString;
var nativeHasOwnProperty = Object.prototype.hasOwnProperty;
function isUndefined$1(obj) {
return obj === undefined;
}
function isArray$2(obj) {
return nativeToString.call(obj) === '[object Array]';
}
/**
* Return true, if target owns a property with the given key.
*
* @param {Object} target
* @param {String} key
*
* @return {Boolean}
*/
function has(target, key) {
return nativeHasOwnProperty.call(target, key);
}
/**
* Iterate over collection; returning something
* (non-undefined) will stop iteration.
*
* @param {Array|Object} collection
* @param {Function} iterator
*
* @return {Object} return result that stopped the iteration
*/
function forEach(collection, iterator) {
var val, result;
if (isUndefined$1(collection)) {
return;
}
var convertKey = isArray$2(collection) ? toNum : identity;
for (var key in collection) {
if (has(collection, key)) {
val = collection[key];
result = iterator(val, convertKey(key));
if (result === false) {
return val;
}
}
}
}
function identity(arg) {
return arg;
}
function toNum(arg) {
return Number(arg);
}
/**
* Assigns style attributes in a style-src compliant way.
*
* @param {Element} element
* @param {...Object} styleSources
*
* @return {Element} the element
*/
function assign$1(element) {
var target = element.style;
for (var _len = arguments.length, styleSources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
styleSources[_key - 1] = arguments[_key];
}
forEach(styleSources, function (style) {
if (!style) {
return;
}
forEach(style, function (value, key) {
target[key] = value;
});
});
return element;
}
/**
* Set attribute `name` to `val`, or get attr `name`.
*
* @param {Element} el
* @param {String} name
* @param {String} [val]
* @api public
*/
function attr$1(el, name, val) {
// get
if (arguments.length == 2) {
return el.getAttribute(name);
}
// remove
if (val === null) {
return el.removeAttribute(name);
}
// set
el.setAttribute(name, val);
return el;
}
var indexOf$1 = [].indexOf;
var indexof = function(arr, obj){
if (indexOf$1) return arr.indexOf(obj);
for (var i = 0; i < arr.length; ++i) {
if (arr[i] === obj) return i;
}
return -1;
};
/**
* Taken from https://github.com/component/classes
*
* Without the component bits.
*/
/**
* Whitespace regexp.
*/
var re$1 = /\s+/;
/**
* toString reference.
*/
var toString$1 = Object.prototype.toString;
/**
* Wrap `el` in a `ClassList`.
*
* @param {Element} el
* @return {ClassList}
* @api public
*/
function classes$1(el) {
return new ClassList$1(el);
}
/**
* Initialize a new ClassList for `el`.
*
* @param {Element} el
* @api private
*/
function ClassList$1(el) {
if (!el || !el.nodeType) {
throw new Error('A DOM element reference is required');
}
this.el = el;
this.list = el.classList;
}
/**
* Add class `name` if not already present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList$1.prototype.add = function (name) {
// classList
if (this.list) {
this.list.add(name);
return this;
}
// fallback
var arr = this.array();
var i = indexof(arr, name);
if (!~i) arr.push(name);
this.el.className = arr.join(' ');
return this;
};
/**
* Remove class `name` when present, or
* pass a regular expression to remove
* any which match.
*
* @param {String|RegExp} name
* @return {ClassList}
* @api public
*/
ClassList$1.prototype.remove = function (name) {
if ('[object RegExp]' == toString$1.call(name)) {
return this.removeMatching(name);
}
// classList
if (this.list) {
this.list.remove(name);
return this;
}
// fallback
var arr = this.array();
var i = indexof(arr, name);
if (~i) arr.splice(i, 1);
this.el.className = arr.join(' ');
return this;
};
/**
* Remove all classes matching `re`.
*
* @param {RegExp} re
* @return {ClassList}
* @api private
*/
ClassList$1.prototype.removeMatching = function (re) {
var arr = this.array();
for (var i = 0; i < arr.length; i++) {
if (re.test(arr[i])) {
this.remove(arr[i]);
}
}
return this;
};
/**
* Toggle class `name`, can force state via `force`.
*
* For browsers that support classList, but do not support `force` yet,
* the mistake will be detected and corrected.
*
* @param {String} name
* @param {Boolean} force
* @return {ClassList}
* @api public
*/
ClassList$1.prototype.toggle = function (name, force) {
// classList
if (this.list) {
if ('undefined' !== typeof force) {
if (force !== this.list.toggle(name, force)) {
this.list.toggle(name); // toggle again to correct
}
} else {
this.list.toggle(name);
}
return this;
}
// fallback
if ('undefined' !== typeof force) {
if (!force) {
this.remove(name);
} else {
this.add(name);
}
} else {
if (this.has(name)) {
this.remove(name);
} else {
this.add(name);
}
}
return this;
};
/**
* Return an array of classes.
*
* @return {Array}
* @api public
*/
ClassList$1.prototype.array = function () {
var className = this.el.getAttribute('class') || '';
var str = className.replace(/^\s+|\s+$/g, '');
var arr = str.split(re$1);
if ('' === arr[0]) arr.shift();
return arr;
};
/**
* Check if class `name` is present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList$1.prototype.has = ClassList$1.prototype.contains = function (name) {
return this.list ? this.list.contains(name) : !!~indexof(this.array(), name);
};
/**
* Remove all children from the given element.
*/
function clear$1(el) {
var c;
while (el.childNodes.length) {
c = el.childNodes[0];
el.removeChild(c);
}
return el;
}
var proto = typeof Element !== 'undefined' ? Element.prototype : {};
var vendor = proto.matches
|| proto.matchesSelector
|| proto.webkitMatchesSelector
|| proto.mozMatchesSelector
|| proto.msMatchesSelector
|| proto.oMatchesSelector;
var matchesSelector = match;
/**
* Match `el` to `selector`.
*
* @param {Element} el
* @param {String} selector
* @return {Boolean}
* @api public
*/
function match(el, selector) {
if (!el || el.nodeType !== 1) return false;
if (vendor) return vendor.call(el, selector);
var nodes = el.parentNode.querySelectorAll(selector);
for (var i = 0; i < nodes.length; i++) {
if (nodes[i] == el) return true;
}
return false;
}
/**
* Closest
*
* @param {Element} el
* @param {String} selector
* @param {Boolean} checkYourSelf (optional)
*/
function closest (element, selector, checkYourSelf) {
var currentElem = checkYourSelf ? element : element.parentNode;
while (currentElem && currentElem.nodeType !== document.DOCUMENT_NODE && currentElem.nodeType !== document.DOCUMENT_FRAGMENT_NODE) {
if (matchesSelector(currentElem, selector)) {
return currentElem;
}
currentElem = currentElem.parentNode;
}
return matchesSelector(currentElem, selector) ? currentElem : null;
}
var bind$1 = window.addEventListener ? 'addEventListener' : 'attachEvent',
unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
prefix$6 = bind$1 !== 'addEventListener' ? 'on' : '';
/**
* Bind `el` event `type` to `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
var bind_1 = function(el, type, fn, capture){
el[bind$1](prefix$6 + type, fn, capture || false);
return fn;
};
/**
* Unbind `el` event `type`'s callback `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
var unbind_1 = function(el, type, fn, capture){
el[unbind](prefix$6 + type, fn, capture || false);
return fn;
};
var componentEvent = {
bind: bind_1,
unbind: unbind_1
};
/**
* Module dependencies.
*/
/**
* Delegate event `type` to `selector`
* and invoke `fn(e)`. A callback function
* is returned which may be passed to `.unbind()`.
*
* @param {Element} el
* @param {String} selector
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @return {Function}
* @api public
*/
// Some events don't bubble, so we want to bind to the capture phase instead
// when delegating.
var forceCaptureEvents = ['focus', 'blur'];
function bind$2(el, selector, type, fn, capture) {
if (forceCaptureEvents.indexOf(type) !== -1) {
capture = true;
}
return componentEvent.bind(el, type, function (e) {
var target = e.target || e.srcElement;
e.delegateTarget = closest(target, selector, true);
if (e.delegateTarget) {
fn.call(el, e);
}
}, capture);
}
/**
* Unbind event `type`'s callback `fn`.
*
* @param {Element} el
* @param {String} type
* @param {Function} fn
* @param {Boolean} capture
* @api public
*/
function unbind$1(el, type, fn, capture) {
if (forceCaptureEvents.indexOf(type) !== -1) {
capture = true;
}
return componentEvent.unbind(el, type, fn, capture);
}
var delegate = {
bind: bind$2,
unbind: unbind$1
};
/**
* Expose `parse`.
*/
var domify = parse$1;
/**
* Tests for browser support.
*/
var innerHTMLBug = false;
var bugTestDiv;
if (typeof document !== 'undefined') {
bugTestDiv = document.createElement('div');
// Setup
bugTestDiv.innerHTML = ' <link/><table></table><a href="/a">a</a><input type="checkbox"/>';
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length;
bugTestDiv = undefined;
}
/**
* Wrap map from jquery.
*/
var map$1 = {
legend: [1, '<fieldset>', '</fieldset>'],
tr: [2, '<table><tbody>', '</tbody></table>'],
col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
// for script/link/style tags to work in IE6-8, you have to wrap
// in a div with a non-whitespace character in front, ha!
_default: innerHTMLBug ? [1, 'X<div>', '</div>'] : [0, '', '']
};
map$1.td =
map$1.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];
map$1.option =
map$1.optgroup = [1, '<select multiple="multiple">', '</select>'];
map$1.thead =
map$1.tbody =
map$1.colgroup =
map$1.caption =
map$1.tfoot = [1, '<table>', '</table>'];
map$1.polyline =
map$1.ellipse =
map$1.polygon =
map$1.circle =
map$1.text =
map$1.line =
map$1.path =
map$1.rect =
map$1.g = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];
/**
* Parse `html` and return a DOM Node instance, which could be a TextNode,
* HTML DOM Node of some kind (<div> for example), or a DocumentFragment
* instance, depending on the contents of the `html` string.
*
* @param {String} html - HTML string to "domify"
* @param {Document} doc - The `document` instance to create the Node for
* @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance
* @api private
*/
function parse$1(html, doc) {
if ('string' != typeof html) throw new TypeError('String expected');
// default to the global `document` object
if (!doc) doc = document;
// tag name
var m = /<([\w:]+)/.exec(html);
if (!m) return doc.createTextNode(html);
html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace
var tag = m[1];
// body support
if (tag == 'body') {
var el = doc.createElement('html');
el.innerHTML = html;
return el.removeChild(el.lastChild);
}
// wrap map
var wrap = map$1[tag] || map$1._default;
var depth = wrap[0];
var prefix = wrap[1];
var suffix = wrap[2];
var el = doc.createElement('div');
el.innerHTML = prefix + html + suffix;
while (depth--) el = el.lastChild;
// one element
if (el.firstChild == el.lastChild) {
return el.removeChild(el.firstChild);
}
// several elements
var fragment = doc.createDocumentFragment();
while (el.firstChild) {
fragment.appendChild(el.removeChild(el.firstChild));
}
return fragment;
}
function query(selector, el) {
el = el || document;
return el.querySelector(selector);
}
function all(selector, el) {
el = el || document;
return el.querySelectorAll(selector);
}
function remove$2(el) {
el.parentNode && el.parentNode.removeChild(el);
}
function ensureImported(element, target) {
if (element.ownerDocument !== target.ownerDocument) {
try {
// may fail on webkit
return target.ownerDocument.importNode(element, true);
} catch (e) {
// ignore
}
}
return element;
}
/**
* appendTo utility
*/
/**
* Append a node to a target element and return the appended node.
*
* @param {SVGElement} element
* @param {SVGElement} target
*
* @return {SVGElement} the appended node
*/
function appendTo(element, target) {
return target.appendChild(ensureImported(element, target));
}
/**
* append utility
*/
/**
* Append a node to an element
*
* @param {SVGElement} element
* @param {SVGElement} node
*
* @return {SVGElement} the element
*/
function append(target, node) {
appendTo(node, target);
return target;
}
/**
* attribute accessor utility
*/
var LENGTH_ATTR = 2;
var CSS_PROPERTIES = {
'alignment-baseline': 1,
'baseline-shift': 1,
'clip': 1,
'clip-path': 1,
'clip-rule': 1,
'color': 1,
'color-interpolation': 1,
'color-interpolation-filters': 1,
'color-profile': 1,
'color-rendering': 1,
'cursor': 1,
'direction': 1,
'display': 1,
'dominant-baseline': 1,
'enable-background': 1,
'fill': 1,
'fill-opacity': 1,
'fill-rule': 1,
'filter': 1,
'flood-color': 1,
'flood-opacity': 1,
'font': 1,
'font-family': 1,
'font-size': LENGTH_ATTR,
'font-size-adjust': 1,
'font-stretch': 1,
'font-style': 1,
'font-variant': 1,
'font-weight': 1,
'glyph-orientation-horizontal': 1,
'glyph-orientation-vertical': 1,
'image-rendering': 1,
'kerning': 1,
'letter-spacing': 1,
'lighting-color': 1,
'marker': 1,
'marker-end': 1,
'marker-mid': 1,
'marker-start': 1,
'mask': 1,
'opacity': 1,
'overflow': 1,
'pointer-events': 1,
'shape-rendering': 1,
'stop-color': 1,
'stop-opacity': 1,
'stroke': 1,
'stroke-dasharray': 1,
'stroke-dashoffset': 1,
'stroke-linecap': 1,
'stroke-linejoin': 1,
'stroke-miterlimit': 1,
'stroke-opacity': 1,
'stroke-width': LENGTH_ATTR,
'text-anchor': 1,
'text-decoration': 1,
'text-rendering': 1,
'unicode-bidi': 1,
'visibility': 1,
'word-spacing': 1,
'writing-mode': 1
};
function getAttribute(node, name) {
if (CSS_PROPERTIES[name]) {
return node.style[name];
} else {
return node.getAttributeNS(null, name);
}
}
function setAttribute(node, name, value) {
var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
var type = CSS_PROPERTIES[hyphenated];
if (type) {
// append pixel unit, unless present
if (type === LENGTH_ATTR && typeof value === 'number') {
value = String(value) + 'px';
}
node.style[hyphenated] = value;
} else {
node.setAttributeNS(null, name, value);
}
}
function setAttributes(node, attrs) {
var names = Object.keys(attrs), i, name;
for (i = 0, name; (name = names[i]); i++) {
setAttribute(node, name, attrs[name]);
}
}
/**
* Gets or sets raw attributes on a node.
*
* @param {SVGElement} node
* @param {Object} [attrs]
* @param {String} [name]
* @param {String} [value]
*
* @return {String}
*/
function attr(node, name, value) {
if (typeof name === 'string') {
if (value !== undefined) {
setAttribute(node, name, value);
} else {
return getAttribute(node, name);
}
} else {
setAttributes(node, name);
}
return node;
}
/**
* Clear utility
*/
function index(arr, obj) {
if (arr.indexOf) {
return arr.indexOf(obj);
}
for (var i = 0; i < arr.length; ++i) {
if (arr[i] === obj) {
return i;
}
}
return -1;
}
var re = /\s+/;
var toString = Object.prototype.toString;
function defined(o) {
return typeof o !== 'undefined';
}
/**
* Wrap `el` in a `ClassList`.
*
* @param {Element} el
* @return {ClassList}
* @api public
*/
function classes(el) {
return new ClassList(el);
}
function ClassList(el) {
if (!el || !el.nodeType) {
throw new Error('A DOM element reference is required');
}
this.el = el;
this.list = el.classList;
}
/**
* Add class `name` if not already present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.add = function(name) {
// classList
if (this.list) {
this.list.add(name);
return this;
}
// fallback
var arr = this.array();
var i = index(arr, name);
if (!~i) {
arr.push(name);
}
if (defined(this.el.className.baseVal)) {
this.el.className.baseVal = arr.join(' ');
} else {
this.el.className = arr.join(' ');
}
return this;
};
/**
* Remove class `name` when present, or
* pass a regular expression to remove
* any which match.
*
* @param {String|RegExp} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.remove = function(name) {
if ('[object RegExp]' === toString.call(name)) {
return this.removeMatching(name);
}
// classList
if (this.list) {
this.list.remove(name);
return this;
}
// fallback
var arr = this.array();
var i = index(arr, name);
if (~i) {
arr.splice(i, 1);
}
this.el.className.baseVal = arr.join(' ');
return this;
};
/**
* Remove all classes matching `re`.
*
* @param {RegExp} re
* @return {ClassList}
* @api private
*/
ClassList.prototype.removeMatching = function(re) {
var arr = this.array();
for (var i = 0; i < arr.length; i++) {
if (re.test(arr[i])) {
this.remove(arr[i]);
}
}
return this;
};
/**
* Toggle class `name`, can force state via `force`.
*
* For browsers that support classList, but do not support `force` yet,
* the mistake will be detected and corrected.
*
* @param {String} name
* @param {Boolean} force
* @return {ClassList}
* @api public
*/
ClassList.prototype.toggle = function(name, force) {
// classList
if (this.list) {
if (defined(force)) {
if (force !== this.list.toggle(name, force)) {
this.list.toggle(name); // toggle again to correct
}
} else {
this.list.toggle(name);
}
return this;
}
// fallback
if (defined(force)) {
if (!force) {
this.remove(name);
} else {
this.add(name);
}
} else {
if (this.has(name)) {
this.remove(name);
} else {
this.add(name);
}
}
return this;
};
/**
* Return an array of classes.
*
* @return {Array}
* @api public
*/
ClassList.prototype.array = function() {
var className = this.el.getAttribute('class') || '';
var str = className.replace(/^\s+|\s+$/g, '');
var arr = str.split(re);
if ('' === arr[0]) {
arr.shift();
}
return arr;
};
/**
* Check if class `name` is present.
*
* @param {String} name
* @return {ClassList}
* @api public
*/
ClassList.prototype.has =
ClassList.prototype.contains = function(name) {
return (
this.list ?
this.list.contains(name) :
!! ~index(this.array(), name)
);
};
function remove$1(element) {
var parent = element.parentNode;
if (parent) {
parent.removeChild(element);
}
return element;
}
/**
* Clear utility
*/
/**
* Removes all children from the given element
*
* @param {DOMElement} element
* @return {DOMElement} the element (for chaining)
*/
function clear(element) {
var child;
while ((child = element.firstChild)) {
remove$1(child);
}
return element;
}
function clone$1(element) {
return element.cloneNode(true);
}
var ns = {
svg: 'http://www.w3.org/2000/svg'
};
/**
* DOM parsing utility
*/
var SVG_START = '<svg xmlns="' + ns.svg + '"';
function parse(svg) {
var unwrap = false;
// ensure we import a valid svg document
if (svg.substring(0, 4) === '<svg') {
if (svg.indexOf(ns.svg) === -1) {
svg = SVG_START + svg.substring(4);
}
} else {
// namespace svg
svg = SVG_START + '>' + svg + '</svg>';
unwrap = true;
}
var parsed = parseDocument(svg);
if (!unwrap) {
return parsed;
}
var fragment = document.createDocumentFragment();
var parent = parsed.firstChild;
while (parent.firstChild) {
fragment.appendChild(parent.firstChild);
}
return fragment;
}
function parseDocument(svg) {
var parser;
// parse
parser = new DOMParser();
parser.async = false;
return parser.parseFromString(svg, 'text/xml');
}
/**
* Create utility for SVG elements
*/
/**
* Create a specific type from name or SVG markup.
*
* @param {String} name the name or markup of the element
* @param {Object} [attrs] attributes to set on the element
*
* @returns {SVGElement}
*/
function create$1(name, attrs) {
var element;
if (name.charAt(0) === '<') {
element = parse(name).firstChild;
element = document.importNode(element, true);
} else {
element = document.createElementNS(ns.svg, name);
}
if (attrs) {
attr(element, attrs);
}
return element;
}
/**
* Geometry helpers
*/
// fake node used to instantiate svg geometry elements
var node = null;
function getNode() {
if (node === null) {
node = create$1('svg');
}
return node;
}
function extend$1(object, props) {
var i, k, keys = Object.keys(props);
for (i = 0; (k = keys[i]); i++) {
object[k] = props[k];
}
return object;
}
/**
* Create matrix via args.
*
* @example
*
* createMatrix({ a: 1, b: 1 });
* createMatrix();
* createMatrix(1, 2, 0, 0, 30, 20);
*
* @return {SVGMatrix}
*/
function createMatrix(a, b, c, d, e, f) {
var matrix = getNode().createSVGMatrix();
switch (arguments.length) {
case 0:
return matrix;
case 1:
return extend$1(matrix, a);
case 6:
return extend$1(matrix, {
a: a,
b: b,
c: c,
d: d,
e: e,
f: f
});
}
}
function createTransform(matrix) {
if (matrix) {
return getNode().createSVGTransformFromMatrix(matrix);
} else {
return getNode().createSVGTransform();
}
}
/**
* Serialization util
*/
var TEXT_ENTITIES = /([&<>]{1})/g;
var ATTR_ENTITIES = /([\n\r"]{1})/g;
var ENTITY_REPLACEMENT = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '\''
};
function escape$1(str, pattern) {
function replaceFn(match, entity) {
return ENTITY_REPLACEMENT[entity] || entity;
}
return str.replace(pattern, replaceFn);
}
function serialize(node, output) {
var i, len, attrMap, attrNode, childNodes;
switch (node.nodeType) {
// TEXT
case 3:
// replace special XML characters
output.push(escape$1(node.textContent, TEXT_ENTITIES));
break;
// ELEMENT
case 1:
output.push('<', node.tagName);
if (node.hasAttributes()) {
attrMap = node.attributes;
for (i = 0, len = attrMap.length; i < len; ++i) {
attrNode = attrMap.item(i);
output.push(' ', attrNode.name, '="', escape$1(attrNode.value, ATTR_ENTITIES), '"');
}
}
if (node.hasChildNodes()) {
output.push('>');
childNodes = node.childNodes;
for (i = 0, len = childNodes.length; i < len; ++i) {
serialize(childNodes.item(i), output);
}
output.push('</', node.tagName, '>');
} else {
output.push('/>');
}
break;
// COMMENT
case 8:
output.push('<!--', escape$1(node.nodeValue, TEXT_ENTITIES), '-->');
break;
// CDATA
case 4:
output.push('<![CDATA[', node.nodeValue, ']]>');
break;
default:
throw new Error('unable to handle node ' + node.nodeType);
}
return output;
}
/**
* innerHTML like functionality for SVG elements.
* based on innerSVG (https://code.google.com/p/innersvg)
*/
function set$1(element, svg) {
var parsed = parse(svg);
// clear element contents
clear(element);
if (!svg) {
return;
}
if (!isFragment(parsed)) {
// extract <svg> from parsed document
parsed = parsed.documentElement;
}
var nodes = slice$1(parsed.childNodes);
// import + append each node
for (var i = 0; i < nodes.length; i++) {
appendTo(nodes[i], element);
}
}
function get$1(element) {
var child = element.firstChild,
output = [];
while (child) {
serialize(child, output);
child = child.nextSibling;
}
return output.join('');
}
function isFragment(node) {
return node.nodeName === '#document-fragment';
}
function innerSVG(element, svg) {
if (svg !== undefined) {
try {
set$1(element, svg);
} catch (e) {
throw new Error('error parsing SVG: ' + e.message);
}
return element;
} else {
return get$1(element);
}
}
function slice$1(arr) {
return Array.prototype.slice.call(arr);
}
/**
* transform accessor utility
*/
function wrapMatrix(transformList, transform) {
if (transform instanceof SVGMatrix) {
return transformList.createSVGTransformFromMatrix(transform);
}
return transform;
}
function setTransforms(transformList, transforms) {
var i, t;
transformList.clear();
for (i = 0; (t = transforms[i]); i++) {
transformList.appendItem(wrapMatrix(transformList, t));
}
}
/**
* Get or set the transforms on the given node.
*
* @param {SVGElement} node
* @param {SVGTransform|SVGMatrix|Array<SVGTransform|SVGMatrix>} [transforms]
*
* @return {SVGTransform} the consolidated transform
*/
function transform$1(node, transforms) {
var transformList = node.transform.baseVal;
if (transforms) {
if (!Array.isArray(transforms)) {
transforms = [ transforms ];
}
setTransforms(transformList, transforms);
}
return transformList.consolidate();
}
var CLASS_PATTERN = /^class /;
/**
* @param {function} fn
*
* @return {boolean}
*/
function isClass(fn) {
return CLASS_PATTERN.test(fn.toString());
}
/**
* @param {any} obj
*
* @return {boolean}
*/
function isArray$1(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
/**
* @param {any} obj
* @param {string} prop
*
* @return {boolean}
*/
function hasOwnProp(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
/**
* @typedef {import('./index').InjectAnnotated } InjectAnnotated
*/
/**
* @template T
*
* @params {[...string[], T] | ...string[], T} args
*
* @return {T & InjectAnnotated}
*/
function annotate() {
var args = Array.prototype.slice.call(arguments);
if (args.length === 1 && isArray$1(args[0])) {
args = args[0];
}
var fn = args.pop();
fn.$inject = args;
return fn;
}
// Current limitations:
// - can't put into "function arg" comments
// function /* (no parenthesis like this) */ (){}
// function abc( /* xx (no parenthesis like this) */ a, b) {}
//
// Just put the comment before function or inside:
// /* (((this is fine))) */ function(a, b) {}
// function abc(a) { /* (((this is fine))) */}
//
// - can't reliably auto-annotate constructor; we'll match the
// first constructor(...) pattern found which may be the one
// of a nested class, too.
var CONSTRUCTOR_ARGS = /constructor\s*[^(]*\(\s*([^)]*)\)/m;
var FN_ARGS = /^(?:async\s+)?(?:function\s*[^(]*)?(?:\(\s*([^)]*)\)|(\w+))/m;
var FN_ARG = /\/\*([^*]*)\*\//m;
/**
* @param {unknown} fn
*
* @return {string[]}
*/
function parseAnnotations(fn) {
if (typeof fn !== 'function') {
throw new Error('Cannot annotate "' + fn + '". Expected a function!');
}
var match = fn.toString().match(isClass(fn) ? CONSTRUCTOR_ARGS : FN_ARGS);
// may parse class without constructor
if (!match) {
return [];
}
var args = match[1] || match[2];
return args && args.split(',').map(function(arg) {
var argMatch = arg.match(FN_ARG);
return (argMatch && argMatch[1] || arg).trim();
}) || [];
}
/**
* @typedef { import('./index').ModuleDeclaration } ModuleDeclaration
* @typedef { import('./index').ModuleDefinition } ModuleDefinition
* @typedef { import('./index').InjectorContext } InjectorContext
*/
/**
* Create a new injector with the given modules.
*
* @param {ModuleDefinition[]} modules
* @param {InjectorContext} [parent]
*/
function Injector(modules, parent) {
parent = parent || {
get: function(name, strict) {
currentlyResolving.push(name);
if (strict === false) {
return null;
} else {
throw error('No provider for "' + name + '"!');
}
}
};
var currentlyResolving = [];
var providers = this._providers = Object.create(parent._providers || null);
var instances = this._instances = Object.create(null);
var self = instances.injector = this;
var error = function(msg) {
var stack = currentlyResolving.join(' -> ');
currentlyResolving.length = 0;
return new Error(stack ? msg + ' (Resolving: ' + stack + ')' : msg);
};
/**
* Return a named service.
*
* @param {string} name
* @param {boolean} [strict=true] if false, resolve missing services to null
*
* @return {any}
*/
function get(name, strict) {
if (!providers[name] && name.indexOf('.') !== -1) {
var parts = name.split('.');
var pivot = get(parts.shift());
while (parts.length) {
pivot = pivot[parts.shift()];
}
return pivot;
}
if (hasOwnProp(instances, name)) {
return instances[name];
}
if (hasOwnProp(providers, name)) {
if (currentlyResolving.indexOf(name) !== -1) {
currentlyResolving.push(name);
throw error('Cannot resolve circular dependency!');
}
currentlyResolving.push(name);
instances[name] = providers[name][0](providers[name][1]);
currentlyResolving.pop();
return instances[name];
}
return parent.get(name, strict);
}
function fnDef(fn, locals) {
if (typeof locals === 'undefined') {
locals = {};
}
if (typeof fn !== 'function') {
if (isArray$1(fn)) {
fn = annotate(fn.slice());
} else {
throw new Error('Cannot invoke "' + fn + '". Expected a function!');
}
}
var inject = fn.$inject || parseAnnotations(fn);
var dependencies = inject.map(function(dep) {
if (hasOwnProp(locals, dep)) {
return locals[dep];
} else {
return get(dep);
}
});
return {
fn: fn,
dependencies: dependencies
};
}
function instantiate(Type) {
var def = fnDef(Type);
var fn = def.fn,
dependencies = def.dependencies;
// instantiate var args constructor
var Constructor = Function.prototype.bind.apply(fn, [ null ].concat(dependencies));
return new Constructor();
}
function invoke(func, context, locals) {
var def = fnDef(func, locals);
var fn = def.fn,
dependencies = def.dependencies;
return fn.apply(context, dependencies);
}
/**
* @param {Injector} childInjector
*
* @return {Function}
*/
function createPrivateInjectorFactory(childInjector) {
return annotate(function(key) {
return childInjector.get(key);
});
}
/**
* @param {ModuleDefinition[]} modules
* @param {string[]} [forceNewInstances]
*
* @return {Injector}
*/
function createChild(modules, forceNewInstances) {
if (forceNewInstances && forceNewInstances.length) {
var fromParentModule = Object.create(null);
var matchedScopes = Object.create(null);
var privateInjectorsCache = [];
var privateChildInjectors = [];
var privateChildFactories = [];
var provider;
var cacheIdx;
var privateChildInjector;
var privateChildInjectorFactory;
for (var name in providers) {
provider = providers[name];
if (forceNewInstances.indexOf(name) !== -1) {
if (provider[2] === 'private') {
cacheIdx = privateInjectorsCache.indexOf(provider[3]);
if (cacheIdx === -1) {
privateChildInjector = provider[3].createChild([], forceNewInstances);
privateChildInjectorFactory = createPrivateInjectorFactory(privateChildInjector);
privateInjectorsCache.push(provider[3]);
privateChildInjectors.push(privateChildInjector);
privateChildFactories.push(privateChildInjectorFactory);
fromParentModule[name] = [ privateChildInjectorFactory, name, 'private', privateChildInjector ];
} else {
fromParentModule[name] = [ privateChildFactories[cacheIdx], name, 'private', privateChildInjectors[cacheIdx] ];
}
} else {
fromParentModule[name] = [ provider[2], provider[1] ];
}
matchedScopes[name] = true;
}
if ((provider[2] === 'factory' || provider[2] === 'type') && provider[1].$scope) {
/* jshint -W083 */
forceNewInstances.forEach(function(scope) {
if (provider[1].$scope.indexOf(scope) !== -1) {
fromParentModule[name] = [ provider[2], provider[1] ];
matchedScopes[scope] = true;
}
});
}
}
forceNewInstances.forEach(function(scope) {
if (!matchedScopes[scope]) {
throw new Error('No provider for "' + scope + '". Cannot use provider from the parent!');
}
});
modules.unshift(fromParentModule);
}
return new Injector(modules, self);
}
var factoryMap = {
factory: invoke,
type: instantiate,
value: function(value) {
return value;
}
};
/**
* @param {ModuleDefinition} moduleDefinition
* @param {Injector} injector
*/
function createInitializer(moduleDefinition, injector) {
var initializers = moduleDefinition.__init__ || [];
return function() {
initializers.forEach(function(initializer) {
// eagerly resolve component (fn or string)
if (typeof initializer === 'string') {
injector.get(initializer);
} else {
injector.invoke(initializer);
}
});
};
}
/**
* @param {ModuleDefinition} moduleDefinition
*/
function loadModule(moduleDefinition) {
var moduleExports = moduleDefinition.__exports__;
// private module
if (moduleExports) {
var nestedModules = moduleDefinition.__modules__;
var clonedModule = Object.keys(moduleDefinition).reduce(function(clonedModule, key) {
if (key !== '__exports__' && key !== '__modules__' && key !== '__init__' && key !== '__depends__') {
clonedModule[key] = moduleDefinition[key];
}
return clonedModule;
}, Object.create(null));
var childModules = (nestedModules || []).concat(clonedModule);
var privateInjector = createChild(childModules);
var getFromPrivateInjector = annotate(function(key) {
return privateInjector.get(key);
});
moduleExports.forEach(function(key) {
providers[key] = [ getFromPrivateInjector, key, 'private', privateInjector ];
});
// ensure child injector initializes
var initializers = (moduleDefinition.__init__ || []).slice();
initializers.unshift(function() {
privateInjector.init();
});
moduleDefinition = Object.assign({}, moduleDefinition, {
__init__: initializers
});
return createInitializer(moduleDefinition, privateInjector);
}
// normal module
Object.keys(moduleDefinition).forEach(function(key) {
if (key === '__init__' || key === '__depends__') {
return;
}
if (moduleDefinition[key][2] === 'private') {
providers[key] = moduleDefinition[key];
return;
}
var type = moduleDefinition[key][0];
var value = moduleDefinition[key][1];
providers[key] = [ factoryMap[type], arrayUnwrap(type, value), type ];
});
return createInitializer(moduleDefinition, self);
}
/**
* @param {ModuleDefinition[]} moduleDefinitions
* @param {ModuleDefinition} moduleDefinition
*
* @return {ModuleDefinition[]}
*/
function resolveDependencies(moduleDefinitions, moduleDefinition) {
if (moduleDefinitions.indexOf(moduleDefinition) !== -1) {
return moduleDefinitions;
}
moduleDefinitions = (moduleDefinition.__depends__ || []).reduce(resolveDependencies, moduleDefinitions);
if (moduleDefinitions.indexOf(moduleDefinition) !== -1) {
return moduleDefinitions;
}
return moduleDefinitions.concat(moduleDefinition);
}
/**
* @param {ModuleDefinition[]} moduleDefinitions
*
* @return { () => void } initializerFn
*/
function bootstrap(moduleDefinitions) {
var initializers = moduleDefinitions
.reduce(resolveDependencies, [])
.map(loadModule);
var initialized = false;
return function() {
if (initialized) {
return;
}
initialized = true;
initializers.forEach(function(initializer) {
return initializer();
});
};
}
// public API
this.get = get;
this.invoke = invoke;
this.instantiate = instantiate;
this.createChild = createChild;
// setup
this.init = bootstrap(modules);
}
// helpers ///////////////
function arrayUnwrap(type, value) {
if (type !== 'value' && isArray$1(value)) {
value = annotate(value.slice());
}
return value;
}
var DEFAULT_RENDER_PRIORITY$1 = 1000;
/**
* The base implementation of shape and connection renderers.
*
* @param {EventBus} eventBus
* @param {number} [renderPriority=1000]
*/
function BaseRenderer(eventBus, renderPriority) {
var self = this;
renderPriority = renderPriority || DEFAULT_RENDER_PRIORITY$1;
eventBus.on([ 'render.shape', 'render.connection' ], renderPriority, function(evt, context) {
var type = evt.type,
element = context.element,
visuals = context.gfx,
attrs = context.attrs;
if (self.canRender(element)) {
if (type === 'render.shape') {
return self.drawShape(visuals, element, attrs);
} else {
return self.drawConnection(visuals, element, attrs);
}
}
});
eventBus.on([ 'render.getShapePath', 'render.getConnectionPath' ], renderPriority, function(evt, element) {
if (self.canRender(element)) {
if (evt.type === 'render.getShapePath') {
return self.getShapePath(element);
} else {
return self.getConnectionPath(element);
}
}
});
}
/**
* Should check whether *this* renderer can render
* the element/connection.
*
* @param {element} element
*
* @returns {boolean}
*/
BaseRenderer.prototype.canRender = function() {};
/**
* Provides the shape's snap svg element to be drawn on the `canvas`.
*
* @param {djs.Graphics} visuals
* @param {Shape} shape
*
* @returns {Snap.svg} [returns a Snap.svg paper element ]
*/
BaseRenderer.prototype.drawShape = function() {};
/**
* Provides the shape's snap svg element to be drawn on the `canvas`.
*
* @param {djs.Graphics} visuals
* @param {Connection} connection
*
* @returns {Snap.svg} [returns a Snap.svg paper element ]
*/
BaseRenderer.prototype.drawConnection = function() {};
/**
* Gets the SVG path of a shape that represents it's visual bounds.
*
* @param {Shape} shape
*
* @return {string} svg path
*/
BaseRenderer.prototype.getShapePath = function() {};
/**
* Gets the SVG path of a connection that represents it's visual bounds.
*
* @param {Connection} connection
*
* @return {string} svg path
*/
BaseRenderer.prototype.getConnectionPath = function() {};
function componentsToPath(elements) {
return elements.join(',').replace(/,?([A-z]),?/g, '$1');
}
function toSVGPoints(points) {
var result = '';
for (var i = 0, p; (p = points[i]); i++) {
result += p.x + ',' + p.y + ' ';
}
return result;
}
function createLine(points, attrs) {
var line = create$1('polyline');
attr(line, { points: toSVGPoints(points) });
if (attrs) {
attr(line, attrs);
}
return line;
}
function updateLine(gfx, points) {
attr(gfx, { points: toSVGPoints(points) });
return gfx;
}
/**
* @typedef { {x:number, y: number, width: number, height: number} } Bounds
*/
/**
* Get parent elements.
*
* @param {Array<djs.model.base>} elements
*
* @returns {Array<djs.model.Base>}
*/
function getParents$1(elements) {
// find elements that are not children of any other elements
return filter(elements, function(element) {
return !find(elements, function(e) {
return e !== element && getParent$1(element, e);
});
});
}
function getParent$1(element, parent) {
if (!parent) {
return;
}
if (element === parent) {
return parent;
}
if (!element.parent) {
return;
}
return getParent$1(element.parent, parent);
}
/**
* Adds an element to a collection and returns true if the
* element was added.
*
* @param {Array<Object>} elements
* @param {Object} e
* @param {boolean} unique
*/
function add$1(elements, e, unique) {
var canAdd = !unique || elements.indexOf(e) === -1;
if (canAdd) {
elements.push(e);
}
return canAdd;
}
/**
* Iterate over each element in a collection, calling the iterator function `fn`
* with (element, index, recursionDepth).
*
* Recurse into all elements that are returned by `fn`.
*
* @param {Object|Array<Object>} elements
* @param {Function} fn iterator function called with (element, index, recursionDepth)
* @param {number} [depth] maximum recursion depth
*/
function eachElement(elements, fn, depth) {
depth = depth || 0;
if (!isArray$3(elements)) {
elements = [ elements ];
}
forEach$1(elements, function(s, i) {
var filter = fn(s, i, depth);
if (isArray$3(filter) && filter.length) {
eachElement(filter, fn, depth + 1);
}
});
}
/**
* Collects self + child elements up to a given depth from a list of elements.
*
* @param {djs.model.Base|Array<djs.model.Base>} elements the elements to select the children from
* @param {boolean} unique whether to return a unique result set (no duplicates)
* @param {number} maxDepth the depth to search through or -1 for infinite
*
* @return {Array<djs.model.Base>} found elements
*/
function selfAndChildren(elements, unique, maxDepth) {
var result = [],
processedChildren = [];
eachElement(elements, function(element, i, depth) {
add$1(result, element, unique);
var children = element.children;
// max traversal depth not reached yet
if (maxDepth === -1 || depth < maxDepth) {
// children exist && children not yet processed
if (children && add$1(processedChildren, children, unique)) {
return children;
}
}
});
return result;
}
/**
* Return self + ALL children for a number of elements
*
* @param {Array<djs.model.Base>} elements to query
* @param {boolean} allowDuplicates to allow duplicates in the result set
*
* @return {Array<djs.model.Base>} the collected elements
*/
function selfAndAllChildren(elements, allowDuplicates) {
return selfAndChildren(elements, !allowDuplicates, -1);
}
/**
* Gets the the closure for all selected elements,
* their enclosed children and connections.
*
* @param {Array<djs.model.Base>} elements
* @param {boolean} [isTopLevel=true]
* @param {Object} [existingClosure]
*
* @return {Object} newClosure
*/
function getClosure(elements, isTopLevel, closure) {
if (isUndefined$2(isTopLevel)) {
isTopLevel = true;
}
if (isObject(isTopLevel)) {
closure = isTopLevel;
isTopLevel = true;
}
closure = closure || {};
var allShapes = copyObject(closure.allShapes),
allConnections = copyObject(closure.allConnections),
enclosedElements = copyObject(closure.enclosedElements),
enclosedConnections = copyObject(closure.enclosedConnections);
var topLevel = copyObject(
closure.topLevel,
isTopLevel && groupBy(elements, function(e) { return e.id; })
);
function handleConnection(c) {
if (topLevel[c.source.id] && topLevel[c.target.id]) {
topLevel[c.id] = [ c ];
}
// not enclosed as a child, but maybe logically
// (connecting two moved elements?)
if (allShapes[c.source.id] && allShapes[c.target.id]) {
enclosedConnections[c.id] = enclosedElements[c.id] = c;
}
allConnections[c.id] = c;
}
function handleElement(element) {
enclosedElements[element.id] = element;
if (element.waypoints) {
// remember connection
enclosedConnections[element.id] = allConnections[element.id] = element;
} else {
// remember shape
allShapes[element.id] = element;
// remember all connections
forEach$1(element.incoming, handleConnection);
forEach$1(element.outgoing, handleConnection);
// recurse into children
return element.children;
}
}
eachElement(elements, handleElement);
return {
allShapes: allShapes,
allConnections: allConnections,
topLevel: topLevel,
enclosedConnections: enclosedConnections,
enclosedElements: enclosedElements
};
}
/**
* Returns the surrounding bbox for all elements in
* the array or the element primitive.
*
* @param {Array<djs.model.Shape>|djs.model.Shape} elements
* @param {boolean} [stopRecursion=false]
*
* @return {Bounds}
*/
function getBBox(elements, stopRecursion) {
stopRecursion = !!stopRecursion;
if (!isArray$3(elements)) {
elements = [ elements ];
}
var minX,
minY,
maxX,
maxY;
forEach$1(elements, function(element) {
// If element is a connection the bbox must be computed first
var bbox = element;
if (element.waypoints && !stopRecursion) {
bbox = getBBox(element.waypoints, true);
}
var x = bbox.x,
y = bbox.y,
height = bbox.height || 0,
width = bbox.width || 0;
if (x < minX || minX === undefined) {
minX = x;
}
if (y < minY || minY === undefined) {
minY = y;
}
if ((x + width) > maxX || maxX === undefined) {
maxX = x + width;
}
if ((y + height) > maxY || maxY === undefined) {
maxY = y + height;
}
});
return {
x: minX,
y: minY,
height: maxY - minY,
width: maxX - minX
};
}
/**
* Returns all elements that are enclosed from the bounding box.
*
* * If bbox.(width|height) is not specified the method returns
* all elements with element.x/y > bbox.x/y
* * If only bbox.x or bbox.y is specified, method return all elements with
* e.x > bbox.x or e.y > bbox.y
*
* @param {Array<djs.model.Shape>} elements List of Elements to search through
* @param {djs.model.Shape} bbox the enclosing bbox.
*
* @return {Array<djs.model.Shape>} enclosed elements
*/
function getEnclosedElements(elements, bbox) {
var filteredElements = {};
forEach$1(elements, function(element) {
var e = element;
if (e.waypoints) {
e = getBBox(e);
}
if (!isNumber(bbox.y) && (e.x > bbox.x)) {
filteredElements[element.id] = element;
}
if (!isNumber(bbox.x) && (e.y > bbox.y)) {
filteredElements[element.id] = element;
}
if (e.x > bbox.x && e.y > bbox.y) {
if (isNumber(bbox.width) && isNumber(bbox.height) &&
e.width + e.x < bbox.width + bbox.x &&
e.height + e.y < bbox.height + bbox.y) {
filteredElements[element.id] = element;
} else if (!isNumber(bbox.width) || !isNumber(bbox.height)) {
filteredElements[element.id] = element;
}
}
});
return filteredElements;
}
function getType(element) {
if ('waypoints' in element) {
return 'connection';
}
if ('x' in element) {
return 'shape';
}
return 'root';
}
function isFrameElement$1(element) {
return !!(element && element.isFrame);
}
// helpers ///////////////////////////////
function copyObject(src1, src2) {
return assign({}, src1 || {}, src2 || {});
}
// apply default renderer with lowest possible priority
// so that it only kicks in if noone else could render
var DEFAULT_RENDER_PRIORITY = 1;
/**
* The default renderer used for shapes and connections.
*
* @param {EventBus} eventBus
* @param {Styles} styles
*/
function DefaultRenderer(eventBus, styles) {
//
BaseRenderer.call(this, eventBus, DEFAULT_RENDER_PRIORITY);
this.CONNECTION_STYLE = styles.style([ 'no-fill' ], { strokeWidth: 5, stroke: 'fuchsia' });
this.SHAPE_STYLE = styles.style({ fill: 'white', stroke: 'fuchsia', strokeWidth: 2 });
this.FRAME_STYLE = styles.style([ 'no-fill' ], { stroke: 'fuchsia', strokeDasharray: 4, strokeWidth: 2 });
}
e(DefaultRenderer, BaseRenderer);
DefaultRenderer.prototype.canRender = function() {
return true;
};
DefaultRenderer.prototype.drawShape = function drawShape(visuals, element, attrs) {
var rect = create$1('rect');
attr(rect, {
x: 0,
y: 0,
width: element.width || 0,
height: element.height || 0
});
if (isFrameElement$1(element)) {
attr(rect, assign({}, this.FRAME_STYLE, attrs || {}));
} else {
attr(rect, assign({}, this.SHAPE_STYLE, attrs || {}));
}
append(visuals, rect);
return rect;
};
DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection, attrs) {
var line = createLine(connection.waypoints, assign({}, this.CONNECTION_STYLE, attrs || {}));
append(visuals, line);
return line;
};
DefaultRenderer.prototype.getShapePath = function getShapePath(shape) {
var x = shape.x,
y = shape.y,
width = shape.width,
height = shape.height;
var shapePath = [
[ 'M', x, y ],
[ 'l', width, 0 ],
[ 'l', 0, height ],
[ 'l', -width, 0 ],
[ 'z' ]
];
return componentsToPath(shapePath);
};
DefaultRenderer.prototype.getConnectionPath = function getConnectionPath(connection) {
var waypoints = connection.waypoints;
var idx, point, connectionPath = [];
for (idx = 0; (point = waypoints[idx]); idx++) {
// take invisible docking into account
// when creating the path
point = point.original || point;
connectionPath.push([ idx === 0 ? 'M' : 'L', point.x, point.y ]);
}
return componentsToPath(connectionPath);
};
DefaultRenderer.$inject = [ 'eventBus', 'styles' ];
/**
* A component that manages shape styles
*/
function Styles() {
var defaultTraits = {
'no-fill': {
fill: 'none'
},
'no-border': {
strokeOpacity: 0.0
},
'no-events': {
pointerEvents: 'none'
}
};
var self = this;
/**
* Builds a style definition from a className, a list of traits and an object of additional attributes.
*
* @param {string} className
* @param {Array<string>} traits
* @param {Object} additionalAttrs
*
* @return {Object} the style defintion
*/
this.cls = function(className, traits, additionalAttrs) {
var attrs = this.style(traits, additionalAttrs);
return assign(attrs, { 'class': className });
};
/**
* Builds a style definition from a list of traits and an object of additional attributes.
*
* @param {Array<string>} traits
* @param {Object} additionalAttrs
*
* @return {Object} the style defintion
*/
this.style = function(traits, additionalAttrs) {
if (!isArray$3(traits) && !additionalAttrs) {
additionalAttrs = traits;
traits = [];
}
var attrs = reduce(traits, function(attrs, t) {
return assign(attrs, defaultTraits[t] || {});
}, {});
return additionalAttrs ? assign(attrs, additionalAttrs) : attrs;
};
this.computeStyle = function(custom, traits, defaultStyles) {
if (!isArray$3(traits)) {
defaultStyles = traits;
traits = [];
}
return self.style(traits || [], assign({}, defaultStyles, custom || {}));
};
}
var DrawModule$1 = {
__init__: [ 'defaultRenderer' ],
defaultRenderer: [ 'type', DefaultRenderer ],
styles: [ 'type', Styles ]
};
/**
* Failsafe remove an element from a collection
*
* @param {Array<Object>} [collection]
* @param {Object} [element]
*
* @return {number} the previous index of the element
*/
function remove(collection, element) {
if (!collection || !element) {
return -1;
}
var idx = collection.indexOf(element);
if (idx !== -1) {
collection.splice(idx, 1);
}
return idx;
}
/**
* Fail save add an element to the given connection, ensuring
* it does not yet exist.
*
* @param {Array<Object>} collection
* @param {Object} element
* @param {number} idx
*/
function add(collection, element, idx) {
if (!collection || !element) {
return;
}
if (typeof idx !== 'number') {
idx = -1;
}
var currentIdx = collection.indexOf(element);
if (currentIdx !== -1) {
if (currentIdx === idx) {
// nothing to do, position has not changed
return;
} else {
if (idx !== -1) {
// remove from current position
collection.splice(currentIdx, 1);
} else {
// already exists in collection
return;
}
}
}
if (idx !== -1) {
// insert at specified position
collection.splice(idx, 0, element);
} else {
// push to end
collection.push(element);
}
}
/**
* Fail save get the index of an element in a collection.
*
* @param {Array<Object>} collection
* @param {Object} element
*
* @return {number} the index or -1 if collection or element do
* not exist or the element is not contained.
*/
function indexOf(collection, element) {
if (!collection || !element) {
return -1;
}
return collection.indexOf(element);
}
/**
* Computes the distance between two points
*
* @param {Point} p
* @param {Point} q
*
* @return {number} distance
*/
function pointDistance(a, b) {
if (!a || !b) {
return -1;
}
return Math.sqrt(
Math.pow(a.x - b.x, 2) +
Math.pow(a.y - b.y, 2)
);
}
/**
* Returns true if the point r is on the line between p and q
*
* @param {Point} p
* @param {Point} q
* @param {Point} r
* @param {number} [accuracy=5] accuracy for points on line check (lower is better)
*
* @return {boolean}
*/
function pointsOnLine(p, q, r, accuracy) {
if (typeof accuracy === 'undefined') {
accuracy = 5;
}
if (!p || !q || !r) {
return false;
}
var val = (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x),
dist = pointDistance(p, q);
// @see http://stackoverflow.com/a/907491/412190
return Math.abs(val / dist) <= accuracy;
}
var ALIGNED_THRESHOLD = 2;
/**
* Check whether two points are horizontally or vertically aligned.
*
* @param {Array<Point>|Point}
* @param {Point}
*
* @return {string|boolean}
*/
function pointsAligned(a, b) {
var points;
if (isArray$3(a)) {
points = a;
} else {
points = [ a, b ];
}
if (pointsAlignedHorizontally(points)) {
return 'h';
}
if (pointsAlignedVertically(points)) {
return 'v';
}
return false;
}
function pointsAlignedHorizontally(a, b) {
var points;
if (isArray$3(a)) {
points = a;
} else {
points = [ a, b ];
}
var firstPoint = points.slice().shift();
return every(points, function(point) {
return Math.abs(firstPoint.y - point.y) <= ALIGNED_THRESHOLD;
});
}
function pointsAlignedVertically(a, b) {
var points;
if (isArray$3(a)) {
points = a;
} else {
points = [ a, b ];
}
var firstPoint = points.slice().shift();
return every(points, function(point) {
return Math.abs(firstPoint.x - point.x) <= ALIGNED_THRESHOLD;
});
}
/**
* Returns true if the point p is inside the rectangle rect
*
* @param {Point} p
* @param {Rect} rect
* @param {number} tolerance
*
* @return {boolean}
*/
function pointInRect(p, rect, tolerance) {
tolerance = tolerance || 0;
return p.x > rect.x - tolerance &&
p.y > rect.y - tolerance &&
p.x < rect.x + rect.width + tolerance &&
p.y < rect.y + rect.height + tolerance;
}
/**
* Returns a point in the middle of points p and q
*
* @param {Point} p
* @param {Point} q
*
* @return {Point} middle point
*/
function getMidPoint(p, q) {
return {
x: Math.round(p.x + ((q.x - p.x) / 2.0)),
y: Math.round(p.y + ((q.y - p.y) / 2.0))
};
}
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
/**
* This file contains source code adapted from Snap.svg (licensed Apache-2.0).
*
* @see https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js
*/
/* eslint no-fallthrough: "off" */
var p2s = /,?([a-z]),?/gi,
toFloat = parseFloat,
math = Math,
PI = math.PI,
mmin = math.min,
mmax = math.max,
pow = math.pow,
abs$7 = math.abs,
pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?[\s]*,?[\s]*)+)/ig,
pathValues = /(-?\d*\.?\d*(?:e[-+]?\d+)?)[\s]*,?[\s]*/ig;
var isArray = Array.isArray || function(o) { return o instanceof Array; };
function hasProperty(obj, property) {
return Object.prototype.hasOwnProperty.call(obj, property);
}
function clone(obj) {
if (typeof obj == 'function' || Object(obj) !== obj) {
return obj;
}
var res = new obj.constructor;
for (var key in obj) {
if (hasProperty(obj, key)) {
res[key] = clone(obj[key]);
}
}
return res;
}
function repush(array, item) {
for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
return array.push(array.splice(i, 1)[0]);
}
}
function cacher(f) {
function newf() {
var arg = Array.prototype.slice.call(arguments, 0),
args = arg.join('\u2400'),
cache = newf.cache = newf.cache || {},
count = newf.count = newf.count || [];
if (hasProperty(cache, args)) {
repush(count, args);
return cache[args];
}
count.length >= 1e3 && delete cache[count.shift()];
count.push(args);
cache[args] = f.apply(0, arg);
return cache[args];
}
return newf;
}
function parsePathString(pathString) {
if (!pathString) {
return null;
}
var pth = paths(pathString);
if (pth.arr) {
return clone(pth.arr);
}
var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 },
data = [];
if (isArray(pathString) && isArray(pathString[0])) { // rough assumption
data = clone(pathString);
}
if (!data.length) {
String(pathString).replace(pathCommand, function(a, b, c) {
var params = [],
name = b.toLowerCase();
c.replace(pathValues, function(a, b) {
b && params.push(+b);
});
if (name == 'm' && params.length > 2) {
data.push([b].concat(params.splice(0, 2)));
name = 'l';
b = b == 'm' ? 'l' : 'L';
}
while (params.length >= paramCounts[name]) {
data.push([b].concat(params.splice(0, paramCounts[name])));
if (!paramCounts[name]) {
break;
}
}
});
}
data.toString = paths.toString;
pth.arr = clone(data);
return data;
}
function paths(ps) {
var p = paths.ps = paths.ps || {};
if (p[ps]) {
p[ps].sleep = 100;
} else {
p[ps] = {
sleep: 100
};
}
setTimeout(function() {
for (var key in p) {
if (hasProperty(p, key) && key != ps) {
p[key].sleep--;
!p[key].sleep && delete p[key];
}
}
});
return p[ps];
}
function rectBBox(x, y, width, height) {
if (arguments.length === 1) {
y = x.y;
width = x.width;
height = x.height;
x = x.x;
}
return {
x: x,
y: y,
width: width,
height: height,
x2: x + width,
y2: y + height
};
}
function pathToString() {
return this.join(',').replace(p2s, '$1');
}
function pathClone(pathArray) {
var res = clone(pathArray);
res.toString = pathToString;
return res;
}
function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
var t1 = 1 - t,
t13 = pow(t1, 3),
t12 = pow(t1, 2),
t2 = t * t,
t3 = t2 * t,
x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y;
return {
x: fixError(x),
y: fixError(y)
};
}
function bezierBBox(points) {
var bbox = curveBBox.apply(null, points);
return rectBBox(
bbox.x0,
bbox.y0,
bbox.x1 - bbox.x0,
bbox.y1 - bbox.y0
);
}
function isPointInsideBBox$2(bbox, x, y) {
return x >= bbox.x &&
x <= bbox.x + bbox.width &&
y >= bbox.y &&
y <= bbox.y + bbox.height;
}
function isBBoxIntersect(bbox1, bbox2) {
bbox1 = rectBBox(bbox1);
bbox2 = rectBBox(bbox2);
return isPointInsideBBox$2(bbox2, bbox1.x, bbox1.y)
|| isPointInsideBBox$2(bbox2, bbox1.x2, bbox1.y)
|| isPointInsideBBox$2(bbox2, bbox1.x, bbox1.y2)
|| isPointInsideBBox$2(bbox2, bbox1.x2, bbox1.y2)
|| isPointInsideBBox$2(bbox1, bbox2.x, bbox2.y)
|| isPointInsideBBox$2(bbox1, bbox2.x2, bbox2.y)
|| isPointInsideBBox$2(bbox1, bbox2.x, bbox2.y2)
|| isPointInsideBBox$2(bbox1, bbox2.x2, bbox2.y2)
|| (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
|| bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
&& (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
|| bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
}
function base3(t, p1, p2, p3, p4) {
var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
return t * t2 - 3 * p1 + 3 * p2;
}
function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
if (z == null) {
z = 1;
}
z = z > 1 ? 1 : z < 0 ? 0 : z;
var z2 = z / 2,
n = 12,
Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
sum = 0;
for (var i = 0; i < n; i++) {
var ct = z2 * Tvalues[i] + z2,
xbase = base3(ct, x1, x2, x3, x4),
ybase = base3(ct, y1, y2, y3, y4),
comb = xbase * xbase + ybase * ybase;
sum += Cvalues[i] * math.sqrt(comb);
}
return z2 * sum;
}
function intersectLines(x1, y1, x2, y2, x3, y3, x4, y4) {
if (
mmax(x1, x2) < mmin(x3, x4) ||
mmin(x1, x2) > mmax(x3, x4) ||
mmax(y1, y2) < mmin(y3, y4) ||
mmin(y1, y2) > mmax(y3, y4)
) {
return;
}
var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (!denominator) {
return;
}
var px = fixError(nx / denominator),
py = fixError(ny / denominator),
px2 = +px.toFixed(2),
py2 = +py.toFixed(2);
if (
px2 < +mmin(x1, x2).toFixed(2) ||
px2 > +mmax(x1, x2).toFixed(2) ||
px2 < +mmin(x3, x4).toFixed(2) ||
px2 > +mmax(x3, x4).toFixed(2) ||
py2 < +mmin(y1, y2).toFixed(2) ||
py2 > +mmax(y1, y2).toFixed(2) ||
py2 < +mmin(y3, y4).toFixed(2) ||
py2 > +mmax(y3, y4).toFixed(2)
) {
return;
}
return { x: px, y: py };
}
function fixError(number) {
return Math.round(number * 100000000000) / 100000000000;
}
function findBezierIntersections(bez1, bez2, justCount) {
var bbox1 = bezierBBox(bez1),
bbox2 = bezierBBox(bez2);
if (!isBBoxIntersect(bbox1, bbox2)) {
return justCount ? 0 : [];
}
// As an optimization, lines will have only 1 segment
var l1 = bezlen.apply(0, bez1),
l2 = bezlen.apply(0, bez2),
n1 = isLine(bez1) ? 1 : ~~(l1 / 5) || 1,
n2 = isLine(bez2) ? 1 : ~~(l2 / 5) || 1,
dots1 = [],
dots2 = [],
xy = {},
res = justCount ? 0 : [];
for (var i = 0; i < n1 + 1; i++) {
var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
dots1.push({ x: p.x, y: p.y, t: i / n1 });
}
for (i = 0; i < n2 + 1; i++) {
p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
dots2.push({ x: p.x, y: p.y, t: i / n2 });
}
for (i = 0; i < n1; i++) {
for (var j = 0; j < n2; j++) {
var di = dots1[i],
di1 = dots1[i + 1],
dj = dots2[j],
dj1 = dots2[j + 1],
ci = abs$7(di1.x - di.x) < .01 ? 'y' : 'x',
cj = abs$7(dj1.x - dj.x) < .01 ? 'y' : 'x',
is = intersectLines(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y),
key;
if (is) {
key = is.x.toFixed(9) + '#' + is.y.toFixed(9);
if (xy[key]) {
continue;
}
xy[key] = true;
var t1 = di.t + abs$7((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
t2 = dj.t + abs$7((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
if (justCount) {
res++;
} else {
res.push({
x: is.x,
y: is.y,
t1: t1,
t2: t2
});
}
}
}
}
}
return res;
}
/**
* Find or counts the intersections between two SVG paths.
*
* Returns a number in counting mode and a list of intersections otherwise.
*
* A single intersection entry contains the intersection coordinates (x, y)
* as well as additional information regarding the intersecting segments
* on each path (segment1, segment2) and the relative location of the
* intersection on these segments (t1, t2).
*
* The path may be an SVG path string or a list of path components
* such as `[ [ 'M', 0, 10 ], [ 'L', 20, 0 ] ]`.
*
* @example
*
* var intersections = findPathIntersections(
* 'M0,0L100,100',
* [ [ 'M', 0, 100 ], [ 'L', 100, 0 ] ]
* );
*
* // intersections = [
* // { x: 50, y: 50, segment1: 1, segment2: 1, t1: 0.5, t2: 0.5 }
* // ]
*
* @param {String|Array<PathDef>} path1
* @param {String|Array<PathDef>} path2
* @param {Boolean} [justCount=false]
*
* @return {Array<Intersection>|Number}
*/
function findPathIntersections(path1, path2, justCount) {
path1 = pathToCurve(path1);
path2 = pathToCurve(path2);
var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
res = justCount ? 0 : [];
for (var i = 0, ii = path1.length; i < ii; i++) {
var pi = path1[i];
if (pi[0] == 'M') {
x1 = x1m = pi[1];
y1 = y1m = pi[2];
} else {
if (pi[0] == 'C') {
bez1 = [x1, y1].concat(pi.slice(1));
x1 = bez1[6];
y1 = bez1[7];
} else {
bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
x1 = x1m;
y1 = y1m;
}
for (var j = 0, jj = path2.length; j < jj; j++) {
var pj = path2[j];
if (pj[0] == 'M') {
x2 = x2m = pj[1];
y2 = y2m = pj[2];
} else {
if (pj[0] == 'C') {
bez2 = [x2, y2].concat(pj.slice(1));
x2 = bez2[6];
y2 = bez2[7];
} else {
bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
x2 = x2m;
y2 = y2m;
}
var intr = findBezierIntersections(bez1, bez2, justCount);
if (justCount) {
res += intr;
} else {
for (var k = 0, kk = intr.length; k < kk; k++) {
intr[k].segment1 = i;
intr[k].segment2 = j;
intr[k].bez1 = bez1;
intr[k].bez2 = bez2;
}
res = res.concat(intr);
}
}
}
}
}
return res;
}
function pathToAbsolute(pathArray) {
var pth = paths(pathArray);
if (pth.abs) {
return pathClone(pth.abs);
}
if (!isArray(pathArray) || !isArray(pathArray && pathArray[0])) { // rough assumption
pathArray = parsePathString(pathArray);
}
if (!pathArray || !pathArray.length) {
return [['M', 0, 0]];
}
var res = [],
x = 0,
y = 0,
mx = 0,
my = 0,
start = 0,
pa0;
if (pathArray[0][0] == 'M') {
x = +pathArray[0][1];
y = +pathArray[0][2];
mx = x;
my = y;
start++;
res[0] = ['M', x, y];
}
for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
res.push(r = []);
pa = pathArray[i];
pa0 = pa[0];
if (pa0 != pa0.toUpperCase()) {
r[0] = pa0.toUpperCase();
switch (r[0]) {
case 'A':
r[1] = pa[1];
r[2] = pa[2];
r[3] = pa[3];
r[4] = pa[4];
r[5] = pa[5];
r[6] = +pa[6] + x;
r[7] = +pa[7] + y;
break;
case 'V':
r[1] = +pa[1] + y;
break;
case 'H':
r[1] = +pa[1] + x;
break;
case 'M':
mx = +pa[1] + x;
my = +pa[2] + y;
default:
for (var j = 1, jj = pa.length; j < jj; j++) {
r[j] = +pa[j] + ((j % 2) ? x : y);
}
}
} else {
for (var k = 0, kk = pa.length; k < kk; k++) {
r[k] = pa[k];
}
}
pa0 = pa0.toUpperCase();
switch (r[0]) {
case 'Z':
x = +mx;
y = +my;
break;
case 'H':
x = r[1];
break;
case 'V':
y = r[1];
break;
case 'M':
mx = r[r.length - 2];
my = r[r.length - 1];
default:
x = r[r.length - 2];
y = r[r.length - 1];
}
}
res.toString = pathToString;
pth.abs = pathClone(res);
return res;
}
function isLine(bez) {
return (
bez[0] === bez[2] &&
bez[1] === bez[3] &&
bez[4] === bez[6] &&
bez[5] === bez[7]
);
}
function lineToCurve(x1, y1, x2, y2) {
return [
x1, y1, x2,
y2, x2, y2
];
}
function qubicToCurve(x1, y1, ax, ay, x2, y2) {
var _13 = 1 / 3,
_23 = 2 / 3;
return [
_13 * x1 + _23 * ax,
_13 * y1 + _23 * ay,
_13 * x2 + _23 * ax,
_13 * y2 + _23 * ay,
x2,
y2
];
}
function arcToCurve(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
// for more information of where this math came from visit:
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
var _120 = PI * 120 / 180,
rad = PI / 180 * (+angle || 0),
res = [],
xy,
rotate = cacher(function(x, y, rad) {
var X = x * math.cos(rad) - y * math.sin(rad),
Y = x * math.sin(rad) + y * math.cos(rad);
return { x: X, y: Y };
});
if (!recursive) {
xy = rotate(x1, y1, -rad);
x1 = xy.x;
y1 = xy.y;
xy = rotate(x2, y2, -rad);
x2 = xy.x;
y2 = xy.y;
var x = (x1 - x2) / 2,
y = (y1 - y2) / 2;
var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
if (h > 1) {
h = math.sqrt(h);
rx = h * rx;
ry = h * ry;
}
var rx2 = rx * rx,
ry2 = ry * ry,
k = (large_arc_flag == sweep_flag ? -1 : 1) *
math.sqrt(abs$7((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
cx = k * rx * y / ry + (x1 + x2) / 2,
cy = k * -ry * x / rx + (y1 + y2) / 2,
f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
f2 = math.asin(((y2 - cy) / ry).toFixed(9));
f1 = x1 < cx ? PI - f1 : f1;
f2 = x2 < cx ? PI - f2 : f2;
f1 < 0 && (f1 = PI * 2 + f1);
f2 < 0 && (f2 = PI * 2 + f2);
if (sweep_flag && f1 > f2) {
f1 = f1 - PI * 2;
}
if (!sweep_flag && f2 > f1) {
f2 = f2 - PI * 2;
}
} else {
f1 = recursive[0];
f2 = recursive[1];
cx = recursive[2];
cy = recursive[3];
}
var df = f2 - f1;
if (abs$7(df) > _120) {
var f2old = f2,
x2old = x2,
y2old = y2;
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
x2 = cx + rx * math.cos(f2);
y2 = cy + ry * math.sin(f2);
res = arcToCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
}
df = f2 - f1;
var c1 = math.cos(f1),
s1 = math.sin(f1),
c2 = math.cos(f2),
s2 = math.sin(f2),
t = math.tan(df / 4),
hx = 4 / 3 * rx * t,
hy = 4 / 3 * ry * t,
m1 = [x1, y1],
m2 = [x1 + hx * s1, y1 - hy * c1],
m3 = [x2 + hx * s2, y2 - hy * c2],
m4 = [x2, y2];
m2[0] = 2 * m1[0] - m2[0];
m2[1] = 2 * m1[1] - m2[1];
if (recursive) {
return [m2, m3, m4].concat(res);
} else {
res = [m2, m3, m4].concat(res).join().split(',');
var newres = [];
for (var i = 0, ii = res.length; i < ii; i++) {
newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
}
return newres;
}
}
// Returns bounding box of cubic bezier curve.
// Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
// Original version: NISHIO Hirokazu
// Modifications: https://github.com/timo22345
function curveBBox(x0, y0, x1, y1, x2, y2, x3, y3) {
var tvalues = [],
bounds = [[], []],
a, b, c, t, t1, t2, b2ac, sqrtb2ac;
for (var i = 0; i < 2; ++i) {
if (i == 0) {
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
} else {
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (abs$7(a) < 1e-12) {
if (abs$7(b) < 1e-12) {
continue;
}
t = -c / b;
if (0 < t && t < 1) {
tvalues.push(t);
}
continue;
}
b2ac = b * b - 4 * c * a;
sqrtb2ac = math.sqrt(b2ac);
if (b2ac < 0) {
continue;
}
t1 = (-b + sqrtb2ac) / (2 * a);
if (0 < t1 && t1 < 1) {
tvalues.push(t1);
}
t2 = (-b - sqrtb2ac) / (2 * a);
if (0 < t2 && t2 < 1) {
tvalues.push(t2);
}
}
var j = tvalues.length,
jlen = j,
mt;
while (j--) {
t = tvalues[j];
mt = 1 - t;
bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
}
bounds[0][jlen] = x0;
bounds[1][jlen] = y0;
bounds[0][jlen + 1] = x3;
bounds[1][jlen + 1] = y3;
bounds[0].length = bounds[1].length = jlen + 2;
return {
x0: mmin.apply(0, bounds[0]),
y0: mmin.apply(0, bounds[1]),
x1: mmax.apply(0, bounds[0]),
y1: mmax.apply(0, bounds[1])
};
}
function pathToCurve(path) {
var pth = paths(path);
// return cached curve, if existing
if (pth.curve) {
return pathClone(pth.curve);
}
var curvedPath = pathToAbsolute(path),
attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null },
processPath = function(path, d, pathCommand) {
var nx, ny;
if (!path) {
return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
}
!(path[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null);
switch (path[0]) {
case 'M':
d.X = path[1];
d.Y = path[2];
break;
case 'A':
path = ['C'].concat(arcToCurve.apply(0, [d.x, d.y].concat(path.slice(1))));
break;
case 'S':
if (pathCommand == 'C' || pathCommand == 'S') {
// In 'S' case we have to take into account, if the previous command is C/S.
nx = d.x * 2 - d.bx;
// And reflect the previous
ny = d.y * 2 - d.by;
// command's control point relative to the current point.
}
else {
// or some else or nothing
nx = d.x;
ny = d.y;
}
path = ['C', nx, ny].concat(path.slice(1));
break;
case 'T':
if (pathCommand == 'Q' || pathCommand == 'T') {
// In 'T' case we have to take into account, if the previous command is Q/T.
d.qx = d.x * 2 - d.qx;
// And make a reflection similar
d.qy = d.y * 2 - d.qy;
// to case 'S'.
}
else {
// or something else or nothing
d.qx = d.x;
d.qy = d.y;
}
path = ['C'].concat(qubicToCurve(d.x, d.y, d.qx, d.qy, path[1], path[2]));
break;
case 'Q':
d.qx = path[1];
d.qy = path[2];
path = ['C'].concat(qubicToCurve(d.x, d.y, path[1], path[2], path[3], path[4]));
break;
case 'L':
path = ['C'].concat(lineToCurve(d.x, d.y, path[1], path[2]));
break;
case 'H':
path = ['C'].concat(lineToCurve(d.x, d.y, path[1], d.y));
break;
case 'V':
path = ['C'].concat(lineToCurve(d.x, d.y, d.x, path[1]));
break;
case 'Z':
path = ['C'].concat(lineToCurve(d.x, d.y, d.X, d.Y));
break;
}
return path;
},
fixArc = function(pp, i) {
if (pp[i].length > 7) {
pp[i].shift();
var pi = pp[i];
while (pi.length) {
pathCommands[i] = 'A'; // if created multiple C:s, their original seg is saved
pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
}
pp.splice(i, 1);
ii = curvedPath.length;
}
},
pathCommands = [], // path commands of original path p
pfirst = '', // temporary holder for original path command
pathCommand = ''; // holder for previous path command of original path
for (var i = 0, ii = curvedPath.length; i < ii; i++) {
curvedPath[i] && (pfirst = curvedPath[i][0]); // save current path command
if (pfirst != 'C') // C is not saved yet, because it may be result of conversion
{
pathCommands[i] = pfirst; // Save current path command
i && (pathCommand = pathCommands[i - 1]); // Get previous path command pathCommand
}
curvedPath[i] = processPath(curvedPath[i], attrs, pathCommand); // Previous path command is inputted to processPath
if (pathCommands[i] != 'A' && pfirst == 'C') pathCommands[i] = 'C'; // A is the only command
// which may produce multiple C:s
// so we have to make sure that C is also C in original path
fixArc(curvedPath, i); // fixArc adds also the right amount of A:s to pathCommands
var seg = curvedPath[i],
seglen = seg.length;
attrs.x = seg[seglen - 2];
attrs.y = seg[seglen - 1];
attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
}
// cache curve
pth.curve = pathClone(curvedPath);
return curvedPath;
}
var intersect = findPathIntersections;
function roundBounds(bounds) {
return {
x: Math.round(bounds.x),
y: Math.round(bounds.y),
width: Math.round(bounds.width),
height: Math.round(bounds.height)
};
}
function roundPoint(point) {
return {
x: Math.round(point.x),
y: Math.round(point.y)
};
}
/**
* Convert the given bounds to a { top, left, bottom, right } descriptor.
*
* @param {Bounds|Point} bounds
*
* @return {Object}
*/
function asTRBL(bounds) {
return {
top: bounds.y,
right: bounds.x + (bounds.width || 0),
bottom: bounds.y + (bounds.height || 0),
left: bounds.x
};
}
/**
* Convert a { top, left, bottom, right } to an objects bounds.
*
* @param {Object} trbl
*
* @return {Bounds}
*/
function asBounds(trbl) {
return {
x: trbl.left,
y: trbl.top,
width: trbl.right - trbl.left,
height: trbl.bottom - trbl.top
};
}
/**
* Get the mid of the given bounds or point.
*
* @param {Bounds|Point} bounds
*
* @return {Point}
*/
function getBoundsMid(bounds) {
return roundPoint({
x: bounds.x + (bounds.width || 0) / 2,
y: bounds.y + (bounds.height || 0) / 2
});
}
/**
* Get the mid of the given Connection.
*
* @param {djs.Base.Connection} connection
*
* @return {Point}
*/
function getConnectionMid(connection) {
var waypoints = connection.waypoints;
// calculate total length and length of each segment
var parts = waypoints.reduce(function(parts, point, index) {
var lastPoint = waypoints[index - 1];
if (lastPoint) {
var lastPart = parts[parts.length - 1];
var startLength = lastPart && lastPart.endLength || 0;
var length = distance(lastPoint, point);
parts.push({
start: lastPoint,
end: point,
startLength: startLength,
endLength: startLength + length,
length: length
});
}
return parts;
}, []);
var totalLength = parts.reduce(function(length, part) {
return length + part.length;
}, 0);
// find which segement contains middle point
var midLength = totalLength / 2;
var i = 0;
var midSegment = parts[i];
while (midSegment.endLength < midLength) {
midSegment = parts[++i];
}
// calculate relative position on mid segment
var segmentProgress = (midLength - midSegment.startLength) / midSegment.length;
var midPoint = {
x: midSegment.start.x + (midSegment.end.x - midSegment.start.x) * segmentProgress,
y: midSegment.start.y + (midSegment.end.y - midSegment.start.y) * segmentProgress
};
return midPoint;
}
/**
* Get the mid of the given Element.
*
* @param {djs.Base.Connection} connection
*
* @return {Point}
*/
function getMid(element) {
if (isConnection$f(element)) {
return getConnectionMid(element);
}
return getBoundsMid(element);
}
// orientation utils //////////////////////
/**
* Get orientation of the given rectangle with respect to
* the reference rectangle.
*
* A padding (positive or negative) may be passed to influence
* horizontal / vertical orientation and intersection.
*
* @param {Bounds} rect
* @param {Bounds} reference
* @param {Point|number} padding
*
* @return {string} the orientation; one of top, top-left, left, ..., bottom, right or intersect.
*/
function getOrientation(rect, reference, padding) {
padding = padding || 0;
// make sure we can use an object, too
// for individual { x, y } padding
if (!isObject(padding)) {
padding = { x: padding, y: padding };
}
var rectOrientation = asTRBL(rect),
referenceOrientation = asTRBL(reference);
var top = rectOrientation.bottom + padding.y <= referenceOrientation.top,
right = rectOrientation.left - padding.x >= referenceOrientation.right,
bottom = rectOrientation.top - padding.y >= referenceOrientation.bottom,
left = rectOrientation.right + padding.x <= referenceOrientation.left;
var vertical = top ? 'top' : (bottom ? 'bottom' : null),
horizontal = left ? 'left' : (right ? 'right' : null);
if (horizontal && vertical) {
return vertical + '-' + horizontal;
} else {
return horizontal || vertical || 'intersect';
}
}
// intersection utils //////////////////////
/**
* Get intersection between an element and a line path.
*
* @param {PathDef} elementPath
* @param {PathDef} linePath
* @param {boolean} cropStart crop from start or end
*
* @return {Point}
*/
function getElementLineIntersection(elementPath, linePath, cropStart) {
var intersections = getIntersections(elementPath, linePath);
// recognize intersections
// only one -> choose
// two close together -> choose first
// two or more distinct -> pull out appropriate one
// none -> ok (fallback to point itself)
if (intersections.length === 1) {
return roundPoint(intersections[0]);
} else if (intersections.length === 2 && pointDistance(intersections[0], intersections[1]) < 1) {
return roundPoint(intersections[0]);
} else if (intersections.length > 1) {
// sort by intersections based on connection segment +
// distance from start
intersections = sortBy(intersections, function(i) {
var distance = Math.floor(i.t2 * 100) || 1;
distance = 100 - distance;
distance = (distance < 10 ? '0' : '') + distance;
// create a sort string that makes sure we sort
// line segment ASC + line segment position DESC (for cropStart)
// line segment ASC + line segment position ASC (for cropEnd)
return i.segment2 + '#' + distance;
});
return roundPoint(intersections[cropStart ? 0 : intersections.length - 1]);
}
return null;
}
function getIntersections(a, b) {
return intersect(a, b);
}
function filterRedundantWaypoints(waypoints) {
// alter copy of waypoints, not original
waypoints = waypoints.slice();
var idx = 0,
point,
previousPoint,
nextPoint;
while (waypoints[idx]) {
point = waypoints[idx];
previousPoint = waypoints[idx - 1];
nextPoint = waypoints[idx + 1];
if (pointDistance(point, nextPoint) === 0 ||
pointsOnLine(previousPoint, nextPoint, point)) {
// remove point, if overlapping with {nextPoint}
// or on line with {previousPoint} -> {point} -> {nextPoint}
waypoints.splice(idx, 1);
} else {
idx++;
}
}
return waypoints;
}
// helpers //////////////////////
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function isConnection$f(element) {
return !!element.waypoints;
}
function round$b(number, resolution) {
return Math.round(number * resolution) / resolution;
}
function ensurePx(number) {
return isNumber(number) ? number + 'px' : number;
}
function findRoot(element) {
while (element.parent) {
element = element.parent;
}
return element;
}
/**
* Creates a HTML container element for a SVG element with
* the given configuration
*
* @param {Object} options
* @return {HTMLElement} the container element
*/
function createContainer(options) {
options = assign({}, { width: '100%', height: '100%' }, options);
var container = options.container || document.body;
// create a <div> around the svg element with the respective size
// this way we can always get the correct container size
// (this is impossible for <svg> elements at the moment)
var parent = document.createElement('div');
parent.setAttribute('class', 'djs-container');
assign$1(parent, {
position: 'relative',
overflow: 'hidden',
width: ensurePx(options.width),
height: ensurePx(options.height)
});
container.appendChild(parent);
return parent;
}
function createGroup(parent, cls, childIndex) {
var group = create$1('g');
classes(group).add(cls);
var index = childIndex !== undefined ? childIndex : parent.childNodes.length - 1;
// must ensure second argument is node or _null_
// cf. https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
parent.insertBefore(group, parent.childNodes[index] || null);
return group;
}
var BASE_LAYER = 'base';
// render plane contents behind utility layers
var PLANE_LAYER_INDEX = 0;
var UTILITY_LAYER_INDEX = 1;
var REQUIRED_MODEL_ATTRS = {
shape: [ 'x', 'y', 'width', 'height' ],
connection: [ 'waypoints' ]
};
/**
* The main drawing canvas.
*
* @class
* @constructor
*
* @emits Canvas#canvas.init
*
* @param {Object} config
* @param {EventBus} eventBus
* @param {GraphicsFactory} graphicsFactory
* @param {ElementRegistry} elementRegistry
*/
function Canvas(config, eventBus, graphicsFactory, elementRegistry) {
this._eventBus = eventBus;
this._elementRegistry = elementRegistry;
this._graphicsFactory = graphicsFactory;
this._rootsIdx = 0;
this._layers = {};
this._planes = [];
this._rootElement = null;
this._init(config || {});
}
Canvas.$inject = [
'config.canvas',
'eventBus',
'graphicsFactory',
'elementRegistry'
];
/**
* Creates a <svg> element that is wrapped into a <div>.
* This way we are always able to correctly figure out the size of the svg element
* by querying the parent node.
* (It is not possible to get the size of a svg element cross browser @ 2014-04-01)
* <div class="djs-container" style="width: {desired-width}, height: {desired-height}">
* <svg width="100%" height="100%">
* ...
* </svg>
* </div>
*/
Canvas.prototype._init = function(config) {
var eventBus = this._eventBus;
// html container
var container = this._container = createContainer(config);
var svg = this._svg = create$1('svg');
attr(svg, { width: '100%', height: '100%' });
append(container, svg);
var viewport = this._viewport = createGroup(svg, 'viewport');
// debounce canvas.viewbox.changed events
// for smoother diagram interaction
if (config.deferUpdate !== false) {
this._viewboxChanged = debounce(bind(this._viewboxChanged, this), 300);
}
eventBus.on('diagram.init', function() {
/**
* An event indicating that the canvas is ready to be drawn on.
*
* @memberOf Canvas
*
* @event canvas.init
*
* @type {Object}
* @property {SVGElement} svg the created svg element
* @property {SVGElement} viewport the direct parent of diagram elements and shapes
*/
eventBus.fire('canvas.init', {
svg: svg,
viewport: viewport
});
}, this);
// reset viewbox on shape changes to
// recompute the viewbox
eventBus.on([
'shape.added',
'connection.added',
'shape.removed',
'connection.removed',
'elements.changed',
'root.set'
], function() {
delete this._cachedViewbox;
}, this);
eventBus.on('diagram.destroy', 500, this._destroy, this);
eventBus.on('diagram.clear', 500, this._clear, this);
};
Canvas.prototype._destroy = function(emit) {
this._eventBus.fire('canvas.destroy', {
svg: this._svg,
viewport: this._viewport
});
var parent = this._container.parentNode;
if (parent) {
parent.removeChild(this._container);
}
delete this._svg;
delete this._container;
delete this._layers;
delete this._planes;
delete this._rootElement;
delete this._viewport;
};
Canvas.prototype._clear = function() {
var self = this;
var allElements = this._elementRegistry.getAll();
// remove all elements
allElements.forEach(function(element) {
var type = getType(element);
if (type === 'root') {
self.removeRootElement(element);
} else {
self._removeElement(element, type);
}
});
// remove all planes
this._planes = [];
this._rootElement = null;
// force recomputation of view box
delete this._cachedViewbox;
};
/**
* Returns the default layer on which
* all elements are drawn.
*
* @returns {SVGElement}
*/
Canvas.prototype.getDefaultLayer = function() {
return this.getLayer(BASE_LAYER, PLANE_LAYER_INDEX);
};
/**
* Returns a layer that is used to draw elements
* or annotations on it.
*
* Non-existing layers retrieved through this method
* will be created. During creation, the optional index
* may be used to create layers below or above existing layers.
* A layer with a certain index is always created above all
* existing layers with the same index.
*
* @param {string} name
* @param {number} index
*
* @returns {SVGElement}
*/
Canvas.prototype.getLayer = function(name, index) {
if (!name) {
throw new Error('must specify a name');
}
var layer = this._layers[name];
if (!layer) {
layer = this._layers[name] = this._createLayer(name, index);
}
// throw an error if layer creation / retrival is
// requested on different index
if (typeof index !== 'undefined' && layer.index !== index) {
throw new Error('layer <' + name + '> already created at index <' + index + '>');
}
return layer.group;
};
/**
* For a given index, return the number of layers that have a higher index and
* are visible.
*
* This is used to determine the node a layer should be inserted at.
*
* @param {Number} index
* @returns {Number}
*/
Canvas.prototype._getChildIndex = function(index) {
return reduce(this._layers, function(childIndex, layer) {
if (layer.visible && index >= layer.index) {
childIndex++;
}
return childIndex;
}, 0);
};
/**
* Creates a given layer and returns it.
*
* @param {string} name
* @param {number} [index=0]
*
* @return {Object} layer descriptor with { index, group: SVGGroup }
*/
Canvas.prototype._createLayer = function(name, index) {
if (typeof index === 'undefined') {
index = UTILITY_LAYER_INDEX;
}
var childIndex = this._getChildIndex(index);
return {
group: createGroup(this._viewport, 'layer-' + name, childIndex),
index: index,
visible: true
};
};
/**
* Shows a given layer.
*
* @param {String} layer
* @returns {SVGElement}
*/
Canvas.prototype.showLayer = function(name) {
if (!name) {
throw new Error('must specify a name');
}
var layer = this._layers[name];
if (!layer) {
throw new Error('layer <' + name + '> does not exist');
}
var viewport = this._viewport;
var group = layer.group;
var index = layer.index;
if (layer.visible) {
return group;
}
var childIndex = this._getChildIndex(index);
viewport.insertBefore(group, viewport.childNodes[childIndex] || null);
layer.visible = true;
return group;
};
/**
* Hides a given layer.
*
* @param {String} layer
* @returns {SVGElement}
*/
Canvas.prototype.hideLayer = function(name) {
if (!name) {
throw new Error('must specify a name');
}
var layer = this._layers[name];
if (!layer) {
throw new Error('layer <' + name + '> does not exist');
}
var group = layer.group;
if (!layer.visible) {
return group;
}
remove$1(group);
layer.visible = false;
return group;
};
Canvas.prototype._removeLayer = function(name) {
var layer = this._layers[name];
if (layer) {
delete this._layers[name];
remove$1(layer.group);
}
};
/**
* Returns the currently active layer. Can be null.
*
* @returns {SVGElement|null}
*/
Canvas.prototype.getActiveLayer = function() {
var plane = this._findPlaneForRoot(this.getRootElement());
if (!plane) {
return null;
}
return plane.layer;
};
/**
* Returns the plane which contains the given element.
*
* @param {string|djs.model.Base} element
*
* @return {djs.model.Base} root for element
*/
Canvas.prototype.findRoot = function(element) {
if (typeof element === 'string') {
element = this._elementRegistry.get(element);
}
if (!element) {
return;
}
var plane = this._findPlaneForRoot(
findRoot(element)
) || {};
return plane.rootElement;
};
/**
* Return a list of all root elements on the diagram.
*
* @return {djs.model.Root[]}
*/
Canvas.prototype.getRootElements = function() {
return this._planes.map(function(plane) {
return plane.rootElement;
});
};
Canvas.prototype._findPlaneForRoot = function(rootElement) {
return find(this._planes, function(plane) {
return plane.rootElement === rootElement;
});
};
/**
* Returns the html element that encloses the
* drawing canvas.
*
* @return {DOMNode}
*/
Canvas.prototype.getContainer = function() {
return this._container;
};
// markers //////////////////////
Canvas.prototype._updateMarker = function(element, marker, add) {
var container;
if (!element.id) {
element = this._elementRegistry.get(element);
}
// we need to access all
container = this._elementRegistry._elements[element.id];
if (!container) {
return;
}
forEach$1([ container.gfx, container.secondaryGfx ], function(gfx) {
if (gfx) {
// invoke either addClass or removeClass based on mode
if (add) {
classes(gfx).add(marker);
} else {
classes(gfx).remove(marker);
}
}
});
/**
* An event indicating that a marker has been updated for an element
*
* @event element.marker.update
* @type {Object}
* @property {djs.model.Element} element the shape
* @property {Object} gfx the graphical representation of the shape
* @property {string} marker
* @property {boolean} add true if the marker was added, false if it got removed
*/
this._eventBus.fire('element.marker.update', { element: element, gfx: container.gfx, marker: marker, add: !!add });
};
/**
* Adds a marker to an element (basically a css class).
*
* Fires the element.marker.update event, making it possible to
* integrate extension into the marker life-cycle, too.
*
* @example
* canvas.addMarker('foo', 'some-marker');
*
* var fooGfx = canvas.getGraphics('foo');
*
* fooGfx; // <g class="... some-marker"> ... </g>
*
* @param {string|djs.model.Base} element
* @param {string} marker
*/
Canvas.prototype.addMarker = function(element, marker) {
this._updateMarker(element, marker, true);
};
/**
* Remove a marker from an element.
*
* Fires the element.marker.update event, making it possible to
* integrate extension into the marker life-cycle, too.
*
* @param {string|djs.model.Base} element
* @param {string} marker
*/
Canvas.prototype.removeMarker = function(element, marker) {
this._updateMarker(element, marker, false);
};
/**
* Check the existence of a marker on element.
*
* @param {string|djs.model.Base} element
* @param {string} marker
*/
Canvas.prototype.hasMarker = function(element, marker) {
if (!element.id) {
element = this._elementRegistry.get(element);
}
var gfx = this.getGraphics(element);
return classes(gfx).has(marker);
};
/**
* Toggles a marker on an element.
*
* Fires the element.marker.update event, making it possible to
* integrate extension into the marker life-cycle, too.
*
* @param {string|djs.model.Base} element
* @param {string} marker
*/
Canvas.prototype.toggleMarker = function(element, marker) {
if (this.hasMarker(element, marker)) {
this.removeMarker(element, marker);
} else {
this.addMarker(element, marker);
}
};
/**
* Returns the current root element.
*
* Supports two different modes for handling root elements:
*
* 1. if no root element has been added before, an implicit root will be added
* and returned. This is used in applications that don't require explicit
* root elements.
*
* 2. when root elements have been added before calling `getRootElement`,
* root elements can be null. This is used for applications that want to manage
* root elements themselves.
*
* @returns {Object|djs.model.Root|null} rootElement.
*/
Canvas.prototype.getRootElement = function() {
var rootElement = this._rootElement;
// can return null if root elements are present but none was set yet
if (rootElement || this._planes.length) {
return rootElement;
}
return this.setRootElement(this.addRootElement(null));
};
/**
* Adds a given root element and returns it.
*
* @param {Object|djs.model.Root} rootElement
*
* @return {Object|djs.model.Root} rootElement
*/
Canvas.prototype.addRootElement = function(rootElement) {
var idx = this._rootsIdx++;
if (!rootElement) {
rootElement = {
id: '__implicitroot_' + idx,
children: [],
isImplicit: true
};
}
var layerName = rootElement.layer = 'root-' + idx;
this._ensureValid('root', rootElement);
var layer = this.getLayer(layerName, PLANE_LAYER_INDEX);
this.hideLayer(layerName);
this._addRoot(rootElement, layer);
this._planes.push({
rootElement: rootElement,
layer: layer
});
return rootElement;
};
/**
* Removes a given rootElement and returns it.
*
* @param {djs.model.Root|String} rootElement
*
* @return {Object|djs.model.Root} rootElement
*/
Canvas.prototype.removeRootElement = function(rootElement) {
if (typeof rootElement === 'string') {
rootElement = this._elementRegistry.get(rootElement);
}
var plane = this._findPlaneForRoot(rootElement);
if (!plane) {
return;
}
// hook up life-cycle events
this._removeRoot(rootElement);
// clean up layer
this._removeLayer(rootElement.layer);
// clean up plane
this._planes = this._planes.filter(function(plane) {
return plane.rootElement !== rootElement;
});
// clean up active root
if (this._rootElement === rootElement) {
this._rootElement = null;
}
return rootElement;
};
// root element handling //////////////////////
/**
* Sets a given element as the new root element for the canvas
* and returns the new root element.
*
* @param {Object|djs.model.Root} rootElement
*
* @return {Object|djs.model.Root} new root element
*/
Canvas.prototype.setRootElement = function(rootElement, override) {
if (isDefined(override)) {
throw new Error('override not supported');
}
if (rootElement === this._rootElement) {
return;
}
var plane;
if (!rootElement) {
throw new Error('rootElement required');
}
plane = this._findPlaneForRoot(rootElement);
// give set add semantics for backwards compatibility
if (!plane) {
rootElement = this.addRootElement(rootElement);
}
this._setRoot(rootElement);
return rootElement;
};
Canvas.prototype._removeRoot = function(element) {
var elementRegistry = this._elementRegistry,
eventBus = this._eventBus;
// simulate element remove event sequence
eventBus.fire('root.remove', { element: element });
eventBus.fire('root.removed', { element: element });
elementRegistry.remove(element);
};
Canvas.prototype._addRoot = function(element, gfx) {
var elementRegistry = this._elementRegistry,
eventBus = this._eventBus;
// resemble element add event sequence
eventBus.fire('root.add', { element: element });
elementRegistry.add(element, gfx);
eventBus.fire('root.added', { element: element, gfx: gfx });
};
Canvas.prototype._setRoot = function(rootElement, layer) {
var currentRoot = this._rootElement;
if (currentRoot) {
// un-associate previous root element <svg>
this._elementRegistry.updateGraphics(currentRoot, null, true);
// hide previous layer
this.hideLayer(currentRoot.layer);
}
if (rootElement) {
if (!layer) {
layer = this._findPlaneForRoot(rootElement).layer;
}
// associate element with <svg>
this._elementRegistry.updateGraphics(rootElement, this._svg, true);
// show root layer
this.showLayer(rootElement.layer);
}
this._rootElement = rootElement;
this._eventBus.fire('root.set', { element: rootElement });
};
// add functionality //////////////////////
Canvas.prototype._ensureValid = function(type, element) {
if (!element.id) {
throw new Error('element must have an id');
}
if (this._elementRegistry.get(element.id)) {
throw new Error('element <' + element.id + '> already exists');
}
var requiredAttrs = REQUIRED_MODEL_ATTRS[type];
var valid = every(requiredAttrs, function(attr) {
return typeof element[attr] !== 'undefined';
});
if (!valid) {
throw new Error(
'must supply { ' + requiredAttrs.join(', ') + ' } with ' + type);
}
};
Canvas.prototype._setParent = function(element, parent, parentIndex) {
add(parent.children, element, parentIndex);
element.parent = parent;
};
/**
* Adds an element to the canvas.
*
* This wires the parent <-> child relationship between the element and
* a explicitly specified parent or an implicit root element.
*
* During add it emits the events
*
* * <{type}.add> (element, parent)
* * <{type}.added> (element, gfx)
*
* Extensions may hook into these events to perform their magic.
*
* @param {string} type
* @param {Object|djs.model.Base} element
* @param {Object|djs.model.Base} [parent]
* @param {number} [parentIndex]
*
* @return {Object|djs.model.Base} the added element
*/
Canvas.prototype._addElement = function(type, element, parent, parentIndex) {
parent = parent || this.getRootElement();
var eventBus = this._eventBus,
graphicsFactory = this._graphicsFactory;
this._ensureValid(type, element);
eventBus.fire(type + '.add', { element: element, parent: parent });
this._setParent(element, parent, parentIndex);
// create graphics
var gfx = graphicsFactory.create(type, element, parentIndex);
this._elementRegistry.add(element, gfx);
// update its visual
graphicsFactory.update(type, element, gfx);
eventBus.fire(type + '.added', { element: element, gfx: gfx });
return element;
};
/**
* Adds a shape to the canvas
*
* @param {Object|djs.model.Shape} shape to add to the diagram
* @param {djs.model.Base} [parent]
* @param {number} [parentIndex]
*
* @return {djs.model.Shape} the added shape
*/
Canvas.prototype.addShape = function(shape, parent, parentIndex) {
return this._addElement('shape', shape, parent, parentIndex);
};
/**
* Adds a connection to the canvas
*
* @param {Object|djs.model.Connection} connection to add to the diagram
* @param {djs.model.Base} [parent]
* @param {number} [parentIndex]
*
* @return {djs.model.Connection} the added connection
*/
Canvas.prototype.addConnection = function(connection, parent, parentIndex) {
return this._addElement('connection', connection, parent, parentIndex);
};
/**
* Internal remove element
*/
Canvas.prototype._removeElement = function(element, type) {
var elementRegistry = this._elementRegistry,
graphicsFactory = this._graphicsFactory,
eventBus = this._eventBus;
element = elementRegistry.get(element.id || element);
if (!element) {
// element was removed already
return;
}
eventBus.fire(type + '.remove', { element: element });
graphicsFactory.remove(element);
// unset parent <-> child relationship
remove(element.parent && element.parent.children, element);
element.parent = null;
eventBus.fire(type + '.removed', { element: element });
elementRegistry.remove(element);
return element;
};
/**
* Removes a shape from the canvas
*
* @param {string|djs.model.Shape} shape or shape id to be removed
*
* @return {djs.model.Shape} the removed shape
*/
Canvas.prototype.removeShape = function(shape) {
/**
* An event indicating that a shape is about to be removed from the canvas.
*
* @memberOf Canvas
*
* @event shape.remove
* @type {Object}
* @property {djs.model.Shape} element the shape descriptor
* @property {Object} gfx the graphical representation of the shape
*/
/**
* An event indicating that a shape has been removed from the canvas.
*
* @memberOf Canvas
*
* @event shape.removed
* @type {Object}
* @property {djs.model.Shape} element the shape descriptor
* @property {Object} gfx the graphical representation of the shape
*/
return this._removeElement(shape, 'shape');
};
/**
* Removes a connection from the canvas
*
* @param {string|djs.model.Connection} connection or connection id to be removed
*
* @return {djs.model.Connection} the removed connection
*/
Canvas.prototype.removeConnection = function(connection) {
/**
* An event indicating that a connection is about to be removed from the canvas.
*
* @memberOf Canvas
*
* @event connection.remove
* @type {Object}
* @property {djs.model.Connection} element the connection descriptor
* @property {Object} gfx the graphical representation of the connection
*/
/**
* An event indicating that a connection has been removed from the canvas.
*
* @memberOf Canvas
*
* @event connection.removed
* @type {Object}
* @property {djs.model.Connection} element the connection descriptor
* @property {Object} gfx the graphical representation of the connection
*/
return this._removeElement(connection, 'connection');
};
/**
* Return the graphical object underlaying a certain diagram element
*
* @param {string|djs.model.Base} element descriptor of the element
* @param {boolean} [secondary=false] whether to return the secondary connected element
*
* @return {SVGElement}
*/
Canvas.prototype.getGraphics = function(element, secondary) {
return this._elementRegistry.getGraphics(element, secondary);
};
/**
* Perform a viewbox update via a given change function.
*
* @param {Function} changeFn
*/
Canvas.prototype._changeViewbox = function(changeFn) {
// notify others of the upcoming viewbox change
this._eventBus.fire('canvas.viewbox.changing');
// perform actual change
changeFn.apply(this);
// reset the cached viewbox so that
// a new get operation on viewbox or zoom
// triggers a viewbox re-computation
this._cachedViewbox = null;
// notify others of the change; this step
// may or may not be debounced
this._viewboxChanged();
};
Canvas.prototype._viewboxChanged = function() {
this._eventBus.fire('canvas.viewbox.changed', { viewbox: this.viewbox() });
};
/**
* Gets or sets the view box of the canvas, i.e. the
* area that is currently displayed.
*
* The getter may return a cached viewbox (if it is currently
* changing). To force a recomputation, pass `false` as the first argument.
*
* @example
*
* canvas.viewbox({ x: 100, y: 100, width: 500, height: 500 })
*
* // sets the visible area of the diagram to (100|100) -> (600|100)
* // and and scales it according to the diagram width
*
* var viewbox = canvas.viewbox(); // pass `false` to force recomputing the box.
*
* console.log(viewbox);
* // {
* // inner: Dimensions,
* // outer: Dimensions,
* // scale,
* // x, y,
* // width, height
* // }
*
* // if the current diagram is zoomed and scrolled, you may reset it to the
* // default zoom via this method, too:
*
* var zoomedAndScrolledViewbox = canvas.viewbox();
*
* canvas.viewbox({
* x: 0,
* y: 0,
* width: zoomedAndScrolledViewbox.outer.width,
* height: zoomedAndScrolledViewbox.outer.height
* });
*
* @param {Object} [box] the new view box to set
* @param {number} box.x the top left X coordinate of the canvas visible in view box
* @param {number} box.y the top left Y coordinate of the canvas visible in view box
* @param {number} box.width the visible width
* @param {number} box.height
*
* @return {Object} the current view box
*/
Canvas.prototype.viewbox = function(box) {
if (box === undefined && this._cachedViewbox) {
return this._cachedViewbox;
}
var viewport = this._viewport,
innerBox,
outerBox = this.getSize(),
matrix,
activeLayer,
transform,
scale,
x, y;
if (!box) {
// compute the inner box based on the
// diagrams active layer. This allows us to exclude
// external components, such as overlays
activeLayer = this._rootElement ? this.getActiveLayer() : null;
innerBox = activeLayer && activeLayer.getBBox() || {};
transform = transform$1(viewport);
matrix = transform ? transform.matrix : createMatrix();
scale = round$b(matrix.a, 1000);
x = round$b(-matrix.e || 0, 1000);
y = round$b(-matrix.f || 0, 1000);
box = this._cachedViewbox = {
x: x ? x / scale : 0,
y: y ? y / scale : 0,
width: outerBox.width / scale,
height: outerBox.height / scale,
scale: scale,
inner: {
width: innerBox.width || 0,
height: innerBox.height || 0,
x: innerBox.x || 0,
y: innerBox.y || 0
},
outer: outerBox
};
return box;
} else {
this._changeViewbox(function() {
scale = Math.min(outerBox.width / box.width, outerBox.height / box.height);
var matrix = this._svg.createSVGMatrix()
.scale(scale)
.translate(-box.x, -box.y);
transform$1(viewport, matrix);
});
}
return box;
};
/**
* Gets or sets the scroll of the canvas.
*
* @param {Object} [delta] the new scroll to apply.
*
* @param {number} [delta.dx]
* @param {number} [delta.dy]
*/
Canvas.prototype.scroll = function(delta) {
var node = this._viewport;
var matrix = node.getCTM();
if (delta) {
this._changeViewbox(function() {
delta = assign({ dx: 0, dy: 0 }, delta || {});
matrix = this._svg.createSVGMatrix().translate(delta.dx, delta.dy).multiply(matrix);
setCTM(node, matrix);
});
}
return { x: matrix.e, y: matrix.f };
};
/**
* Scrolls the viewbox to contain the given element.
* Optionally specify a padding to be applied to the edges.
*
* @param {Object|String} [element] the element to scroll to.
* @param {Object|Number} [padding=100] the padding to be applied. Can also specify top, bottom, left and right.
*
*/
Canvas.prototype.scrollToElement = function(element, padding) {
var defaultPadding = 100;
if (typeof element === 'string') {
element = this._elementRegistry.get(element);
}
// set to correct rootElement
var rootElement = this.findRoot(element);
if (rootElement !== this.getRootElement()) {
this.setRootElement(rootElement);
}
if (!padding) {
padding = {};
}
if (typeof padding === 'number') {
defaultPadding = padding;
}
padding = {
top: padding.top || defaultPadding,
right: padding.right || defaultPadding,
bottom: padding.bottom || defaultPadding,
left: padding.left || defaultPadding
};
var elementBounds = getBBox(element),
elementTrbl = asTRBL(elementBounds),
viewboxBounds = this.viewbox(),
zoom = this.zoom(),
dx, dy;
// shrink viewboxBounds with padding
viewboxBounds.y += padding.top / zoom;
viewboxBounds.x += padding.left / zoom;
viewboxBounds.width -= (padding.right + padding.left) / zoom;
viewboxBounds.height -= (padding.bottom + padding.top) / zoom;
var viewboxTrbl = asTRBL(viewboxBounds);
var canFit = elementBounds.width < viewboxBounds.width && elementBounds.height < viewboxBounds.height;
if (!canFit) {
// top-left when element can't fit
dx = elementBounds.x - viewboxBounds.x;
dy = elementBounds.y - viewboxBounds.y;
} else {
var dRight = Math.max(0, elementTrbl.right - viewboxTrbl.right),
dLeft = Math.min(0, elementTrbl.left - viewboxTrbl.left),
dBottom = Math.max(0, elementTrbl.bottom - viewboxTrbl.bottom),
dTop = Math.min(0, elementTrbl.top - viewboxTrbl.top);
dx = dRight || dLeft;
dy = dBottom || dTop;
}
this.scroll({ dx: -dx * zoom, dy: -dy * zoom });
};
/**
* Gets or sets the current zoom of the canvas, optionally zooming
* to the specified position.
*
* The getter may return a cached zoom level. Call it with `false` as
* the first argument to force recomputation of the current level.
*
* @param {string|number} [newScale] the new zoom level, either a number, i.e. 0.9,
* or `fit-viewport` to adjust the size to fit the current viewport
* @param {string|Point} [center] the reference point { x: .., y: ..} to zoom to, 'auto' to zoom into mid or null
*
* @return {number} the current scale
*/
Canvas.prototype.zoom = function(newScale, center) {
if (!newScale) {
return this.viewbox(newScale).scale;
}
if (newScale === 'fit-viewport') {
return this._fitViewport(center);
}
var outer,
matrix;
this._changeViewbox(function() {
if (typeof center !== 'object') {
outer = this.viewbox().outer;
center = {
x: outer.width / 2,
y: outer.height / 2
};
}
matrix = this._setZoom(newScale, center);
});
return round$b(matrix.a, 1000);
};
function setCTM(node, m) {
var mstr = 'matrix(' + m.a + ',' + m.b + ',' + m.c + ',' + m.d + ',' + m.e + ',' + m.f + ')';
node.setAttribute('transform', mstr);
}
Canvas.prototype._fitViewport = function(center) {
var vbox = this.viewbox(),
outer = vbox.outer,
inner = vbox.inner,
newScale,
newViewbox;
// display the complete diagram without zooming in.
// instead of relying on internal zoom, we perform a
// hard reset on the canvas viewbox to realize this
//
// if diagram does not need to be zoomed in, we focus it around
// the diagram origin instead
if (inner.x >= 0 &&
inner.y >= 0 &&
inner.x + inner.width <= outer.width &&
inner.y + inner.height <= outer.height &&
!center) {
newViewbox = {
x: 0,
y: 0,
width: Math.max(inner.width + inner.x, outer.width),
height: Math.max(inner.height + inner.y, outer.height)
};
} else {
newScale = Math.min(1, outer.width / inner.width, outer.height / inner.height);
newViewbox = {
x: inner.x + (center ? inner.width / 2 - outer.width / newScale / 2 : 0),
y: inner.y + (center ? inner.height / 2 - outer.height / newScale / 2 : 0),
width: outer.width / newScale,
height: outer.height / newScale
};
}
this.viewbox(newViewbox);
return this.viewbox(false).scale;
};
Canvas.prototype._setZoom = function(scale, center) {
var svg = this._svg,
viewport = this._viewport;
var matrix = svg.createSVGMatrix();
var point = svg.createSVGPoint();
var centerPoint,
originalPoint,
currentMatrix,
scaleMatrix,
newMatrix;
currentMatrix = viewport.getCTM();
var currentScale = currentMatrix.a;
if (center) {
centerPoint = assign(point, center);
// revert applied viewport transformations
originalPoint = centerPoint.matrixTransform(currentMatrix.inverse());
// create scale matrix
scaleMatrix = matrix
.translate(originalPoint.x, originalPoint.y)
.scale(1 / currentScale * scale)
.translate(-originalPoint.x, -originalPoint.y);
newMatrix = currentMatrix.multiply(scaleMatrix);
} else {
newMatrix = matrix.scale(scale);
}
setCTM(this._viewport, newMatrix);
return newMatrix;
};
/**
* Returns the size of the canvas
*
* @return {Dimensions}
*/
Canvas.prototype.getSize = function() {
return {
width: this._container.clientWidth,
height: this._container.clientHeight
};
};
/**
* Return the absolute bounding box for the given element
*
* The absolute bounding box may be used to display overlays in the
* callers (browser) coordinate system rather than the zoomed in/out
* canvas coordinates.
*
* @param {ElementDescriptor} element
* @return {Bounds} the absolute bounding box
*/
Canvas.prototype.getAbsoluteBBox = function(element) {
var vbox = this.viewbox();
var bbox;
// connection
// use svg bbox
if (element.waypoints) {
var gfx = this.getGraphics(element);
bbox = gfx.getBBox();
}
// shapes
// use data
else {
bbox = element;
}
var x = bbox.x * vbox.scale - vbox.x * vbox.scale;
var y = bbox.y * vbox.scale - vbox.y * vbox.scale;
var width = bbox.width * vbox.scale;
var height = bbox.height * vbox.scale;
return {
x: x,
y: y,
width: width,
height: height
};
};
/**
* Fires an event in order other modules can react to the
* canvas resizing
*/
Canvas.prototype.resized = function() {
// force recomputation of view box
delete this._cachedViewbox;
this._eventBus.fire('canvas.resized');
};
var ELEMENT_ID = 'data-element-id';
/**
* @class
*
* A registry that keeps track of all shapes in the diagram.
*/
function ElementRegistry(eventBus) {
this._elements = {};
this._eventBus = eventBus;
}
ElementRegistry.$inject = [ 'eventBus' ];
/**
* Register a pair of (element, gfx, (secondaryGfx)).
*
* @param {djs.model.Base} element
* @param {SVGElement} gfx
* @param {SVGElement} [secondaryGfx] optional other element to register, too
*/
ElementRegistry.prototype.add = function(element, gfx, secondaryGfx) {
var id = element.id;
this._validateId(id);
// associate dom node with element
attr(gfx, ELEMENT_ID, id);
if (secondaryGfx) {
attr(secondaryGfx, ELEMENT_ID, id);
}
this._elements[id] = { element: element, gfx: gfx, secondaryGfx: secondaryGfx };
};
/**
* Removes an element from the registry.
*
* @param {djs.model.Base} element
*/
ElementRegistry.prototype.remove = function(element) {
var elements = this._elements,
id = element.id || element,
container = id && elements[id];
if (container) {
// unset element id on gfx
attr(container.gfx, ELEMENT_ID, '');
if (container.secondaryGfx) {
attr(container.secondaryGfx, ELEMENT_ID, '');
}
delete elements[id];
}
};
/**
* Update the id of an element
*
* @param {djs.model.Base} element
* @param {string} newId
*/
ElementRegistry.prototype.updateId = function(element, newId) {
this._validateId(newId);
if (typeof element === 'string') {
element = this.get(element);
}
this._eventBus.fire('element.updateId', {
element: element,
newId: newId
});
var gfx = this.getGraphics(element),
secondaryGfx = this.getGraphics(element, true);
this.remove(element);
element.id = newId;
this.add(element, gfx, secondaryGfx);
};
/**
* Update the graphics of an element
*
* @param {djs.model.Base} element
* @param {SVGElement} gfx
* @param {boolean} [secondary=false] whether to update the secondary connected element
*/
ElementRegistry.prototype.updateGraphics = function(filter, gfx, secondary) {
var id = filter.id || filter;
var container = this._elements[id];
if (secondary) {
container.secondaryGfx = gfx;
} else {
container.gfx = gfx;
}
if (gfx) {
attr(gfx, ELEMENT_ID, id);
}
return gfx;
};
/**
* Return the model element for a given id or graphics.
*
* @example
*
* elementRegistry.get('SomeElementId_1');
* elementRegistry.get(gfx);
*
*
* @param {string|SVGElement} filter for selecting the element
*
* @return {djs.model.Base}
*/
ElementRegistry.prototype.get = function(filter) {
var id;
if (typeof filter === 'string') {
id = filter;
} else {
id = filter && attr(filter, ELEMENT_ID);
}
var container = this._elements[id];
return container && container.element;
};
/**
* Return all elements that match a given filter function.
*
* @param {Function} fn
*
* @return {Array<djs.model.Base>}
*/
ElementRegistry.prototype.filter = function(fn) {
var filtered = [];
this.forEach(function(element, gfx) {
if (fn(element, gfx)) {
filtered.push(element);
}
});
return filtered;
};
/**
* Return the first element that satisfies the provided testing function.
*
* @param {Function} fn
*
* @return {djs.model.Base}
*/
ElementRegistry.prototype.find = function(fn) {
var map = this._elements,
keys = Object.keys(map);
for (var i = 0; i < keys.length; i++) {
var id = keys[i],
container = map[id],
element = container.element,
gfx = container.gfx;
if (fn(element, gfx)) {
return element;
}
}
};
/**
* Return all rendered model elements.
*
* @return {Array<djs.model.Base>}
*/
ElementRegistry.prototype.getAll = function() {
return this.filter(function(e) { return e; });
};
/**
* Iterate over all diagram elements.
*
* @param {Function} fn
*/
ElementRegistry.prototype.forEach = function(fn) {
var map = this._elements;
Object.keys(map).forEach(function(id) {
var container = map[id],
element = container.element,
gfx = container.gfx;
return fn(element, gfx);
});
};
/**
* Return the graphical representation of an element or its id.
*
* @example
* elementRegistry.getGraphics('SomeElementId_1');
* elementRegistry.getGraphics(rootElement); // <g ...>
*
* elementRegistry.getGraphics(rootElement, true); // <svg ...>
*
*
* @param {string|djs.model.Base} filter
* @param {boolean} [secondary=false] whether to return the secondary connected element
*
* @return {SVGElement}
*/
ElementRegistry.prototype.getGraphics = function(filter, secondary) {
var id = filter.id || filter;
var container = this._elements[id];
return container && (secondary ? container.secondaryGfx : container.gfx);
};
/**
* Validate the suitability of the given id and signals a problem
* with an exception.
*
* @param {string} id
*
* @throws {Error} if id is empty or already assigned
*/
ElementRegistry.prototype._validateId = function(id) {
if (!id) {
throw new Error('element must have an id');
}
if (this._elements[id]) {
throw new Error('element with id ' + id + ' already added');
}
};
var objectRefs = {exports: {}};
var collection = {};
/**
* An empty collection stub. Use {@link RefsCollection.extend} to extend a
* collection with ref semantics.
*
* @class RefsCollection
*/
/**
* Extends a collection with {@link Refs} aware methods
*
* @memberof RefsCollection
* @static
*
* @param {Array<Object>} collection
* @param {Refs} refs instance
* @param {Object} property represented by the collection
* @param {Object} target object the collection is attached to
*
* @return {RefsCollection<Object>} the extended array
*/
function extend(collection, refs, property, target) {
var inverseProperty = property.inverse;
/**
* Removes the given element from the array and returns it.
*
* @method RefsCollection#remove
*
* @param {Object} element the element to remove
*/
Object.defineProperty(collection, 'remove', {
value: function(element) {
var idx = this.indexOf(element);
if (idx !== -1) {
this.splice(idx, 1);
// unset inverse
refs.unset(element, inverseProperty, target);
}
return element;
}
});
/**
* Returns true if the collection contains the given element
*
* @method RefsCollection#contains
*
* @param {Object} element the element to check for
*/
Object.defineProperty(collection, 'contains', {
value: function(element) {
return this.indexOf(element) !== -1;
}
});
/**
* Adds an element to the array, unless it exists already (set semantics).
*
* @method RefsCollection#add
*
* @param {Object} element the element to add
* @param {Number} optional index to add element to
* (possibly moving other elements around)
*/
Object.defineProperty(collection, 'add', {
value: function(element, idx) {
var currentIdx = this.indexOf(element);
if (typeof idx === 'undefined') {
if (currentIdx !== -1) {
// element already in collection (!)
return;
}
// add to end of array, as no idx is specified
idx = this.length;
}
// handle already in collection
if (currentIdx !== -1) {
// remove element from currentIdx
this.splice(currentIdx, 1);
}
// add element at idx
this.splice(idx, 0, element);
if (currentIdx === -1) {
// set inverse, unless element was
// in collection already
refs.set(element, inverseProperty, target);
}
}
});
// a simple marker, identifying this element
// as being a refs collection
Object.defineProperty(collection, '__refs_collection', {
value: true
});
return collection;
}
function isExtended(collection) {
return collection.__refs_collection === true;
}
collection.extend = extend;
collection.isExtended = isExtended;
var Collection = collection;
function hasOwnProperty$1(e, property) {
return Object.prototype.hasOwnProperty.call(e, property.name || property);
}
function defineCollectionProperty(ref, property, target) {
var collection = Collection.extend(target[property.name] || [], ref, property, target);
Object.defineProperty(target, property.name, {
enumerable: property.enumerable,
value: collection
});
if (collection.length) {
collection.forEach(function(o) {
ref.set(o, property.inverse, target);
});
}
}
function defineProperty$1(ref, property, target) {
var inverseProperty = property.inverse;
var _value = target[property.name];
Object.defineProperty(target, property.name, {
configurable: property.configurable,
enumerable: property.enumerable,
get: function() {
return _value;
},
set: function(value) {
// return if we already performed all changes
if (value === _value) {
return;
}
var old = _value;
// temporary set null
_value = null;
if (old) {
ref.unset(old, inverseProperty, target);
}
// set new value
_value = value;
// set inverse value
ref.set(_value, inverseProperty, target);
}
});
}
/**
* Creates a new references object defining two inversly related
* attribute descriptors a and b.
*
* <p>
* When bound to an object using {@link Refs#bind} the references
* get activated and ensure that add and remove operations are applied
* reversely, too.
* </p>
*
* <p>
* For attributes represented as collections {@link Refs} provides the
* {@link RefsCollection#add}, {@link RefsCollection#remove} and {@link RefsCollection#contains} extensions
* that must be used to properly hook into the inverse change mechanism.
* </p>
*
* @class Refs
*
* @classdesc A bi-directional reference between two attributes.
*
* @param {Refs.AttributeDescriptor} a property descriptor
* @param {Refs.AttributeDescriptor} b property descriptor
*
* @example
*
* var refs = Refs({ name: 'wheels', collection: true, enumerable: true }, { name: 'car' });
*
* var car = { name: 'toyota' };
* var wheels = [{ pos: 'front-left' }, { pos: 'front-right' }];
*
* refs.bind(car, 'wheels');
*
* car.wheels // []
* car.wheels.add(wheels[0]);
* car.wheels.add(wheels[1]);
*
* car.wheels // [{ pos: 'front-left' }, { pos: 'front-right' }]
*
* wheels[0].car // { name: 'toyota' };
* car.wheels.remove(wheels[0]);
*
* wheels[0].car // undefined
*/
function Refs$1(a, b) {
if (!(this instanceof Refs$1)) {
return new Refs$1(a, b);
}
// link
a.inverse = b;
b.inverse = a;
this.props = {};
this.props[a.name] = a;
this.props[b.name] = b;
}
/**
* Binds one side of a bi-directional reference to a
* target object.
*
* @memberOf Refs
*
* @param {Object} target
* @param {String} property
*/
Refs$1.prototype.bind = function(target, property) {
if (typeof property === 'string') {
if (!this.props[property]) {
throw new Error('no property <' + property + '> in ref');
}
property = this.props[property];
}
if (property.collection) {
defineCollectionProperty(this, property, target);
} else {
defineProperty$1(this, property, target);
}
};
Refs$1.prototype.ensureRefsCollection = function(target, property) {
var collection = target[property.name];
if (!Collection.isExtended(collection)) {
defineCollectionProperty(this, property, target);
}
return collection;
};
Refs$1.prototype.ensureBound = function(target, property) {
if (!hasOwnProperty$1(target, property)) {
this.bind(target, property);
}
};
Refs$1.prototype.unset = function(target, property, value) {
if (target) {
this.ensureBound(target, property);
if (property.collection) {
this.ensureRefsCollection(target, property).remove(value);
} else {
target[property.name] = undefined;
}
}
};
Refs$1.prototype.set = function(target, property, value) {
if (target) {
this.ensureBound(target, property);
if (property.collection) {
this.ensureRefsCollection(target, property).add(value);
} else {
target[property.name] = value;
}
}
};
var refs = Refs$1;
(function (module) {
module.exports = refs;
module.exports.Collection = collection;
} (objectRefs));
var Refs = /*@__PURE__*/getDefaultExportFromCjs(objectRefs.exports);
var parentRefs = new Refs({ name: 'children', enumerable: true, collection: true }, { name: 'parent' }),
labelRefs = new Refs({ name: 'labels', enumerable: true, collection: true }, { name: 'labelTarget' }),
attacherRefs = new Refs({ name: 'attachers', collection: true }, { name: 'host' }),
outgoingRefs = new Refs({ name: 'outgoing', collection: true }, { name: 'source' }),
incomingRefs = new Refs({ name: 'incoming', collection: true }, { name: 'target' });
/**
* @namespace djs.model
*/
/**
* @memberOf djs.model
*/
/**
* The basic graphical representation
*
* @class
*
* @abstract
*/
function Base$1() {
/**
* The object that backs up the shape
*
* @name Base#businessObject
* @type Object
*/
Object.defineProperty(this, 'businessObject', {
writable: true
});
/**
* Single label support, will mapped to multi label array
*
* @name Base#label
* @type Object
*/
Object.defineProperty(this, 'label', {
get: function() {
return this.labels[0];
},
set: function(newLabel) {
var label = this.label,
labels = this.labels;
if (!newLabel && label) {
labels.remove(label);
} else {
labels.add(newLabel, 0);
}
}
});
/**
* The parent shape
*
* @name Base#parent
* @type Shape
*/
parentRefs.bind(this, 'parent');
/**
* The list of labels
*
* @name Base#labels
* @type Label
*/
labelRefs.bind(this, 'labels');
/**
* The list of outgoing connections
*
* @name Base#outgoing
* @type Array<Connection>
*/
outgoingRefs.bind(this, 'outgoing');
/**
* The list of incoming connections
*
* @name Base#incoming
* @type Array<Connection>
*/
incomingRefs.bind(this, 'incoming');
}
/**
* A graphical object
*
* @class
* @constructor
*
* @extends Base
*/
function Shape() {
Base$1.call(this);
/**
* Indicates frame shapes
*
* @name Shape#isFrame
* @type boolean
*/
/**
* The list of children
*
* @name Shape#children
* @type Array<Base>
*/
parentRefs.bind(this, 'children');
/**
* @name Shape#host
* @type Shape
*/
attacherRefs.bind(this, 'host');
/**
* @name Shape#attachers
* @type Shape
*/
attacherRefs.bind(this, 'attachers');
}
e(Shape, Base$1);
/**
* A root graphical object
*
* @class
* @constructor
*
* @extends Shape
*/
function Root() {
Shape.call(this);
}
e(Root, Shape);
/**
* A label for an element
*
* @class
* @constructor
*
* @extends Shape
*/
function Label() {
Shape.call(this);
/**
* The labeled element
*
* @name Label#labelTarget
* @type Base
*/
labelRefs.bind(this, 'labelTarget');
}
e(Label, Shape);
/**
* A connection between two elements
*
* @class
* @constructor
*
* @extends Base
*/
function Connection() {
Base$1.call(this);
/**
* The element this connection originates from
*
* @name Connection#source
* @type Base
*/
outgoingRefs.bind(this, 'source');
/**
* The element this connection points to
*
* @name Connection#target
* @type Base
*/
incomingRefs.bind(this, 'target');
}
e(Connection, Base$1);
var types$6 = {
connection: Connection,
shape: Shape,
label: Label,
root: Root
};
/**
* Creates a new model element of the specified type
*
* @method create
*
* @example
*
* var shape1 = Model.create('shape', { x: 10, y: 10, width: 100, height: 100 });
* var shape2 = Model.create('shape', { x: 210, y: 210, width: 100, height: 100 });
*
* var connection = Model.create('connection', { waypoints: [ { x: 110, y: 55 }, {x: 210, y: 55 } ] });
*
* @param {string} type lower-cased model name
* @param {Object} attrs attributes to initialize the new model instance with
*
* @return {Base} the new model instance
*/
function create(type, attrs) {
var Type = types$6[type];
if (!Type) {
throw new Error('unknown type: <' + type + '>');
}
return assign(new Type(), attrs);
}
/**
* A factory for diagram-js shapes
*/
function ElementFactory$1() {
this._uid = 12;
}
ElementFactory$1.prototype.createRoot = function(attrs) {
return this.create('root', attrs);
};
ElementFactory$1.prototype.createLabel = function(attrs) {
return this.create('label', attrs);
};
ElementFactory$1.prototype.createShape = function(attrs) {
return this.create('shape', attrs);
};
ElementFactory$1.prototype.createConnection = function(attrs) {
return this.create('connection', attrs);
};
/**
* Create a model element with the given type and
* a number of pre-set attributes.
*
* @param {string} type
* @param {Object} attrs
* @return {djs.model.Base} the newly created model instance
*/
ElementFactory$1.prototype.create = function(type, attrs) {
attrs = assign({}, attrs || {});
if (!attrs.id) {
attrs.id = type + '_' + (this._uid++);
}
return create(type, attrs);
};
var FN_REF = '__fn';
var DEFAULT_PRIORITY$5 = 1000;
var slice = Array.prototype.slice;
/**
* A general purpose event bus.
*
* This component is used to communicate across a diagram instance.
* Other parts of a diagram can use it to listen to and broadcast events.
*
*
* ## Registering for Events
*
* The event bus provides the {@link EventBus#on} and {@link EventBus#once}
* methods to register for events. {@link EventBus#off} can be used to
* remove event registrations. Listeners receive an instance of {@link Event}
* as the first argument. It allows them to hook into the event execution.
*
* ```javascript
*
* // listen for event
* eventBus.on('foo', function(event) {
*
* // access event type
* event.type; // 'foo'
*
* // stop propagation to other listeners
* event.stopPropagation();
*
* // prevent event default
* event.preventDefault();
* });
*
* // listen for event with custom payload
* eventBus.on('bar', function(event, payload) {
* console.log(payload);
* });
*
* // listen for event returning value
* eventBus.on('foobar', function(event) {
*
* // stop event propagation + prevent default
* return false;
*
* // stop event propagation + return custom result
* return {
* complex: 'listening result'
* };
* });
*
*
* // listen with custom priority (default=1000, higher is better)
* eventBus.on('priorityfoo', 1500, function(event) {
* console.log('invoked first!');
* });
*
*
* // listen for event and pass the context (`this`)
* eventBus.on('foobar', function(event) {
* this.foo();
* }, this);
* ```
*
*
* ## Emitting Events
*
* Events can be emitted via the event bus using {@link EventBus#fire}.
*
* ```javascript
*
* // false indicates that the default action
* // was prevented by listeners
* if (eventBus.fire('foo') === false) {
* console.log('default has been prevented!');
* };
*
*
* // custom args + return value listener
* eventBus.on('sum', function(event, a, b) {
* return a + b;
* });
*
* // you can pass custom arguments + retrieve result values.
* var sum = eventBus.fire('sum', 1, 2);
* console.log(sum); // 3
* ```
*/
function EventBus() {
this._listeners = {};
// cleanup on destroy on lowest priority to allow
// message passing until the bitter end
this.on('diagram.destroy', 1, this._destroy, this);
}
/**
* Register an event listener for events with the given name.
*
* The callback will be invoked with `event, ...additionalArguments`
* that have been passed to {@link EventBus#fire}.
*
* Returning false from a listener will prevent the events default action
* (if any is specified). To stop an event from being processed further in
* other listeners execute {@link Event#stopPropagation}.
*
* Returning anything but `undefined` from a listener will stop the listener propagation.
*
* @param {string|Array<string>} events
* @param {number} [priority=1000] the priority in which this listener is called, larger is higher
* @param {Function} callback
* @param {Object} [that] Pass context (`this`) to the callback
*/
EventBus.prototype.on = function(events, priority, callback, that) {
events = isArray$3(events) ? events : [ events ];
if (isFunction(priority)) {
that = callback;
callback = priority;
priority = DEFAULT_PRIORITY$5;
}
if (!isNumber(priority)) {
throw new Error('priority must be a number');
}
var actualCallback = callback;
if (that) {
actualCallback = bind(callback, that);
// make sure we remember and are able to remove
// bound callbacks via {@link #off} using the original
// callback
actualCallback[FN_REF] = callback[FN_REF] || callback;
}
var self = this;
events.forEach(function(e) {
self._addListener(e, {
priority: priority,
callback: actualCallback,
next: null
});
});
};
/**
* Register an event listener that is executed only once.
*
* @param {string} event the event name to register for
* @param {number} [priority=1000] the priority in which this listener is called, larger is higher
* @param {Function} callback the callback to execute
* @param {Object} [that] Pass context (`this`) to the callback
*/
EventBus.prototype.once = function(event, priority, callback, that) {
var self = this;
if (isFunction(priority)) {
that = callback;
callback = priority;
priority = DEFAULT_PRIORITY$5;
}
if (!isNumber(priority)) {
throw new Error('priority must be a number');
}
function wrappedCallback() {
wrappedCallback.__isTomb = true;
var result = callback.apply(that, arguments);
self.off(event, wrappedCallback);
return result;
}
// make sure we remember and are able to remove
// bound callbacks via {@link #off} using the original
// callback
wrappedCallback[FN_REF] = callback;
this.on(event, priority, wrappedCallback);
};
/**
* Removes event listeners by event and callback.
*
* If no callback is given, all listeners for a given event name are being removed.
*
* @param {string|Array<string>} events
* @param {Function} [callback]
*/
EventBus.prototype.off = function(events, callback) {
events = isArray$3(events) ? events : [ events ];
var self = this;
events.forEach(function(event) {
self._removeListener(event, callback);
});
};
/**
* Create an EventBus event.
*
* @param {Object} data
*
* @return {Object} event, recognized by the eventBus
*/
EventBus.prototype.createEvent = function(data) {
var event = new InternalEvent();
event.init(data);
return event;
};
/**
* Fires a named event.
*
* @example
*
* // fire event by name
* events.fire('foo');
*
* // fire event object with nested type
* var event = { type: 'foo' };
* events.fire(event);
*
* // fire event with explicit type
* var event = { x: 10, y: 20 };
* events.fire('element.moved', event);
*
* // pass additional arguments to the event
* events.on('foo', function(event, bar) {
* alert(bar);
* });
*
* events.fire({ type: 'foo' }, 'I am bar!');
*
* @param {string} [name] the optional event name
* @param {Object} [event] the event object
* @param {...Object} additional arguments to be passed to the callback functions
*
* @return {boolean} the events return value, if specified or false if the
* default action was prevented by listeners
*/
EventBus.prototype.fire = function(type, data) {
var event,
firstListener,
returnValue,
args;
args = slice.call(arguments);
if (typeof type === 'object') {
data = type;
type = data.type;
}
if (!type) {
throw new Error('no event type specified');
}
firstListener = this._listeners[type];
if (!firstListener) {
return;
}
// we make sure we fire instances of our home made
// events here. We wrap them only once, though
if (data instanceof InternalEvent) {
// we are fine, we alread have an event
event = data;
} else {
event = this.createEvent(data);
}
// ensure we pass the event as the first parameter
args[0] = event;
// original event type (in case we delegate)
var originalType = event.type;
// update event type before delegation
if (type !== originalType) {
event.type = type;
}
try {
returnValue = this._invokeListeners(event, args, firstListener);
} finally {
// reset event type after delegation
if (type !== originalType) {
event.type = originalType;
}
}
// set the return value to false if the event default
// got prevented and no other return value exists
if (returnValue === undefined && event.defaultPrevented) {
returnValue = false;
}
return returnValue;
};
EventBus.prototype.handleError = function(error) {
return this.fire('error', { error: error }) === false;
};
EventBus.prototype._destroy = function() {
this._listeners = {};
};
EventBus.prototype._invokeListeners = function(event, args, listener) {
var returnValue;
while (listener) {
// handle stopped propagation
if (event.cancelBubble) {
break;
}
returnValue = this._invokeListener(event, args, listener);
listener = listener.next;
}
return returnValue;
};
EventBus.prototype._invokeListener = function(event, args, listener) {
var returnValue;
if (listener.callback.__isTomb) {
return returnValue;
}
try {
// returning false prevents the default action
returnValue = invokeFunction(listener.callback, args);
// stop propagation on return value
if (returnValue !== undefined) {
event.returnValue = returnValue;
event.stopPropagation();
}
// prevent default on return false
if (returnValue === false) {
event.preventDefault();
}
} catch (error) {
if (!this.handleError(error)) {
console.error('unhandled error in event listener', error);
throw error;
}
}
return returnValue;
};
/*
* Add new listener with a certain priority to the list
* of listeners (for the given event).
*
* The semantics of listener registration / listener execution are
* first register, first serve: New listeners will always be inserted
* after existing listeners with the same priority.
*
* Example: Inserting two listeners with priority 1000 and 1300
*
* * before: [ 1500, 1500, 1000, 1000 ]
* * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ]
*
* @param {string} event
* @param {Object} listener { priority, callback }
*/
EventBus.prototype._addListener = function(event, newListener) {
var listener = this._getListeners(event),
previousListener;
// no prior listeners
if (!listener) {
this._setListeners(event, newListener);
return;
}
// ensure we order listeners by priority from
// 0 (high) to n > 0 (low)
while (listener) {
if (listener.priority < newListener.priority) {
newListener.next = listener;
if (previousListener) {
previousListener.next = newListener;
} else {
this._setListeners(event, newListener);
}
return;
}
previousListener = listener;
listener = listener.next;
}
// add new listener to back
previousListener.next = newListener;
};
EventBus.prototype._getListeners = function(name) {
return this._listeners[name];
};
EventBus.prototype._setListeners = function(name, listener) {
this._listeners[name] = listener;
};
EventBus.prototype._removeListener = function(event, callback) {
var listener = this._getListeners(event),
nextListener,
previousListener,
listenerCallback;
if (!callback) {
// clear listeners
this._setListeners(event, null);
return;
}
while (listener) {
nextListener = listener.next;
listenerCallback = listener.callback;
if (listenerCallback === callback || listenerCallback[FN_REF] === callback) {
if (previousListener) {
previousListener.next = nextListener;
} else {
// new first listener
this._setListeners(event, nextListener);
}
}
previousListener = listener;
listener = nextListener;
}
};
/**
* A event that is emitted via the event bus.
*/
function InternalEvent() { }
InternalEvent.prototype.stopPropagation = function() {
this.cancelBubble = true;
};
InternalEvent.prototype.preventDefault = function() {
this.defaultPrevented = true;
};
InternalEvent.prototype.init = function(data) {
assign(this, data || {});
};
/**
* Invoke function. Be fast...
*
* @param {Function} fn
* @param {Array<Object>} args
*
* @return {Any}
*/
function invokeFunction(fn, args) {
return fn.apply(null, args);
}
/**
* SVGs for elements are generated by the {@link GraphicsFactory}.
*
* This utility gives quick access to the important semantic
* parts of an element.
*/
/**
* Returns the visual part of a diagram element
*
* @param {Snap<SVGElement>} gfx
*
* @return {Snap<SVGElement>}
*/
function getVisual(gfx) {
return gfx.childNodes[0];
}
/**
* Returns the children for a given diagram element.
*
* @param {Snap<SVGElement>} gfx
* @return {Snap<SVGElement>}
*/
function getChildren$1(gfx) {
return gfx.parentNode.childNodes[1];
}
/**
* @param {<SVGElement>} element
* @param {number} x
* @param {number} y
* @param {number} angle
* @param {number} amount
*/
function transform(gfx, x, y, angle, amount) {
var translate = createTransform();
translate.setTranslate(x, y);
var rotate = createTransform();
rotate.setRotate(angle || 0, 0, 0);
var scale = createTransform();
scale.setScale(amount || 1, amount || 1);
transform$1(gfx, [ translate, rotate, scale ]);
}
/**
* @param {SVGElement} element
* @param {number} x
* @param {number} y
*/
function translate$2(gfx, x, y) {
var translate = createTransform();
translate.setTranslate(x, y);
transform$1(gfx, translate);
}
/**
* @param {SVGElement} element
* @param {number} angle
*/
function rotate(gfx, angle) {
var rotate = createTransform();
rotate.setRotate(angle, 0, 0);
transform$1(gfx, rotate);
}
/**
* A factory that creates graphical elements
*
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
*/
function GraphicsFactory(eventBus, elementRegistry) {
this._eventBus = eventBus;
this._elementRegistry = elementRegistry;
}
GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ];
GraphicsFactory.prototype._getChildrenContainer = function(element) {
var gfx = this._elementRegistry.getGraphics(element);
var childrenGfx;
// root element
if (!element.parent) {
childrenGfx = gfx;
} else {
childrenGfx = getChildren$1(gfx);
if (!childrenGfx) {
childrenGfx = create$1('g');
classes(childrenGfx).add('djs-children');
append(gfx.parentNode, childrenGfx);
}
}
return childrenGfx;
};
/**
* Clears the graphical representation of the element and returns the
* cleared visual (the <g class="djs-visual" /> element).
*/
GraphicsFactory.prototype._clear = function(gfx) {
var visual = getVisual(gfx);
clear$1(visual);
return visual;
};
/**
* Creates a gfx container for shapes and connections
*
* The layout is as follows:
*
* <g class="djs-group">
*
* <!-- the gfx -->
* <g class="djs-element djs-(shape|connection|frame)">
* <g class="djs-visual">
* <!-- the renderer draws in here -->
* </g>
*
* <!-- extensions (overlays, click box, ...) goes here
* </g>
*
* <!-- the gfx child nodes -->
* <g class="djs-children"></g>
* </g>
*
* @param {string} type the type of the element, i.e. shape | connection
* @param {SVGElement} [childrenGfx]
* @param {number} [parentIndex] position to create container in parent
* @param {boolean} [isFrame] is frame element
*
* @return {SVGElement}
*/
GraphicsFactory.prototype._createContainer = function(
type, childrenGfx, parentIndex, isFrame
) {
var outerGfx = create$1('g');
classes(outerGfx).add('djs-group');
// insert node at position
if (typeof parentIndex !== 'undefined') {
prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]);
} else {
append(childrenGfx, outerGfx);
}
var gfx = create$1('g');
classes(gfx).add('djs-element');
classes(gfx).add('djs-' + type);
if (isFrame) {
classes(gfx).add('djs-frame');
}
append(outerGfx, gfx);
// create visual
var visual = create$1('g');
classes(visual).add('djs-visual');
append(gfx, visual);
return gfx;
};
GraphicsFactory.prototype.create = function(type, element, parentIndex) {
var childrenGfx = this._getChildrenContainer(element.parent);
return this._createContainer(type, childrenGfx, parentIndex, isFrameElement$1(element));
};
GraphicsFactory.prototype.updateContainments = function(elements) {
var self = this,
elementRegistry = this._elementRegistry,
parents;
parents = reduce(elements, function(map, e) {
if (e.parent) {
map[e.parent.id] = e.parent;
}
return map;
}, {});
// update all parents of changed and reorganized their children
// in the correct order (as indicated in our model)
forEach$1(parents, function(parent) {
var children = parent.children;
if (!children) {
return;
}
var childrenGfx = self._getChildrenContainer(parent);
forEach$1(children.slice().reverse(), function(child) {
var childGfx = elementRegistry.getGraphics(child);
prependTo(childGfx.parentNode, childrenGfx);
});
});
};
GraphicsFactory.prototype.drawShape = function(visual, element) {
var eventBus = this._eventBus;
return eventBus.fire('render.shape', { gfx: visual, element: element });
};
GraphicsFactory.prototype.getShapePath = function(element) {
var eventBus = this._eventBus;
return eventBus.fire('render.getShapePath', element);
};
GraphicsFactory.prototype.drawConnection = function(visual, element) {
var eventBus = this._eventBus;
return eventBus.fire('render.connection', { gfx: visual, element: element });
};
GraphicsFactory.prototype.getConnectionPath = function(waypoints) {
var eventBus = this._eventBus;
return eventBus.fire('render.getConnectionPath', waypoints);
};
GraphicsFactory.prototype.update = function(type, element, gfx) {
// do NOT update root element
if (!element.parent) {
return;
}
var visual = this._clear(gfx);
// redraw
if (type === 'shape') {
this.drawShape(visual, element);
// update positioning
translate$2(gfx, element.x, element.y);
} else
if (type === 'connection') {
this.drawConnection(visual, element);
} else {
throw new Error('unknown type: ' + type);
}
if (element.hidden) {
attr(gfx, 'display', 'none');
} else {
attr(gfx, 'display', 'block');
}
};
GraphicsFactory.prototype.remove = function(element) {
var gfx = this._elementRegistry.getGraphics(element);
// remove
remove$1(gfx.parentNode);
};
// helpers //////////
function prependTo(newNode, parentNode, siblingNode) {
var node = siblingNode || parentNode.firstChild;
// do not prepend node to itself to prevent IE from crashing
// https://github.com/bpmn-io/bpmn-js/issues/746
if (newNode === node) {
return;
}
parentNode.insertBefore(newNode, node);
}
var CoreModule$1 = {
__depends__: [ DrawModule$1 ],
__init__: [ 'canvas' ],
canvas: [ 'type', Canvas ],
elementRegistry: [ 'type', ElementRegistry ],
elementFactory: [ 'type', ElementFactory$1 ],
eventBus: [ 'type', EventBus ],
graphicsFactory: [ 'type', GraphicsFactory ]
};
/**
* @typedef { import('didi').ModuleDeclaration } Module
*/
/**
* Bootstrap an injector from a list of modules, instantiating a number of default components
*
* @param {Array<Module>} modules
*
* @return {Injector} a injector to use to access the components
*/
function bootstrap(modules) {
var injector = new Injector(modules);
injector.init();
return injector;
}
/**
* Creates an injector from passed options.
*
* @param {Object} options
* @return {Injector}
*/
function createInjector(options) {
options = options || {};
var configModule = {
'config': [ 'value', options ]
};
var modules = [ configModule, CoreModule$1 ].concat(options.modules || []);
return bootstrap(modules);
}
/**
* The main diagram-js entry point that bootstraps the diagram with the given
* configuration.
*
* To register extensions with the diagram, pass them as Array<Module> to the constructor.
*
* @class djs.Diagram
* @memberOf djs
* @constructor
*
* @example
*
* <caption>Creating a plug-in that logs whenever a shape is added to the canvas.</caption>
*
* // plug-in implemenentation
* function MyLoggingPlugin(eventBus) {
* eventBus.on('shape.added', function(event) {
* console.log('shape ', event.shape, ' was added to the diagram');
* });
* }
*
* // export as module
* export default {
* __init__: [ 'myLoggingPlugin' ],
* myLoggingPlugin: [ 'type', MyLoggingPlugin ]
* };
*
*
* // instantiate the diagram with the new plug-in
*
* import MyLoggingModule from 'path-to-my-logging-plugin';
*
* var diagram = new Diagram({
* modules: [
* MyLoggingModule
* ]
* });
*
* diagram.invoke([ 'canvas', function(canvas) {
* // add shape to drawing canvas
* canvas.addShape({ x: 10, y: 10 });
* });
*
* // 'shape ... was added to the diagram' logged to console
*
* @param {Object} options
* @param {Array<Module>} [options.modules] external modules to instantiate with the diagram
* @param {Injector} [injector] an (optional) injector to bootstrap the diagram with
*/
function Diagram(options, injector) {
// create injector unless explicitly specified
this.injector = injector = injector || createInjector(options);
// API
/**
* Resolves a diagram service
*
* @method Diagram#get
*
* @param {string} name the name of the diagram service to be retrieved
* @param {boolean} [strict=true] if false, resolve missing services to null
*/
this.get = injector.get;
/**
* Executes a function into which diagram services are injected
*
* @method Diagram#invoke
*
* @param {Function|Object[]} fn the function to resolve
* @param {Object} locals a number of locals to use to resolve certain dependencies
*/
this.invoke = injector.invoke;
// init
// indicate via event
/**
* An event indicating that all plug-ins are loaded.
*
* Use this event to fire other events to interested plug-ins
*
* @memberOf Diagram
*
* @event diagram.init
*
* @example
*
* eventBus.on('diagram.init', function() {
* eventBus.fire('my-custom-event', { foo: 'BAR' });
* });
*
* @type {Object}
*/
this.get('eventBus').fire('diagram.init');
}
/**
* Destroys the diagram
*
* @method Diagram#destroy
*/
Diagram.prototype.destroy = function() {
this.get('eventBus').fire('diagram.destroy');
};
/**
* Clear the diagram, removing all contents.
*/
Diagram.prototype.clear = function() {
this.get('eventBus').fire('diagram.clear');
};
/**
* Moddle base element.
*/
function Base() { }
Base.prototype.get = function(name) {
return this.$model.properties.get(this, name);
};
Base.prototype.set = function(name, value) {
this.$model.properties.set(this, name, value);
};
/**
* A model element factory.
*
* @param {Moddle} model
* @param {Properties} properties
*/
function Factory(model, properties) {
this.model = model;
this.properties = properties;
}
Factory.prototype.createType = function(descriptor) {
var model = this.model;
var props = this.properties,
prototype = Object.create(Base.prototype);
// initialize default values
forEach$1(descriptor.properties, function(p) {
if (!p.isMany && p.default !== undefined) {
prototype[p.name] = p.default;
}
});
props.defineModel(prototype, model);
props.defineDescriptor(prototype, descriptor);
var name = descriptor.ns.name;
/**
* The new type constructor
*/
function ModdleElement(attrs) {
props.define(this, '$type', { value: name, enumerable: true });
props.define(this, '$attrs', { value: {} });
props.define(this, '$parent', { writable: true });
forEach$1(attrs, bind(function(val, key) {
this.set(key, val);
}, this));
}
ModdleElement.prototype = prototype;
ModdleElement.hasType = prototype.$instanceOf = this.model.hasType;
// static links
props.defineModel(ModdleElement, model);
props.defineDescriptor(ModdleElement, descriptor);
return ModdleElement;
};
/**
* Built-in moddle types
*/
var BUILTINS = {
String: true,
Boolean: true,
Integer: true,
Real: true,
Element: true
};
/**
* Converters for built in types from string representations
*/
var TYPE_CONVERTERS = {
String: function(s) { return s; },
Boolean: function(s) { return s === 'true'; },
Integer: function(s) { return parseInt(s, 10); },
Real: function(s) { return parseFloat(s); }
};
/**
* Convert a type to its real representation
*/
function coerceType(type, value) {
var converter = TYPE_CONVERTERS[type];
if (converter) {
return converter(value);
} else {
return value;
}
}
/**
* Return whether the given type is built-in
*/
function isBuiltIn(type) {
return !!BUILTINS[type];
}
/**
* Return whether the given type is simple
*/
function isSimple(type) {
return !!TYPE_CONVERTERS[type];
}
/**
* Parses a namespaced attribute name of the form (ns:)localName to an object,
* given a default prefix to assume in case no explicit namespace is given.
*
* @param {String} name
* @param {String} [defaultPrefix] the default prefix to take, if none is present.
*
* @return {Object} the parsed name
*/
function parseName(name, defaultPrefix) {
var parts = name.split(/:/),
localName, prefix;
// no prefix (i.e. only local name)
if (parts.length === 1) {
localName = name;
prefix = defaultPrefix;
} else
// prefix + local name
if (parts.length === 2) {
localName = parts[1];
prefix = parts[0];
} else {
throw new Error('expected <prefix:localName> or <localName>, got ' + name);
}
name = (prefix ? prefix + ':' : '') + localName;
return {
name: name,
prefix: prefix,
localName: localName
};
}
/**
* A utility to build element descriptors.
*/
function DescriptorBuilder(nameNs) {
this.ns = nameNs;
this.name = nameNs.name;
this.allTypes = [];
this.allTypesByName = {};
this.properties = [];
this.propertiesByName = {};
}
DescriptorBuilder.prototype.build = function() {
return pick(this, [
'ns',
'name',
'allTypes',
'allTypesByName',
'properties',
'propertiesByName',
'bodyProperty',
'idProperty'
]);
};
/**
* Add property at given index.
*
* @param {Object} p
* @param {Number} [idx]
* @param {Boolean} [validate=true]
*/
DescriptorBuilder.prototype.addProperty = function(p, idx, validate) {
if (typeof idx === 'boolean') {
validate = idx;
idx = undefined;
}
this.addNamedProperty(p, validate !== false);
var properties = this.properties;
if (idx !== undefined) {
properties.splice(idx, 0, p);
} else {
properties.push(p);
}
};
DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) {
var oldNameNs = oldProperty.ns;
var props = this.properties,
propertiesByName = this.propertiesByName,
rename = oldProperty.name !== newProperty.name;
if (oldProperty.isId) {
if (!newProperty.isId) {
throw new Error(
'property <' + newProperty.ns.name + '> must be id property ' +
'to refine <' + oldProperty.ns.name + '>');
}
this.setIdProperty(newProperty, false);
}
if (oldProperty.isBody) {
if (!newProperty.isBody) {
throw new Error(
'property <' + newProperty.ns.name + '> must be body property ' +
'to refine <' + oldProperty.ns.name + '>');
}
// TODO: Check compatibility
this.setBodyProperty(newProperty, false);
}
// validate existence and get location of old property
var idx = props.indexOf(oldProperty);
if (idx === -1) {
throw new Error('property <' + oldNameNs.name + '> not found in property list');
}
// remove old property
props.splice(idx, 1);
// replacing the named property is intentional
//
// * validate only if this is a "rename" operation
// * add at specific index unless we "replace"
//
this.addProperty(newProperty, replace ? undefined : idx, rename);
// make new property available under old name
propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
};
DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) {
var nsPrefix = p.ns.prefix;
var parts = targetPropertyName.split('#');
var name = parseName(parts[0], nsPrefix);
var attrName = parseName(parts[1], name.prefix).name;
var redefinedProperty = this.propertiesByName[attrName];
if (!redefinedProperty) {
throw new Error('refined property <' + attrName + '> not found');
} else {
this.replaceProperty(redefinedProperty, p, replace);
}
delete p.redefines;
};
DescriptorBuilder.prototype.addNamedProperty = function(p, validate) {
var ns = p.ns,
propsByName = this.propertiesByName;
if (validate) {
this.assertNotDefined(p, ns.name);
this.assertNotDefined(p, ns.localName);
}
propsByName[ns.name] = propsByName[ns.localName] = p;
};
DescriptorBuilder.prototype.removeNamedProperty = function(p) {
var ns = p.ns,
propsByName = this.propertiesByName;
delete propsByName[ns.name];
delete propsByName[ns.localName];
};
DescriptorBuilder.prototype.setBodyProperty = function(p, validate) {
if (validate && this.bodyProperty) {
throw new Error(
'body property defined multiple times ' +
'(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)');
}
this.bodyProperty = p;
};
DescriptorBuilder.prototype.setIdProperty = function(p, validate) {
if (validate && this.idProperty) {
throw new Error(
'id property defined multiple times ' +
'(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
}
this.idProperty = p;
};
DescriptorBuilder.prototype.assertNotDefined = function(p, name) {
var propertyName = p.name,
definedProperty = this.propertiesByName[propertyName];
if (definedProperty) {
throw new Error(
'property <' + propertyName + '> already defined; ' +
'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' +
'<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines');
}
};
DescriptorBuilder.prototype.hasProperty = function(name) {
return this.propertiesByName[name];
};
DescriptorBuilder.prototype.addTrait = function(t, inherited) {
var typesByName = this.allTypesByName,
types = this.allTypes;
var typeName = t.name;
if (typeName in typesByName) {
return;
}
forEach$1(t.properties, bind(function(p) {
// clone property to allow extensions
p = assign({}, p, {
name: p.ns.localName,
inherited: inherited
});
Object.defineProperty(p, 'definedBy', {
value: t
});
var replaces = p.replaces,
redefines = p.redefines;
// add replace/redefine support
if (replaces || redefines) {
this.redefineProperty(p, replaces || redefines, replaces);
} else {
if (p.isBody) {
this.setBodyProperty(p);
}
if (p.isId) {
this.setIdProperty(p);
}
this.addProperty(p);
}
}, this));
types.push(t);
typesByName[typeName] = t;
};
/**
* A registry of Moddle packages.
*
* @param {Array<Package>} packages
* @param {Properties} properties
*/
function Registry(packages, properties) {
this.packageMap = {};
this.typeMap = {};
this.packages = [];
this.properties = properties;
forEach$1(packages, bind(this.registerPackage, this));
}
Registry.prototype.getPackage = function(uriOrPrefix) {
return this.packageMap[uriOrPrefix];
};
Registry.prototype.getPackages = function() {
return this.packages;
};
Registry.prototype.registerPackage = function(pkg) {
// copy package
pkg = assign({}, pkg);
var pkgMap = this.packageMap;
ensureAvailable(pkgMap, pkg, 'prefix');
ensureAvailable(pkgMap, pkg, 'uri');
// register types
forEach$1(pkg.types, bind(function(descriptor) {
this.registerType(descriptor, pkg);
}, this));
pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg;
this.packages.push(pkg);
};
/**
* Register a type from a specific package with us
*/
Registry.prototype.registerType = function(type, pkg) {
type = assign({}, type, {
superClass: (type.superClass || []).slice(),
extends: (type.extends || []).slice(),
properties: (type.properties || []).slice(),
meta: assign((type.meta || {}))
});
var ns = parseName(type.name, pkg.prefix),
name = ns.name,
propertiesByName = {};
// parse properties
forEach$1(type.properties, bind(function(p) {
// namespace property names
var propertyNs = parseName(p.name, ns.prefix),
propertyName = propertyNs.name;
// namespace property types
if (!isBuiltIn(p.type)) {
p.type = parseName(p.type, propertyNs.prefix).name;
}
assign(p, {
ns: propertyNs,
name: propertyName
});
propertiesByName[propertyName] = p;
}, this));
// update ns + name
assign(type, {
ns: ns,
name: name,
propertiesByName: propertiesByName
});
forEach$1(type.extends, bind(function(extendsName) {
var extended = this.typeMap[extendsName];
extended.traits = extended.traits || [];
extended.traits.push(name);
}, this));
// link to package
this.definePackage(type, pkg);
// register
this.typeMap[name] = type;
};
/**
* Traverse the type hierarchy from bottom to top,
* calling iterator with (type, inherited) for all elements in
* the inheritance chain.
*
* @param {Object} nsName
* @param {Function} iterator
* @param {Boolean} [trait=false]
*/
Registry.prototype.mapTypes = function(nsName, iterator, trait) {
var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name];
var self = this;
/**
* Traverse the selected trait.
*
* @param {String} cls
*/
function traverseTrait(cls) {
return traverseSuper(cls, true);
}
/**
* Traverse the selected super type or trait
*
* @param {String} cls
* @param {Boolean} [trait=false]
*/
function traverseSuper(cls, trait) {
var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix);
self.mapTypes(parentNs, iterator, trait);
}
if (!type) {
throw new Error('unknown type <' + nsName.name + '>');
}
forEach$1(type.superClass, trait ? traverseTrait : traverseSuper);
// call iterator with (type, inherited=!trait)
iterator(type, !trait);
forEach$1(type.traits, traverseTrait);
};
/**
* Returns the effective descriptor for a type.
*
* @param {String} type the namespaced name (ns:localName) of the type
*
* @return {Descriptor} the resulting effective descriptor
*/
Registry.prototype.getEffectiveDescriptor = function(name) {
var nsName = parseName(name);
var builder = new DescriptorBuilder(nsName);
this.mapTypes(nsName, function(type, inherited) {
builder.addTrait(type, inherited);
});
var descriptor = builder.build();
// define package link
this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg);
return descriptor;
};
Registry.prototype.definePackage = function(target, pkg) {
this.properties.define(target, '$pkg', { value: pkg });
};
///////// helpers ////////////////////////////
function ensureAvailable(packageMap, pkg, identifierKey) {
var value = pkg[identifierKey];
if (value in packageMap) {
throw new Error('package with ' + identifierKey + ' <' + value + '> already defined');
}
}
/**
* A utility that gets and sets properties of model elements.
*
* @param {Model} model
*/
function Properties(model) {
this.model = model;
}
/**
* Sets a named property on the target element.
* If the value is undefined, the property gets deleted.
*
* @param {Object} target
* @param {String} name
* @param {Object} value
*/
Properties.prototype.set = function(target, name, value) {
var property = this.model.getPropertyDescriptor(target, name);
var propertyName = property && property.name;
if (isUndefined(value)) {
// unset the property, if the specified value is undefined;
// delete from $attrs (for extensions) or the target itself
if (property) {
delete target[propertyName];
} else {
delete target.$attrs[name];
}
} else {
// set the property, defining well defined properties on the fly
// or simply updating them in target.$attrs (for extensions)
if (property) {
if (propertyName in target) {
target[propertyName] = value;
} else {
defineProperty(target, property, value);
}
} else {
target.$attrs[name] = value;
}
}
};
/**
* Returns the named property of the given element
*
* @param {Object} target
* @param {String} name
*
* @return {Object}
*/
Properties.prototype.get = function(target, name) {
var property = this.model.getPropertyDescriptor(target, name);
if (!property) {
return target.$attrs[name];
}
var propertyName = property.name;
// check if access to collection property and lazily initialize it
if (!target[propertyName] && property.isMany) {
defineProperty(target, property, []);
}
return target[propertyName];
};
/**
* Define a property on the target element
*
* @param {Object} target
* @param {String} name
* @param {Object} options
*/
Properties.prototype.define = function(target, name, options) {
if (!options.writable) {
var value = options.value;
// use getters for read-only variables to support ES6 proxies
// cf. https://github.com/bpmn-io/internal-docs/issues/386
options = assign({}, options, {
get: function() { return value; }
});
delete options.value;
}
Object.defineProperty(target, name, options);
};
/**
* Define the descriptor for an element
*/
Properties.prototype.defineDescriptor = function(target, descriptor) {
this.define(target, '$descriptor', { value: descriptor });
};
/**
* Define the model for an element
*/
Properties.prototype.defineModel = function(target, model) {
this.define(target, '$model', { value: model });
};
function isUndefined(val) {
return typeof val === 'undefined';
}
function defineProperty(target, property, value) {
Object.defineProperty(target, property.name, {
enumerable: !property.isReference,
writable: true,
value: value,
configurable: true
});
}
//// Moddle implementation /////////////////////////////////////////////////
/**
* @class Moddle
*
* A model that can be used to create elements of a specific type.
*
* @example
*
* var Moddle = require('moddle');
*
* var pkg = {
* name: 'mypackage',
* prefix: 'my',
* types: [
* { name: 'Root' }
* ]
* };
*
* var moddle = new Moddle([pkg]);
*
* @param {Array<Package>} packages the packages to contain
*/
function Moddle(packages) {
this.properties = new Properties(this);
this.factory = new Factory(this, this.properties);
this.registry = new Registry(packages, this.properties);
this.typeCache = {};
}
/**
* Create an instance of the specified type.
*
* @method Moddle#create
*
* @example
*
* var foo = moddle.create('my:Foo');
* var bar = moddle.create('my:Bar', { id: 'BAR_1' });
*
* @param {String|Object} descriptor the type descriptor or name know to the model
* @param {Object} attrs a number of attributes to initialize the model instance with
* @return {Object} model instance
*/
Moddle.prototype.create = function(descriptor, attrs) {
var Type = this.getType(descriptor);
if (!Type) {
throw new Error('unknown type <' + descriptor + '>');
}
return new Type(attrs);
};
/**
* Returns the type representing a given descriptor
*
* @method Moddle#getType
*
* @example
*
* var Foo = moddle.getType('my:Foo');
* var foo = new Foo({ 'id' : 'FOO_1' });
*
* @param {String|Object} descriptor the type descriptor or name know to the model
* @return {Object} the type representing the descriptor
*/
Moddle.prototype.getType = function(descriptor) {
var cache = this.typeCache;
var name = isString(descriptor) ? descriptor : descriptor.ns.name;
var type = cache[name];
if (!type) {
descriptor = this.registry.getEffectiveDescriptor(name);
type = cache[name] = this.factory.createType(descriptor);
}
return type;
};
/**
* Creates an any-element type to be used within model instances.
*
* This can be used to create custom elements that lie outside the meta-model.
* The created element contains all the meta-data required to serialize it
* as part of meta-model elements.
*
* @method Moddle#createAny
*
* @example
*
* var foo = moddle.createAny('vendor:Foo', 'http://vendor', {
* value: 'bar'
* });
*
* var container = moddle.create('my:Container', 'http://my', {
* any: [ foo ]
* });
*
* // go ahead and serialize the stuff
*
*
* @param {String} name the name of the element
* @param {String} nsUri the namespace uri of the element
* @param {Object} [properties] a map of properties to initialize the instance with
* @return {Object} the any type instance
*/
Moddle.prototype.createAny = function(name, nsUri, properties) {
var nameNs = parseName(name);
var element = {
$type: name,
$instanceOf: function(type) {
return type === this.$type;
}
};
var descriptor = {
name: name,
isGeneric: true,
ns: {
prefix: nameNs.prefix,
localName: nameNs.localName,
uri: nsUri
}
};
this.properties.defineDescriptor(element, descriptor);
this.properties.defineModel(element, this);
this.properties.define(element, '$parent', { enumerable: false, writable: true });
this.properties.define(element, '$instanceOf', { enumerable: false, writable: true });
forEach$1(properties, function(a, key) {
if (isObject(a) && a.value !== undefined) {
element[a.name] = a.value;
} else {
element[key] = a;
}
});
return element;
};
/**
* Returns a registered package by uri or prefix
*
* @return {Object} the package
*/
Moddle.prototype.getPackage = function(uriOrPrefix) {
return this.registry.getPackage(uriOrPrefix);
};
/**
* Returns a snapshot of all known packages
*
* @return {Object} the package
*/
Moddle.prototype.getPackages = function() {
return this.registry.getPackages();
};
/**
* Returns the descriptor for an element
*/
Moddle.prototype.getElementDescriptor = function(element) {
return element.$descriptor;
};
/**
* Returns true if the given descriptor or instance
* represents the given type.
*
* May be applied to this, if element is omitted.
*/
Moddle.prototype.hasType = function(element, type) {
if (type === undefined) {
type = element;
element = this;
}
var descriptor = element.$model.getElementDescriptor(element);
return (type in descriptor.allTypesByName);
};
/**
* Returns the descriptor of an elements named property
*/
Moddle.prototype.getPropertyDescriptor = function(element, property) {
return this.getElementDescriptor(element).propertiesByName[property];
};
/**
* Returns a mapped type's descriptor
*/
Moddle.prototype.getTypeDescriptor = function(type) {
return this.registry.typeMap[type];
};
var fromCharCode = String.fromCharCode;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var ENTITY_PATTERN = /&#(\d+);|&#x([0-9a-f]+);|&(\w+);/ig;
var ENTITY_MAPPING = {
'amp': '&',
'apos': '\'',
'gt': '>',
'lt': '<',
'quot': '"'
};
// map UPPERCASE variants of supported special chars
Object.keys(ENTITY_MAPPING).forEach(function(k) {
ENTITY_MAPPING[k.toUpperCase()] = ENTITY_MAPPING[k];
});
function replaceEntities(_, d, x, z) {
// reserved names, i.e. &nbsp;
if (z) {
if (hasOwnProperty.call(ENTITY_MAPPING, z)) {
return ENTITY_MAPPING[z];
} else {
// fall back to original value
return '&' + z + ';';
}
}
// decimal encoded char
if (d) {
return fromCharCode(d);
}
// hex encoded char
return fromCharCode(parseInt(x, 16));
}
/**
* A basic entity decoder that can decode a minimal
* sub-set of reserved names (&amp;) as well as
* hex (&#xaaf;) and decimal (&#1231;) encoded characters.
*
* @param {string} str
*
* @return {string} decoded string
*/
function decodeEntities(s) {
if (s.length > 3 && s.indexOf('&') !== -1) {
return s.replace(ENTITY_PATTERN, replaceEntities);
}
return s;
}
var XSI_URI = 'http://www.w3.org/2001/XMLSchema-instance';
var XSI_PREFIX = 'xsi';
var XSI_TYPE$1 = 'xsi:type';
var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node';
function error$2(msg) {
return new Error(msg);
}
function missingNamespaceForPrefix(prefix) {
return 'missing namespace for prefix <' + prefix + '>';
}
function getter(getFn) {
return {
'get': getFn,
'enumerable': true
};
}
function cloneNsMatrix(nsMatrix) {
var clone = {}, key;
for (key in nsMatrix) {
clone[key] = nsMatrix[key];
}
return clone;
}
function uriPrefix(prefix) {
return prefix + '$uri';
}
function buildNsMatrix(nsUriToPrefix) {
var nsMatrix = {},
uri,
prefix;
for (uri in nsUriToPrefix) {
prefix = nsUriToPrefix[uri];
nsMatrix[prefix] = prefix;
nsMatrix[uriPrefix(prefix)] = uri;
}
return nsMatrix;
}
function noopGetContext() {
return { 'line': 0, 'column': 0 };
}
function throwFunc(err) {
throw err;
}
/**
* Creates a new parser with the given options.
*
* @constructor
*
* @param {!Object<string, ?>=} options
*/
function Parser(options) {
if (!this) {
return new Parser(options);
}
var proxy = options && options['proxy'];
var onText,
onOpenTag,
onCloseTag,
onCDATA,
onError = throwFunc,
onWarning,
onComment,
onQuestion,
onAttention;
var getContext = noopGetContext;
/**
* Do we need to parse the current elements attributes for namespaces?
*
* @type {boolean}
*/
var maybeNS = false;
/**
* Do we process namespaces at all?
*
* @type {boolean}
*/
var isNamespace = false;
/**
* The caught error returned on parse end
*
* @type {Error}
*/
var returnError = null;
/**
* Should we stop parsing?
*
* @type {boolean}
*/
var parseStop = false;
/**
* A map of { uri: prefix } used by the parser.
*
* This map will ensure we can normalize prefixes during processing;
* for each uri, only one prefix will be exposed to the handlers.
*
* @type {!Object<string, string>}}
*/
var nsUriToPrefix;
/**
* Handle parse error.
*
* @param {string|Error} err
*/
function handleError(err) {
if (!(err instanceof Error)) {
err = error$2(err);
}
returnError = err;
onError(err, getContext);
}
/**
* Handle parse error.
*
* @param {string|Error} err
*/
function handleWarning(err) {
if (!onWarning) {
return;
}
if (!(err instanceof Error)) {
err = error$2(err);
}
onWarning(err, getContext);
}
/**
* Register parse listener.
*
* @param {string} name
* @param {Function} cb
*
* @return {Parser}
*/
this['on'] = function(name, cb) {
if (typeof cb !== 'function') {
throw error$2('required args <name, cb>');
}
switch (name) {
case 'openTag': onOpenTag = cb; break;
case 'text': onText = cb; break;
case 'closeTag': onCloseTag = cb; break;
case 'error': onError = cb; break;
case 'warn': onWarning = cb; break;
case 'cdata': onCDATA = cb; break;
case 'attention': onAttention = cb; break; // <!XXXXX zzzz="eeee">
case 'question': onQuestion = cb; break; // <? .... ?>
case 'comment': onComment = cb; break;
default:
throw error$2('unsupported event: ' + name);
}
return this;
};
/**
* Set the namespace to prefix mapping.
*
* @example
*
* parser.ns({
* 'http://foo': 'foo',
* 'http://bar': 'bar'
* });
*
* @param {!Object<string, string>} nsMap
*
* @return {Parser}
*/
this['ns'] = function(nsMap) {
if (typeof nsMap === 'undefined') {
nsMap = {};
}
if (typeof nsMap !== 'object') {
throw error$2('required args <nsMap={}>');
}
var _nsUriToPrefix = {}, k;
for (k in nsMap) {
_nsUriToPrefix[k] = nsMap[k];
}
// FORCE default mapping for schema instance
_nsUriToPrefix[XSI_URI] = XSI_PREFIX;
isNamespace = true;
nsUriToPrefix = _nsUriToPrefix;
return this;
};
/**
* Parse xml string.
*
* @param {string} xml
*
* @return {Error} returnError, if not thrown
*/
this['parse'] = function(xml) {
if (typeof xml !== 'string') {
throw error$2('required args <xml=string>');
}
returnError = null;
parse(xml);
getContext = noopGetContext;
parseStop = false;
return returnError;
};
/**
* Stop parsing.
*/
this['stop'] = function() {
parseStop = true;
};
/**
* Parse string, invoking configured listeners on element.
*
* @param {string} xml
*/
function parse(xml) {
var nsMatrixStack = isNamespace ? [] : null,
nsMatrix = isNamespace ? buildNsMatrix(nsUriToPrefix) : null,
_nsMatrix,
nodeStack = [],
anonymousNsCount = 0,
tagStart = false,
tagEnd = false,
i = 0, j = 0,
x, y, q, w, v,
xmlns,
elementName,
_elementName,
elementProxy
;
var attrsString = '',
attrsStart = 0,
cachedAttrs // false = parsed with errors, null = needs parsing
;
/**
* Parse attributes on demand and returns the parsed attributes.
*
* Return semantics: (1) `false` on attribute parse error,
* (2) object hash on extracted attrs.
*
* @return {boolean|Object}
*/
function getAttrs() {
if (cachedAttrs !== null) {
return cachedAttrs;
}
var nsUri,
nsUriPrefix,
nsName,
defaultAlias = isNamespace && nsMatrix['xmlns'],
attrList = isNamespace && maybeNS ? [] : null,
i = attrsStart,
s = attrsString,
l = s.length,
hasNewMatrix,
newalias,
value,
alias,
name,
attrs = {},
seenAttrs = {},
skipAttr,
w,
j;
parseAttr:
for (; i < l; i++) {
skipAttr = false;
w = s.charCodeAt(i);
if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE={ \f\n\r\t\v}
continue;
}
// wait for non whitespace character
if (w < 65 || w > 122 || (w > 90 && w < 97)) {
if (w !== 95 && w !== 58) { // char 95"_" 58":"
handleWarning('illegal first char attribute name');
skipAttr = true;
}
}
// parse attribute name
for (j = i + 1; j < l; j++) {
w = s.charCodeAt(j);
if (
w > 96 && w < 123 ||
w > 64 && w < 91 ||
w > 47 && w < 59 ||
w === 46 || // '.'
w === 45 || // '-'
w === 95 // '_'
) {
continue;
}
// unexpected whitespace
if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
handleWarning('missing attribute value');
i = j;
continue parseAttr;
}
// expected "="
if (w === 61) { // "=" == 61
break;
}
handleWarning('illegal attribute name char');
skipAttr = true;
}
name = s.substring(i, j);
if (name === 'xmlns:xmlns') {
handleWarning('illegal declaration of xmlns');
skipAttr = true;
}
w = s.charCodeAt(j + 1);
if (w === 34) { // '"'
j = s.indexOf('"', i = j + 2);
if (j === -1) {
j = s.indexOf('\'', i);
if (j !== -1) {
handleWarning('attribute value quote missmatch');
skipAttr = true;
}
}
} else if (w === 39) { // "'"
j = s.indexOf('\'', i = j + 2);
if (j === -1) {
j = s.indexOf('"', i);
if (j !== -1) {
handleWarning('attribute value quote missmatch');
skipAttr = true;
}
}
} else {
handleWarning('missing attribute value quotes');
skipAttr = true;
// skip to next space
for (j = j + 1; j < l; j++) {
w = s.charCodeAt(j + 1);
if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
break;
}
}
}
if (j === -1) {
handleWarning('missing closing quotes');
j = l;
skipAttr = true;
}
if (!skipAttr) {
value = s.substring(i, j);
}
i = j;
// ensure SPACE follows attribute
// skip illegal content otherwise
// example a="b"c
for (; j + 1 < l; j++) {
w = s.charCodeAt(j + 1);
if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE
break;
}
// FIRST ILLEGAL CHAR
if (i === j) {
handleWarning('illegal character after attribute end');
skipAttr = true;
}
}
// advance cursor to next attribute
i = j + 1;
if (skipAttr) {
continue parseAttr;
}
// check attribute re-declaration
if (name in seenAttrs) {
handleWarning('attribute <' + name + '> already defined');
continue;
}
seenAttrs[name] = true;
if (!isNamespace) {
attrs[name] = value;
continue;
}
// try to extract namespace information
if (maybeNS) {
newalias = (
name === 'xmlns'
? 'xmlns'
: (name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:')
? name.substr(6)
: null
);
// handle xmlns(:alias) assignment
if (newalias !== null) {
nsUri = decodeEntities(value);
nsUriPrefix = uriPrefix(newalias);
alias = nsUriToPrefix[nsUri];
if (!alias) {
// no prefix defined or prefix collision
if (
(newalias === 'xmlns') ||
(nsUriPrefix in nsMatrix && nsMatrix[nsUriPrefix] !== nsUri)
) {
// alocate free ns prefix
do {
alias = 'ns' + (anonymousNsCount++);
} while (typeof nsMatrix[alias] !== 'undefined');
} else {
alias = newalias;
}
nsUriToPrefix[nsUri] = alias;
}
if (nsMatrix[newalias] !== alias) {
if (!hasNewMatrix) {
nsMatrix = cloneNsMatrix(nsMatrix);
hasNewMatrix = true;
}
nsMatrix[newalias] = alias;
if (newalias === 'xmlns') {
nsMatrix[uriPrefix(alias)] = nsUri;
defaultAlias = alias;
}
nsMatrix[nsUriPrefix] = nsUri;
}
// expose xmlns(:asd)="..." in attributes
attrs[name] = value;
continue;
}
// collect attributes until all namespace
// declarations are processed
attrList.push(name, value);
continue;
} /** end if (maybeNs) */
// handle attributes on element without
// namespace declarations
w = name.indexOf(':');
if (w === -1) {
attrs[name] = value;
continue;
}
// normalize ns attribute name
if (!(nsName = nsMatrix[name.substring(0, w)])) {
handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
continue;
}
name = defaultAlias === nsName
? name.substr(w + 1)
: nsName + name.substr(w);
// end: normalize ns attribute name
// normalize xsi:type ns attribute value
if (name === XSI_TYPE$1) {
w = value.indexOf(':');
if (w !== -1) {
nsName = value.substring(0, w);
// handle default prefixes, i.e. xs:String gracefully
nsName = nsMatrix[nsName] || nsName;
value = nsName + value.substring(w);
} else {
value = defaultAlias + ':' + value;
}
}
// end: normalize xsi:type ns attribute value
attrs[name] = value;
}
// handle deferred, possibly namespaced attributes
if (maybeNS) {
// normalize captured attributes
for (i = 0, l = attrList.length; i < l; i++) {
name = attrList[i++];
value = attrList[i];
w = name.indexOf(':');
if (w !== -1) {
// normalize ns attribute name
if (!(nsName = nsMatrix[name.substring(0, w)])) {
handleWarning(missingNamespaceForPrefix(name.substring(0, w)));
continue;
}
name = defaultAlias === nsName
? name.substr(w + 1)
: nsName + name.substr(w);
// end: normalize ns attribute name
// normalize xsi:type ns attribute value
if (name === XSI_TYPE$1) {
w = value.indexOf(':');
if (w !== -1) {
nsName = value.substring(0, w);
// handle default prefixes, i.e. xs:String gracefully
nsName = nsMatrix[nsName] || nsName;
value = nsName + value.substring(w);
} else {
value = defaultAlias + ':' + value;
}
}
// end: normalize xsi:type ns attribute value
}
attrs[name] = value;
}
// end: normalize captured attributes
}
return cachedAttrs = attrs;
}
/**
* Extract the parse context { line, column, part }
* from the current parser position.
*
* @return {Object} parse context
*/
function getParseContext() {
var splitsRe = /(\r\n|\r|\n)/g;
var line = 0;
var column = 0;
var startOfLine = 0;
var endOfLine = j;
var match;
var data;
while (i >= startOfLine) {
match = splitsRe.exec(xml);
if (!match) {
break;
}
// end of line = (break idx + break chars)
endOfLine = match[0].length + match.index;
if (endOfLine > i) {
break;
}
// advance to next line
line += 1;
startOfLine = endOfLine;
}
// EOF errors
if (i == -1) {
column = endOfLine;
data = xml.substring(j);
} else
// start errors
if (j === 0) {
data = xml.substring(j, i);
}
// other errors
else {
column = i - startOfLine;
data = (j == -1 ? xml.substring(i) : xml.substring(i, j + 1));
}
return {
'data': data,
'line': line,
'column': column
};
}
getContext = getParseContext;
if (proxy) {
elementProxy = Object.create({}, {
'name': getter(function() {
return elementName;
}),
'originalName': getter(function() {
return _elementName;
}),
'attrs': getter(getAttrs),
'ns': getter(function() {
return nsMatrix;
})
});
}
// actual parse logic
while (j !== -1) {
if (xml.charCodeAt(j) === 60) { // "<"
i = j;
} else {
i = xml.indexOf('<', j);
}
// parse end
if (i === -1) {
if (nodeStack.length) {
return handleError('unexpected end of file');
}
if (j === 0) {
return handleError('missing start tag');
}
if (j < xml.length) {
if (xml.substring(j).trim()) {
handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
}
}
return;
}
// parse text
if (j !== i) {
if (nodeStack.length) {
if (onText) {
onText(xml.substring(j, i), decodeEntities, getContext);
if (parseStop) {
return;
}
}
} else {
if (xml.substring(j, i).trim()) {
handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE);
if (parseStop) {
return;
}
}
}
}
w = xml.charCodeAt(i+1);
// parse comments + CDATA
if (w === 33) { // "!"
q = xml.charCodeAt(i+2);
// CDATA section
if (q === 91 && xml.substr(i + 3, 6) === 'CDATA[') { // 91 == "["
j = xml.indexOf(']]>', i);
if (j === -1) {
return handleError('unclosed cdata');
}
if (onCDATA) {
onCDATA(xml.substring(i + 9, j), getContext);
if (parseStop) {
return;
}
}
j += 3;
continue;
}
// comment
if (q === 45 && xml.charCodeAt(i + 3) === 45) { // 45 == "-"
j = xml.indexOf('-->', i);
if (j === -1) {
return handleError('unclosed comment');
}
if (onComment) {
onComment(xml.substring(i + 4, j), decodeEntities, getContext);
if (parseStop) {
return;
}
}
j += 3;
continue;
}
}
// parse question <? ... ?>
if (w === 63) { // "?"
j = xml.indexOf('?>', i);
if (j === -1) {
return handleError('unclosed question');
}
if (onQuestion) {
onQuestion(xml.substring(i, j + 2), getContext);
if (parseStop) {
return;
}
}
j += 2;
continue;
}
// find matching closing tag for attention or standard tags
// for that we must skip through attribute values
// (enclosed in single or double quotes)
for (x = i + 1; ; x++) {
v = xml.charCodeAt(x);
if (isNaN(v)) {
j = -1;
return handleError('unclosed tag');
}
// [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'"
// skips the quoted string
// (double quotes) does not appear in a literal enclosed by (double quotes)
// (single quote) does not appear in a literal enclosed by (single quote)
if (v === 34) { // '"'
q = xml.indexOf('"', x + 1);
x = q !== -1 ? q : x;
} else if (v === 39) { // "'"
q = xml.indexOf("'", x + 1);
x = q !== -1 ? q : x;
} else if (v === 62) { // '>'
j = x;
break;
}
}
// parse attention <! ...>
// previously comment and CDATA have already been parsed
if (w === 33) { // "!"
if (onAttention) {
onAttention(xml.substring(i, j + 1), decodeEntities, getContext);
if (parseStop) {
return;
}
}
j += 1;
continue;
}
// don't process attributes;
// there are none
cachedAttrs = {};
// if (xml.charCodeAt(i+1) === 47) { // </...
if (w === 47) { // </...
tagStart = false;
tagEnd = true;
if (!nodeStack.length) {
return handleError('missing open tag');
}
// verify open <-> close tag match
x = elementName = nodeStack.pop();
q = i + 2 + x.length;
if (xml.substring(i + 2, q) !== x) {
return handleError('closing tag mismatch');
}
// verify chars in close tag
for (; q < j; q++) {
w = xml.charCodeAt(q);
if (w === 32 || (w > 8 && w < 14)) { // \f\n\r\t\v space
continue;
}
return handleError('close tag');
}
} else {
if (xml.charCodeAt(j - 1) === 47) { // .../>
x = elementName = xml.substring(i + 1, j - 1);
tagStart = true;
tagEnd = true;
} else {
x = elementName = xml.substring(i + 1, j);
tagStart = true;
tagEnd = false;
}
if (!(w > 96 && w < 123 || w > 64 && w < 91 || w === 95 || w === 58)) { // char 95"_" 58":"
return handleError('illegal first char nodeName');
}
for (q = 1, y = x.length; q < y; q++) {
w = x.charCodeAt(q);
if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w == 46) {
continue;
}
if (w === 32 || (w < 14 && w > 8)) { // \f\n\r\t\v space
elementName = x.substring(0, q);
// maybe there are attributes
cachedAttrs = null;
break;
}
return handleError('invalid nodeName');
}
if (!tagEnd) {
nodeStack.push(elementName);
}
}
if (isNamespace) {
_nsMatrix = nsMatrix;
if (tagStart) {
// remember old namespace
// unless we're self-closing
if (!tagEnd) {
nsMatrixStack.push(_nsMatrix);
}
if (cachedAttrs === null) {
// quick check, whether there may be namespace
// declarations on the node; if that is the case
// we need to eagerly parse the node attributes
if ((maybeNS = x.indexOf('xmlns', q) !== -1)) {
attrsStart = q;
attrsString = x;
getAttrs();
maybeNS = false;
}
}
}
_elementName = elementName;
w = elementName.indexOf(':');
if (w !== -1) {
xmlns = nsMatrix[elementName.substring(0, w)];
// prefix given; namespace must exist
if (!xmlns) {
return handleError('missing namespace on <' + _elementName + '>');
}
elementName = elementName.substr(w + 1);
} else {
xmlns = nsMatrix['xmlns'];
// if no default namespace is defined,
// we'll import the element as anonymous.
//
// it is up to users to correct that to the document defined
// targetNamespace, or whatever their undersanding of the
// XML spec mandates.
}
// adjust namespace prefixs as configured
if (xmlns) {
elementName = xmlns + ':' + elementName;
}
}
if (tagStart) {
attrsStart = q;
attrsString = x;
if (onOpenTag) {
if (proxy) {
onOpenTag(elementProxy, decodeEntities, tagEnd, getContext);
} else {
onOpenTag(elementName, getAttrs, decodeEntities, tagEnd, getContext);
}
if (parseStop) {
return;
}
}
}
if (tagEnd) {
if (onCloseTag) {
onCloseTag(proxy ? elementProxy : elementName, decodeEntities, tagStart, getContext);
if (parseStop) {
return;
}
}
// restore old namespace
if (isNamespace) {
if (!tagStart) {
nsMatrix = nsMatrixStack.pop();
} else {
nsMatrix = _nsMatrix;
}
}
}
j += 1;
}
} /** end parse */
}
function hasLowerCaseAlias(pkg) {
return pkg.xml && pkg.xml.tagAlias === 'lowerCase';
}
var DEFAULT_NS_MAP = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xml': 'http://www.w3.org/XML/1998/namespace'
};
var XSI_TYPE = 'xsi:type';
function serializeFormat(element) {
return element.xml && element.xml.serialize;
}
function serializeAsType(element) {
return serializeFormat(element) === XSI_TYPE;
}
function serializeAsProperty(element) {
return serializeFormat(element) === 'property';
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function aliasToName(aliasNs, pkg) {
if (!hasLowerCaseAlias(pkg)) {
return aliasNs.name;
}
return aliasNs.prefix + ':' + capitalize(aliasNs.localName);
}
function prefixedToName(nameNs, pkg) {
var name = nameNs.name,
localName = nameNs.localName;
var typePrefix = pkg.xml && pkg.xml.typePrefix;
if (typePrefix && localName.indexOf(typePrefix) === 0) {
return nameNs.prefix + ':' + localName.slice(typePrefix.length);
} else {
return name;
}
}
function normalizeXsiTypeName(name, model) {
var nameNs = parseName(name);
var pkg = model.getPackage(nameNs.prefix);
return prefixedToName(nameNs, pkg);
}
function error$1(message) {
return new Error(message);
}
/**
* Get the moddle descriptor for a given instance or type.
*
* @param {ModdleElement|Function} element
*
* @return {Object} the moddle descriptor
*/
function getModdleDescriptor(element) {
return element.$descriptor;
}
/**
* A parse context.
*
* @class
*
* @param {Object} options
* @param {ElementHandler} options.rootHandler the root handler for parsing a document
* @param {boolean} [options.lax=false] whether or not to ignore invalid elements
*/
function Context(options) {
/**
* @property {ElementHandler} rootHandler
*/
/**
* @property {Boolean} lax
*/
assign(this, options);
this.elementsById = {};
this.references = [];
this.warnings = [];
/**
* Add an unresolved reference.
*
* @param {Object} reference
*/
this.addReference = function(reference) {
this.references.push(reference);
};
/**
* Add a processed element.
*
* @param {ModdleElement} element
*/
this.addElement = function(element) {
if (!element) {
throw error$1('expected element');
}
var elementsById = this.elementsById;
var descriptor = getModdleDescriptor(element);
var idProperty = descriptor.idProperty,
id;
if (idProperty) {
id = element.get(idProperty.name);
if (id) {
// for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) {
throw new Error('illegal ID <' + id + '>');
}
if (elementsById[id]) {
throw error$1('duplicate ID <' + id + '>');
}
elementsById[id] = element;
}
}
};
/**
* Add an import warning.
*
* @param {Object} warning
* @param {String} warning.message
* @param {Error} [warning.error]
*/
this.addWarning = function(warning) {
this.warnings.push(warning);
};
}
function BaseHandler() {}
BaseHandler.prototype.handleEnd = function() {};
BaseHandler.prototype.handleText = function() {};
BaseHandler.prototype.handleNode = function() {};
/**
* A simple pass through handler that does nothing except for
* ignoring all input it receives.
*
* This is used to ignore unknown elements and
* attributes.
*/
function NoopHandler() { }
NoopHandler.prototype = Object.create(BaseHandler.prototype);
NoopHandler.prototype.handleNode = function() {
return this;
};
function BodyHandler() {}
BodyHandler.prototype = Object.create(BaseHandler.prototype);
BodyHandler.prototype.handleText = function(text) {
this.body = (this.body || '') + text;
};
function ReferenceHandler(property, context) {
this.property = property;
this.context = context;
}
ReferenceHandler.prototype = Object.create(BodyHandler.prototype);
ReferenceHandler.prototype.handleNode = function(node) {
if (this.element) {
throw error$1('expected no sub nodes');
} else {
this.element = this.createReference(node);
}
return this;
};
ReferenceHandler.prototype.handleEnd = function() {
this.element.id = this.body;
};
ReferenceHandler.prototype.createReference = function(node) {
return {
property: this.property.ns.name,
id: ''
};
};
function ValueHandler(propertyDesc, element) {
this.element = element;
this.propertyDesc = propertyDesc;
}
ValueHandler.prototype = Object.create(BodyHandler.prototype);
ValueHandler.prototype.handleEnd = function() {
var value = this.body || '',
element = this.element,
propertyDesc = this.propertyDesc;
value = coerceType(propertyDesc.type, value);
if (propertyDesc.isMany) {
element.get(propertyDesc.name).push(value);
} else {
element.set(propertyDesc.name, value);
}
};
function BaseElementHandler() {}
BaseElementHandler.prototype = Object.create(BodyHandler.prototype);
BaseElementHandler.prototype.handleNode = function(node) {
var parser = this,
element = this.element;
if (!element) {
element = this.element = this.createElement(node);
this.context.addElement(element);
} else {
parser = this.handleChild(node);
}
return parser;
};
/**
* @class Reader.ElementHandler
*
*/
function ElementHandler(model, typeName, context) {
this.model = model;
this.type = model.getType(typeName);
this.context = context;
}
ElementHandler.prototype = Object.create(BaseElementHandler.prototype);
ElementHandler.prototype.addReference = function(reference) {
this.context.addReference(reference);
};
ElementHandler.prototype.handleText = function(text) {
var element = this.element,
descriptor = getModdleDescriptor(element),
bodyProperty = descriptor.bodyProperty;
if (!bodyProperty) {
throw error$1('unexpected body text <' + text + '>');
}
BodyHandler.prototype.handleText.call(this, text);
};
ElementHandler.prototype.handleEnd = function() {
var value = this.body,
element = this.element,
descriptor = getModdleDescriptor(element),
bodyProperty = descriptor.bodyProperty;
if (bodyProperty && value !== undefined) {
value = coerceType(bodyProperty.type, value);
element.set(bodyProperty.name, value);
}
};
/**
* Create an instance of the model from the given node.
*
* @param {Element} node the xml node
*/
ElementHandler.prototype.createElement = function(node) {
var attributes = node.attributes,
Type = this.type,
descriptor = getModdleDescriptor(Type),
context = this.context,
instance = new Type({}),
model = this.model,
propNameNs;
forEach$1(attributes, function(value, name) {
var prop = descriptor.propertiesByName[name],
values;
if (prop && prop.isReference) {
if (!prop.isMany) {
context.addReference({
element: instance,
property: prop.ns.name,
id: value
});
} else {
// IDREFS: parse references as whitespace-separated list
values = value.split(' ');
forEach$1(values, function(v) {
context.addReference({
element: instance,
property: prop.ns.name,
id: v
});
});
}
} else {
if (prop) {
value = coerceType(prop.type, value);
} else
if (name !== 'xmlns') {
propNameNs = parseName(name, descriptor.ns.prefix);
// check whether attribute is defined in a well-known namespace
// if that is the case we emit a warning to indicate potential misuse
if (model.getPackage(propNameNs.prefix)) {
context.addWarning({
message: 'unknown attribute <' + name + '>',
element: instance,
property: name,
value: value
});
}
}
instance.set(name, value);
}
});
return instance;
};
ElementHandler.prototype.getPropertyForNode = function(node) {
var name = node.name;
var nameNs = parseName(name);
var type = this.type,
model = this.model,
descriptor = getModdleDescriptor(type);
var propertyName = nameNs.name,
property = descriptor.propertiesByName[propertyName],
elementTypeName,
elementType;
// search for properties by name first
if (property && !property.isAttr) {
if (serializeAsType(property)) {
elementTypeName = node.attributes[XSI_TYPE];
// xsi type is optional, if it does not exists the
// default type is assumed
if (elementTypeName) {
// take possible type prefixes from XML
// into account, i.e.: xsi:type="t{ActualType}"
elementTypeName = normalizeXsiTypeName(elementTypeName, model);
elementType = model.getType(elementTypeName);
return assign({}, property, {
effectiveType: getModdleDescriptor(elementType).name
});
}
}
// search for properties by name first
return property;
}
var pkg = model.getPackage(nameNs.prefix);
if (pkg) {
elementTypeName = aliasToName(nameNs, pkg);
elementType = model.getType(elementTypeName);
// search for collection members later
property = find(descriptor.properties, function(p) {
return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type);
});
if (property) {
return assign({}, property, {
effectiveType: getModdleDescriptor(elementType).name
});
}
} else {
// parse unknown element (maybe extension)
property = find(descriptor.properties, function(p) {
return !p.isReference && !p.isAttribute && p.type === 'Element';
});
if (property) {
return property;
}
}
throw error$1('unrecognized element <' + nameNs.name + '>');
};
ElementHandler.prototype.toString = function() {
return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']';
};
ElementHandler.prototype.valueHandler = function(propertyDesc, element) {
return new ValueHandler(propertyDesc, element);
};
ElementHandler.prototype.referenceHandler = function(propertyDesc) {
return new ReferenceHandler(propertyDesc, this.context);
};
ElementHandler.prototype.handler = function(type) {
if (type === 'Element') {
return new GenericElementHandler(this.model, type, this.context);
} else {
return new ElementHandler(this.model, type, this.context);
}
};
/**
* Handle the child element parsing
*
* @param {Element} node the xml node
*/
ElementHandler.prototype.handleChild = function(node) {
var propertyDesc, type, element, childHandler;
propertyDesc = this.getPropertyForNode(node);
element = this.element;
type = propertyDesc.effectiveType || propertyDesc.type;
if (isSimple(type)) {
return this.valueHandler(propertyDesc, element);
}
if (propertyDesc.isReference) {
childHandler = this.referenceHandler(propertyDesc).handleNode(node);
} else {
childHandler = this.handler(type).handleNode(node);
}
var newElement = childHandler.element;
// child handles may decide to skip elements
// by not returning anything
if (newElement !== undefined) {
if (propertyDesc.isMany) {
element.get(propertyDesc.name).push(newElement);
} else {
element.set(propertyDesc.name, newElement);
}
if (propertyDesc.isReference) {
assign(newElement, {
element: element
});
this.context.addReference(newElement);
} else {
// establish child -> parent relationship
newElement.$parent = element;
}
}
return childHandler;
};
/**
* An element handler that performs special validation
* to ensure the node it gets initialized with matches
* the handlers type (namespace wise).
*
* @param {Moddle} model
* @param {String} typeName
* @param {Context} context
*/
function RootElementHandler(model, typeName, context) {
ElementHandler.call(this, model, typeName, context);
}
RootElementHandler.prototype = Object.create(ElementHandler.prototype);
RootElementHandler.prototype.createElement = function(node) {
var name = node.name,
nameNs = parseName(name),
model = this.model,
type = this.type,
pkg = model.getPackage(nameNs.prefix),
typeName = pkg && aliasToName(nameNs, pkg) || name;
// verify the correct namespace if we parse
// the first element in the handler tree
//
// this ensures we don't mistakenly import wrong namespace elements
if (!type.hasType(typeName)) {
throw error$1('unexpected element <' + node.originalName + '>');
}
return ElementHandler.prototype.createElement.call(this, node);
};
function GenericElementHandler(model, typeName, context) {
this.model = model;
this.context = context;
}
GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype);
GenericElementHandler.prototype.createElement = function(node) {
var name = node.name,
ns = parseName(name),
prefix = ns.prefix,
uri = node.ns[prefix + '$uri'],
attributes = node.attributes;
return this.model.createAny(name, uri, attributes);
};
GenericElementHandler.prototype.handleChild = function(node) {
var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node),
element = this.element;
var newElement = handler.element,
children;
if (newElement !== undefined) {
children = element.$children = element.$children || [];
children.push(newElement);
// establish child -> parent relationship
newElement.$parent = element;
}
return handler;
};
GenericElementHandler.prototype.handleEnd = function() {
if (this.body) {
this.element.$body = this.body;
}
};
/**
* A reader for a meta-model
*
* @param {Object} options
* @param {Model} options.model used to read xml files
* @param {Boolean} options.lax whether to make parse errors warnings
*/
function Reader(options) {
if (options instanceof Moddle) {
options = {
model: options
};
}
assign(this, { lax: false }, options);
}
/**
* The fromXML result.
*
* @typedef {Object} ParseResult
*
* @property {ModdleElement} rootElement
* @property {Array<Object>} references
* @property {Array<Error>} warnings
* @property {Object} elementsById - a mapping containing each ID -> ModdleElement
*/
/**
* The fromXML result.
*
* @typedef {Error} ParseError
*
* @property {Array<Error>} warnings
*/
/**
* Parse the given XML into a moddle document tree.
*
* @param {String} xml
* @param {ElementHandler|Object} options or rootHandler
*
* @returns {Promise<ParseResult, ParseError>}
*/
Reader.prototype.fromXML = function(xml, options, done) {
var rootHandler = options.rootHandler;
if (options instanceof ElementHandler) {
// root handler passed via (xml, { rootHandler: ElementHandler }, ...)
rootHandler = options;
options = {};
} else {
if (typeof options === 'string') {
// rootHandler passed via (xml, 'someString', ...)
rootHandler = this.handler(options);
options = {};
} else if (typeof rootHandler === 'string') {
// rootHandler passed via (xml, { rootHandler: 'someString' }, ...)
rootHandler = this.handler(rootHandler);
}
}
var model = this.model,
lax = this.lax;
var context = new Context(assign({}, options, { rootHandler: rootHandler })),
parser = new Parser({ proxy: true }),
stack = createStack();
rootHandler.context = context;
// push root handler
stack.push(rootHandler);
/**
* Handle error.
*
* @param {Error} err
* @param {Function} getContext
* @param {boolean} lax
*
* @return {boolean} true if handled
*/
function handleError(err, getContext, lax) {
var ctx = getContext();
var line = ctx.line,
column = ctx.column,
data = ctx.data;
// we receive the full context data here,
// for elements trim down the information
// to the tag name, only
if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) {
data = data.slice(0, data.indexOf(' ')) + '>';
}
var message =
'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' +
'line: ' + line + '\n\t' +
'column: ' + column + '\n\t' +
'nested error: ' + err.message;
if (lax) {
context.addWarning({
message: message,
error: err
});
return true;
} else {
throw error$1(message);
}
}
function handleWarning(err, getContext) {
// just like handling errors in <lax=true> mode
return handleError(err, getContext, true);
}
/**
* Resolve collected references on parse end.
*/
function resolveReferences() {
var elementsById = context.elementsById;
var references = context.references;
var i, r;
for (i = 0; (r = references[i]); i++) {
var element = r.element;
var reference = elementsById[r.id];
var property = getModdleDescriptor(element).propertiesByName[r.property];
if (!reference) {
context.addWarning({
message: 'unresolved reference <' + r.id + '>',
element: r.element,
property: r.property,
value: r.id
});
}
if (property.isMany) {
var collection = element.get(property.name),
idx = collection.indexOf(r);
// we replace an existing place holder (idx != -1) or
// append to the collection instead
if (idx === -1) {
idx = collection.length;
}
if (!reference) {
// remove unresolvable reference
collection.splice(idx, 1);
} else {
// add or update reference in collection
collection[idx] = reference;
}
} else {
element.set(property.name, reference);
}
}
}
function handleClose() {
stack.pop().handleEnd();
}
var PREAMBLE_START_PATTERN = /^<\?xml /i;
var ENCODING_PATTERN = / encoding="([^"]+)"/i;
var UTF_8_PATTERN = /^utf-8$/i;
function handleQuestion(question) {
if (!PREAMBLE_START_PATTERN.test(question)) {
return;
}
var match = ENCODING_PATTERN.exec(question);
var encoding = match && match[1];
if (!encoding || UTF_8_PATTERN.test(encoding)) {
return;
}
context.addWarning({
message:
'unsupported document encoding <' + encoding + '>, ' +
'falling back to UTF-8'
});
}
function handleOpen(node, getContext) {
var handler = stack.peek();
try {
stack.push(handler.handleNode(node));
} catch (err) {
if (handleError(err, getContext, lax)) {
stack.push(new NoopHandler());
}
}
}
function handleCData(text, getContext) {
try {
stack.peek().handleText(text);
} catch (err) {
handleWarning(err, getContext);
}
}
function handleText(text, getContext) {
// strip whitespace only nodes, i.e. before
// <!CDATA[ ... ]> sections and in between tags
if (!text.trim()) {
return;
}
handleCData(text, getContext);
}
var uriMap = model.getPackages().reduce(function(uriMap, p) {
uriMap[p.uri] = p.prefix;
return uriMap;
}, {
'http://www.w3.org/XML/1998/namespace': 'xml' // add default xml ns
});
parser
.ns(uriMap)
.on('openTag', function(obj, decodeStr, selfClosing, getContext) {
// gracefully handle unparsable attributes (attrs=false)
var attrs = obj.attrs || {};
var decodedAttrs = Object.keys(attrs).reduce(function(d, key) {
var value = decodeStr(attrs[key]);
d[key] = value;
return d;
}, {});
var node = {
name: obj.name,
originalName: obj.originalName,
attributes: decodedAttrs,
ns: obj.ns
};
handleOpen(node, getContext);
})
.on('question', handleQuestion)
.on('closeTag', handleClose)
.on('cdata', handleCData)
.on('text', function(text, decodeEntities, getContext) {
handleText(decodeEntities(text), getContext);
})
.on('error', handleError)
.on('warn', handleWarning);
// async XML parsing to make sure the execution environment
// (node or brower) is kept responsive and that certain optimization
// strategies can kick in.
return new Promise(function(resolve, reject) {
var err;
try {
parser.parse(xml);
resolveReferences();
} catch (e) {
err = e;
}
var rootElement = rootHandler.element;
if (!err && !rootElement) {
err = error$1('failed to parse document as <' + rootHandler.type.$descriptor.name + '>');
}
var warnings = context.warnings;
var references = context.references;
var elementsById = context.elementsById;
if (err) {
err.warnings = warnings;
return reject(err);
} else {
return resolve({
rootElement: rootElement,
elementsById: elementsById,
references: references,
warnings: warnings
});
}
});
};
Reader.prototype.handler = function(name) {
return new RootElementHandler(this.model, name);
};
// helpers //////////////////////////
function createStack() {
var stack = [];
Object.defineProperty(stack, 'peek', {
value: function() {
return this[this.length - 1];
}
});
return stack;
}
var XML_PREAMBLE = '<?xml version="1.0" encoding="UTF-8"?>\n';
var ESCAPE_ATTR_CHARS = /<|>|'|"|&|\n\r|\n/g;
var ESCAPE_CHARS = /<|>|&/g;
function Namespaces(parent) {
var prefixMap = {};
var uriMap = {};
var used = {};
var wellknown = [];
var custom = [];
// API
this.byUri = function(uri) {
return uriMap[uri] || (
parent && parent.byUri(uri)
);
};
this.add = function(ns, isWellknown) {
uriMap[ns.uri] = ns;
if (isWellknown) {
wellknown.push(ns);
} else {
custom.push(ns);
}
this.mapPrefix(ns.prefix, ns.uri);
};
this.uriByPrefix = function(prefix) {
return prefixMap[prefix || 'xmlns'];
};
this.mapPrefix = function(prefix, uri) {
prefixMap[prefix || 'xmlns'] = uri;
};
this.getNSKey = function(ns) {
return (ns.prefix !== undefined) ? (ns.uri + '|' + ns.prefix) : ns.uri;
};
this.logUsed = function(ns) {
var uri = ns.uri;
var nsKey = this.getNSKey(ns);
used[nsKey] = this.byUri(uri);
// Inform parent recursively about the usage of this NS
if (parent) {
parent.logUsed(ns);
}
};
this.getUsed = function(ns) {
function isUsed(ns) {
var nsKey = self.getNSKey(ns);
return used[nsKey];
}
var self = this;
var allNs = [].concat(wellknown, custom);
return allNs.filter(isUsed);
};
}
function lower(string) {
return string.charAt(0).toLowerCase() + string.slice(1);
}
function nameToAlias(name, pkg) {
if (hasLowerCaseAlias(pkg)) {
return lower(name);
} else {
return name;
}
}
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
function nsName(ns) {
if (isString(ns)) {
return ns;
} else {
return (ns.prefix ? ns.prefix + ':' : '') + ns.localName;
}
}
function getNsAttrs(namespaces) {
return namespaces.getUsed().filter(function(ns) {
// do not serialize built in <xml> namespace
return ns.prefix !== 'xml';
}).map(function(ns) {
var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : '');
return { name: name, value: ns.uri };
});
}
function getElementNs(ns, descriptor) {
if (descriptor.isGeneric) {
return assign({ localName: descriptor.ns.localName }, ns);
} else {
return assign({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns);
}
}
function getPropertyNs(ns, descriptor) {
return assign({ localName: descriptor.ns.localName }, ns);
}
function getSerializableProperties(element) {
var descriptor = element.$descriptor;
return filter(descriptor.properties, function(p) {
var name = p.name;
if (p.isVirtual) {
return false;
}
// do not serialize defaults
if (!has$1(element, name)) {
return false;
}
var value = element[name];
// do not serialize default equals
if (value === p.default) {
return false;
}
// do not serialize null properties
if (value === null) {
return false;
}
return p.isMany ? value.length : true;
});
}
var ESCAPE_ATTR_MAP = {
'\n': '#10',
'\n\r': '#10',
'"': '#34',
'\'': '#39',
'<': '#60',
'>': '#62',
'&': '#38'
};
var ESCAPE_MAP = {
'<': 'lt',
'>': 'gt',
'&': 'amp'
};
function escape(str, charPattern, replaceMap) {
// ensure we are handling strings here
str = isString(str) ? str : '' + str;
return str.replace(charPattern, function(s) {
return '&' + replaceMap[s] + ';';
});
}
/**
* Escape a string attribute to not contain any bad values (line breaks, '"', ...)
*
* @param {String} str the string to escape
* @return {String} the escaped string
*/
function escapeAttr(str) {
return escape(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP);
}
function escapeBody(str) {
return escape(str, ESCAPE_CHARS, ESCAPE_MAP);
}
function filterAttributes(props) {
return filter(props, function(p) { return p.isAttr; });
}
function filterContained(props) {
return filter(props, function(p) { return !p.isAttr; });
}
function ReferenceSerializer(tagName) {
this.tagName = tagName;
}
ReferenceSerializer.prototype.build = function(element) {
this.element = element;
return this;
};
ReferenceSerializer.prototype.serializeTo = function(writer) {
writer
.appendIndent()
.append('<' + this.tagName + '>' + this.element.id + '</' + this.tagName + '>')
.appendNewLine();
};
function BodySerializer() {}
BodySerializer.prototype.serializeValue =
BodySerializer.prototype.serializeTo = function(writer) {
writer.append(
this.escape
? escapeBody(this.value)
: this.value
);
};
BodySerializer.prototype.build = function(prop, value) {
this.value = value;
if (prop.type === 'String' && value.search(ESCAPE_CHARS) !== -1) {
this.escape = true;
}
return this;
};
function ValueSerializer(tagName) {
this.tagName = tagName;
}
inherits(ValueSerializer, BodySerializer);
ValueSerializer.prototype.serializeTo = function(writer) {
writer
.appendIndent()
.append('<' + this.tagName + '>');
this.serializeValue(writer);
writer
.append('</' + this.tagName + '>')
.appendNewLine();
};
function ElementSerializer(parent, propertyDescriptor) {
this.body = [];
this.attrs = [];
this.parent = parent;
this.propertyDescriptor = propertyDescriptor;
}
ElementSerializer.prototype.build = function(element) {
this.element = element;
var elementDescriptor = element.$descriptor,
propertyDescriptor = this.propertyDescriptor;
var otherAttrs,
properties;
var isGeneric = elementDescriptor.isGeneric;
if (isGeneric) {
otherAttrs = this.parseGeneric(element);
} else {
otherAttrs = this.parseNsAttributes(element);
}
if (propertyDescriptor) {
this.ns = this.nsPropertyTagName(propertyDescriptor);
} else {
this.ns = this.nsTagName(elementDescriptor);
}
// compute tag name
this.tagName = this.addTagName(this.ns);
if (!isGeneric) {
properties = getSerializableProperties(element);
this.parseAttributes(filterAttributes(properties));
this.parseContainments(filterContained(properties));
}
this.parseGenericAttributes(element, otherAttrs);
return this;
};
ElementSerializer.prototype.nsTagName = function(descriptor) {
var effectiveNs = this.logNamespaceUsed(descriptor.ns);
return getElementNs(effectiveNs, descriptor);
};
ElementSerializer.prototype.nsPropertyTagName = function(descriptor) {
var effectiveNs = this.logNamespaceUsed(descriptor.ns);
return getPropertyNs(effectiveNs, descriptor);
};
ElementSerializer.prototype.isLocalNs = function(ns) {
return ns.uri === this.ns.uri;
};
/**
* Get the actual ns attribute name for the given element.
*
* @param {Object} element
* @param {Boolean} [element.inherited=false]
*
* @return {Object} nsName
*/
ElementSerializer.prototype.nsAttributeName = function(element) {
var ns;
if (isString(element)) {
ns = parseName(element);
} else {
ns = element.ns;
}
// return just local name for inherited attributes
if (element.inherited) {
return { localName: ns.localName };
}
// parse + log effective ns
var effectiveNs = this.logNamespaceUsed(ns);
// LOG ACTUAL namespace use
this.getNamespaces().logUsed(effectiveNs);
// strip prefix if same namespace like parent
if (this.isLocalNs(effectiveNs)) {
return { localName: ns.localName };
} else {
return assign({ localName: ns.localName }, effectiveNs);
}
};
ElementSerializer.prototype.parseGeneric = function(element) {
var self = this,
body = this.body;
var attributes = [];
forEach$1(element, function(val, key) {
var nonNsAttr;
if (key === '$body') {
body.push(new BodySerializer().build({ type: 'String' }, val));
} else
if (key === '$children') {
forEach$1(val, function(child) {
body.push(new ElementSerializer(self).build(child));
});
} else
if (key.indexOf('$') !== 0) {
nonNsAttr = self.parseNsAttribute(element, key, val);
if (nonNsAttr) {
attributes.push({ name: key, value: val });
}
}
});
return attributes;
};
ElementSerializer.prototype.parseNsAttribute = function(element, name, value) {
var model = element.$model;
var nameNs = parseName(name);
var ns;
// parse xmlns:foo="http://foo.bar"
if (nameNs.prefix === 'xmlns') {
ns = { prefix: nameNs.localName, uri: value };
}
// parse xmlns="http://foo.bar"
if (!nameNs.prefix && nameNs.localName === 'xmlns') {
ns = { uri: value };
}
if (!ns) {
return {
name: name,
value: value
};
}
if (model && model.getPackage(value)) {
// register well known namespace
this.logNamespace(ns, true, true);
} else {
// log custom namespace directly as used
var actualNs = this.logNamespaceUsed(ns, true);
this.getNamespaces().logUsed(actualNs);
}
};
/**
* Parse namespaces and return a list of left over generic attributes
*
* @param {Object} element
* @return {Array<Object>}
*/
ElementSerializer.prototype.parseNsAttributes = function(element, attrs) {
var self = this;
var genericAttrs = element.$attrs;
var attributes = [];
// parse namespace attributes first
// and log them. push non namespace attributes to a list
// and process them later
forEach$1(genericAttrs, function(value, name) {
var nonNsAttr = self.parseNsAttribute(element, name, value);
if (nonNsAttr) {
attributes.push(nonNsAttr);
}
});
return attributes;
};
ElementSerializer.prototype.parseGenericAttributes = function(element, attributes) {
var self = this;
forEach$1(attributes, function(attr) {
// do not serialize xsi:type attribute
// it is set manually based on the actual implementation type
if (attr.name === XSI_TYPE) {
return;
}
try {
self.addAttribute(self.nsAttributeName(attr.name), attr.value);
} catch (e) {
console.warn(
'missing namespace information for ',
attr.name, '=', attr.value, 'on', element,
e);
}
});
};
ElementSerializer.prototype.parseContainments = function(properties) {
var self = this,
body = this.body,
element = this.element;
forEach$1(properties, function(p) {
var value = element.get(p.name),
isReference = p.isReference,
isMany = p.isMany;
if (!isMany) {
value = [ value ];
}
if (p.isBody) {
body.push(new BodySerializer().build(p, value[0]));
} else
if (isSimple(p.type)) {
forEach$1(value, function(v) {
body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v));
});
} else
if (isReference) {
forEach$1(value, function(v) {
body.push(new ReferenceSerializer(self.addTagName(self.nsPropertyTagName(p))).build(v));
});
} else {
// allow serialization via type
// rather than element name
var asType = serializeAsType(p),
asProperty = serializeAsProperty(p);
forEach$1(value, function(v) {
var serializer;
if (asType) {
serializer = new TypeSerializer(self, p);
} else
if (asProperty) {
serializer = new ElementSerializer(self, p);
} else {
serializer = new ElementSerializer(self);
}
body.push(serializer.build(v));
});
}
});
};
ElementSerializer.prototype.getNamespaces = function(local) {
var namespaces = this.namespaces,
parent = this.parent,
parentNamespaces;
if (!namespaces) {
parentNamespaces = parent && parent.getNamespaces();
if (local || !parentNamespaces) {
this.namespaces = namespaces = new Namespaces(parentNamespaces);
} else {
namespaces = parentNamespaces;
}
}
return namespaces;
};
ElementSerializer.prototype.logNamespace = function(ns, wellknown, local) {
var namespaces = this.getNamespaces(local);
var nsUri = ns.uri,
nsPrefix = ns.prefix;
var existing = namespaces.byUri(nsUri);
if (!existing || local) {
namespaces.add(ns, wellknown);
}
namespaces.mapPrefix(nsPrefix, nsUri);
return ns;
};
ElementSerializer.prototype.logNamespaceUsed = function(ns, local) {
var element = this.element,
model = element.$model,
namespaces = this.getNamespaces(local);
// ns may be
//
// * prefix only
// * prefix:uri
// * localName only
var prefix = ns.prefix,
uri = ns.uri,
newPrefix, idx,
wellknownUri;
// handle anonymous namespaces (elementForm=unqualified), cf. #23
if (!prefix && !uri) {
return { localName: ns.localName };
}
wellknownUri = DEFAULT_NS_MAP[prefix] || model && (model.getPackage(prefix) || {}).uri;
uri = uri || wellknownUri || namespaces.uriByPrefix(prefix);
if (!uri) {
throw new Error('no namespace uri given for prefix <' + prefix + '>');
}
ns = namespaces.byUri(uri);
if (!ns) {
newPrefix = prefix;
idx = 1;
// find a prefix that is not mapped yet
while (namespaces.uriByPrefix(newPrefix)) {
newPrefix = prefix + '_' + idx++;
}
ns = this.logNamespace({ prefix: newPrefix, uri: uri }, wellknownUri === uri);
}
if (prefix) {
namespaces.mapPrefix(prefix, uri);
}
return ns;
};
ElementSerializer.prototype.parseAttributes = function(properties) {
var self = this,
element = this.element;
forEach$1(properties, function(p) {
var value = element.get(p.name);
if (p.isReference) {
if (!p.isMany) {
value = value.id;
}
else {
var values = [];
forEach$1(value, function(v) {
values.push(v.id);
});
// IDREFS is a whitespace-separated list of references.
value = values.join(' ');
}
}
self.addAttribute(self.nsAttributeName(p), value);
});
};
ElementSerializer.prototype.addTagName = function(nsTagName) {
var actualNs = this.logNamespaceUsed(nsTagName);
this.getNamespaces().logUsed(actualNs);
return nsName(nsTagName);
};
ElementSerializer.prototype.addAttribute = function(name, value) {
var attrs = this.attrs;
if (isString(value)) {
value = escapeAttr(value);
}
// de-duplicate attributes
// https://github.com/bpmn-io/moddle-xml/issues/66
var idx = findIndex(attrs, function(element) {
return (
element.name.localName === name.localName &&
element.name.uri === name.uri &&
element.name.prefix === name.prefix
);
});
var attr = { name: name, value: value };
if (idx !== -1) {
attrs.splice(idx, 1, attr);
} else {
attrs.push(attr);
}
};
ElementSerializer.prototype.serializeAttributes = function(writer) {
var attrs = this.attrs,
namespaces = this.namespaces;
if (namespaces) {
attrs = getNsAttrs(namespaces).concat(attrs);
}
forEach$1(attrs, function(a) {
writer
.append(' ')
.append(nsName(a.name)).append('="').append(a.value).append('"');
});
};
ElementSerializer.prototype.serializeTo = function(writer) {
var firstBody = this.body[0],
indent = firstBody && firstBody.constructor !== BodySerializer;
writer
.appendIndent()
.append('<' + this.tagName);
this.serializeAttributes(writer);
writer.append(firstBody ? '>' : ' />');
if (firstBody) {
if (indent) {
writer
.appendNewLine()
.indent();
}
forEach$1(this.body, function(b) {
b.serializeTo(writer);
});
if (indent) {
writer
.unindent()
.appendIndent();
}
writer.append('</' + this.tagName + '>');
}
writer.appendNewLine();
};
/**
* A serializer for types that handles serialization of data types
*/
function TypeSerializer(parent, propertyDescriptor) {
ElementSerializer.call(this, parent, propertyDescriptor);
}
inherits(TypeSerializer, ElementSerializer);
TypeSerializer.prototype.parseNsAttributes = function(element) {
// extracted attributes
var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element);
var descriptor = element.$descriptor;
// only serialize xsi:type if necessary
if (descriptor.name === this.propertyDescriptor.type) {
return attributes;
}
var typeNs = this.typeNs = this.nsTagName(descriptor);
this.getNamespaces().logUsed(this.typeNs);
// add xsi:type attribute to represent the elements
// actual type
var pkg = element.$model.getPackage(typeNs.uri),
typePrefix = (pkg.xml && pkg.xml.typePrefix) || '';
this.addAttribute(
this.nsAttributeName(XSI_TYPE),
(typeNs.prefix ? typeNs.prefix + ':' : '') + typePrefix + descriptor.ns.localName
);
return attributes;
};
TypeSerializer.prototype.isLocalNs = function(ns) {
return ns.uri === (this.typeNs || this.ns).uri;
};
function SavingWriter() {
this.value = '';
this.write = function(str) {
this.value += str;
};
}
function FormatingWriter(out, format) {
var indent = [''];
this.append = function(str) {
out.write(str);
return this;
};
this.appendNewLine = function() {
if (format) {
out.write('\n');
}
return this;
};
this.appendIndent = function() {
if (format) {
out.write(indent.join(' '));
}
return this;
};
this.indent = function() {
indent.push('');
return this;
};
this.unindent = function() {
indent.pop();
return this;
};
}
/**
* A writer for meta-model backed document trees
*
* @param {Object} options output options to pass into the writer
*/
function Writer(options) {
options = assign({ format: false, preamble: true }, options || {});
function toXML(tree, writer) {
var internalWriter = writer || new SavingWriter();
var formatingWriter = new FormatingWriter(internalWriter, options.format);
if (options.preamble) {
formatingWriter.append(XML_PREAMBLE);
}
new ElementSerializer().build(tree).serializeTo(formatingWriter);
if (!writer) {
return internalWriter.value;
}
}
return {
toXML: toXML
};
}
/**
* A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files.
*
* @class BpmnModdle
* @extends Moddle
*
* @param {Object|Array} packages to use for instantiating the model
* @param {Object} [options] additional options to pass over
*/
function BpmnModdle(packages, options) {
Moddle.call(this, packages, options);
}
BpmnModdle.prototype = Object.create(Moddle.prototype);
/**
* The fromXML result.
*
* @typedef {Object} ParseResult
*
* @property {ModdleElement} rootElement
* @property {Array<Object>} references
* @property {Array<Error>} warnings
* @property {Object} elementsById - a mapping containing each ID -> ModdleElement
*/
/**
* The fromXML error.
*
* @typedef {Error} ParseError
*
* @property {Array<Error>} warnings
*/
/**
* Instantiates a BPMN model tree from a given xml string.
*
* @param {String} xmlStr
* @param {String} [typeName='bpmn:Definitions'] name of the root element
* @param {Object} [options] options to pass to the underlying reader
*
* @returns {Promise<ParseResult, ParseError>}
*/
BpmnModdle.prototype.fromXML = function(xmlStr, typeName, options) {
if (!isString(typeName)) {
options = typeName;
typeName = 'bpmn:Definitions';
}
var reader = new Reader(assign({ model: this, lax: true }, options));
var rootHandler = reader.handler(typeName);
return reader.fromXML(xmlStr, rootHandler);
};
/**
* The toXML result.
*
* @typedef {Object} SerializationResult
*
* @property {String} xml
*/
/**
* Serializes a BPMN 2.0 object tree to XML.
*
* @param {String} element the root element, typically an instance of `bpmn:Definitions`
* @param {Object} [options] to pass to the underlying writer
*
* @returns {Promise<SerializationResult, Error>}
*/
BpmnModdle.prototype.toXML = function(element, options) {
var writer = new Writer(options);
return new Promise(function(resolve, reject) {
try {
var result = writer.toXML(element);
return resolve({
xml: result
});
} catch (err) {
return reject(err);
}
});
};
var name$5 = "BPMN20";
var uri$5 = "http://www.omg.org/spec/BPMN/20100524/MODEL";
var prefix$5 = "bpmn";
var associations$5 = [
];
var types$5 = [
{
name: "Interface",
superClass: [
"RootElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "operations",
type: "Operation",
isMany: true
},
{
name: "implementationRef",
isAttr: true,
type: "String"
}
]
},
{
name: "Operation",
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "inMessageRef",
type: "Message",
isReference: true
},
{
name: "outMessageRef",
type: "Message",
isReference: true
},
{
name: "errorRef",
type: "Error",
isMany: true,
isReference: true
},
{
name: "implementationRef",
isAttr: true,
type: "String"
}
]
},
{
name: "EndPoint",
superClass: [
"RootElement"
]
},
{
name: "Auditing",
superClass: [
"BaseElement"
]
},
{
name: "GlobalTask",
superClass: [
"CallableElement"
],
properties: [
{
name: "resources",
type: "ResourceRole",
isMany: true
}
]
},
{
name: "Monitoring",
superClass: [
"BaseElement"
]
},
{
name: "Performer",
superClass: [
"ResourceRole"
]
},
{
name: "Process",
superClass: [
"FlowElementsContainer",
"CallableElement"
],
properties: [
{
name: "processType",
type: "ProcessType",
isAttr: true
},
{
name: "isClosed",
isAttr: true,
type: "Boolean"
},
{
name: "auditing",
type: "Auditing"
},
{
name: "monitoring",
type: "Monitoring"
},
{
name: "properties",
type: "Property",
isMany: true
},
{
name: "laneSets",
isMany: true,
replaces: "FlowElementsContainer#laneSets",
type: "LaneSet"
},
{
name: "flowElements",
isMany: true,
replaces: "FlowElementsContainer#flowElements",
type: "FlowElement"
},
{
name: "artifacts",
type: "Artifact",
isMany: true
},
{
name: "resources",
type: "ResourceRole",
isMany: true
},
{
name: "correlationSubscriptions",
type: "CorrelationSubscription",
isMany: true
},
{
name: "supports",
type: "Process",
isMany: true,
isReference: true
},
{
name: "definitionalCollaborationRef",
type: "Collaboration",
isAttr: true,
isReference: true
},
{
name: "isExecutable",
isAttr: true,
type: "Boolean"
}
]
},
{
name: "LaneSet",
superClass: [
"BaseElement"
],
properties: [
{
name: "lanes",
type: "Lane",
isMany: true
},
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "Lane",
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "partitionElementRef",
type: "BaseElement",
isAttr: true,
isReference: true
},
{
name: "partitionElement",
type: "BaseElement"
},
{
name: "flowNodeRef",
type: "FlowNode",
isMany: true,
isReference: true
},
{
name: "childLaneSet",
type: "LaneSet",
xml: {
serialize: "xsi:type"
}
}
]
},
{
name: "GlobalManualTask",
superClass: [
"GlobalTask"
]
},
{
name: "ManualTask",
superClass: [
"Task"
]
},
{
name: "UserTask",
superClass: [
"Task"
],
properties: [
{
name: "renderings",
type: "Rendering",
isMany: true
},
{
name: "implementation",
isAttr: true,
type: "String"
}
]
},
{
name: "Rendering",
superClass: [
"BaseElement"
]
},
{
name: "HumanPerformer",
superClass: [
"Performer"
]
},
{
name: "PotentialOwner",
superClass: [
"HumanPerformer"
]
},
{
name: "GlobalUserTask",
superClass: [
"GlobalTask"
],
properties: [
{
name: "implementation",
isAttr: true,
type: "String"
},
{
name: "renderings",
type: "Rendering",
isMany: true
}
]
},
{
name: "Gateway",
isAbstract: true,
superClass: [
"FlowNode"
],
properties: [
{
name: "gatewayDirection",
type: "GatewayDirection",
"default": "Unspecified",
isAttr: true
}
]
},
{
name: "EventBasedGateway",
superClass: [
"Gateway"
],
properties: [
{
name: "instantiate",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "eventGatewayType",
type: "EventBasedGatewayType",
isAttr: true,
"default": "Exclusive"
}
]
},
{
name: "ComplexGateway",
superClass: [
"Gateway"
],
properties: [
{
name: "activationCondition",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "default",
type: "SequenceFlow",
isAttr: true,
isReference: true
}
]
},
{
name: "ExclusiveGateway",
superClass: [
"Gateway"
],
properties: [
{
name: "default",
type: "SequenceFlow",
isAttr: true,
isReference: true
}
]
},
{
name: "InclusiveGateway",
superClass: [
"Gateway"
],
properties: [
{
name: "default",
type: "SequenceFlow",
isAttr: true,
isReference: true
}
]
},
{
name: "ParallelGateway",
superClass: [
"Gateway"
]
},
{
name: "RootElement",
isAbstract: true,
superClass: [
"BaseElement"
]
},
{
name: "Relationship",
superClass: [
"BaseElement"
],
properties: [
{
name: "type",
isAttr: true,
type: "String"
},
{
name: "direction",
type: "RelationshipDirection",
isAttr: true
},
{
name: "source",
isMany: true,
isReference: true,
type: "Element"
},
{
name: "target",
isMany: true,
isReference: true,
type: "Element"
}
]
},
{
name: "BaseElement",
isAbstract: true,
properties: [
{
name: "id",
isAttr: true,
type: "String",
isId: true
},
{
name: "documentation",
type: "Documentation",
isMany: true
},
{
name: "extensionDefinitions",
type: "ExtensionDefinition",
isMany: true,
isReference: true
},
{
name: "extensionElements",
type: "ExtensionElements"
}
]
},
{
name: "Extension",
properties: [
{
name: "mustUnderstand",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "definition",
type: "ExtensionDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "ExtensionDefinition",
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "extensionAttributeDefinitions",
type: "ExtensionAttributeDefinition",
isMany: true
}
]
},
{
name: "ExtensionAttributeDefinition",
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "type",
isAttr: true,
type: "String"
},
{
name: "isReference",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "extensionDefinition",
type: "ExtensionDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "ExtensionElements",
properties: [
{
name: "valueRef",
isAttr: true,
isReference: true,
type: "Element"
},
{
name: "values",
type: "Element",
isMany: true
},
{
name: "extensionAttributeDefinition",
type: "ExtensionAttributeDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "Documentation",
superClass: [
"BaseElement"
],
properties: [
{
name: "text",
type: "String",
isBody: true
},
{
name: "textFormat",
"default": "text/plain",
isAttr: true,
type: "String"
}
]
},
{
name: "Event",
isAbstract: true,
superClass: [
"FlowNode",
"InteractionNode"
],
properties: [
{
name: "properties",
type: "Property",
isMany: true
}
]
},
{
name: "IntermediateCatchEvent",
superClass: [
"CatchEvent"
]
},
{
name: "IntermediateThrowEvent",
superClass: [
"ThrowEvent"
]
},
{
name: "EndEvent",
superClass: [
"ThrowEvent"
]
},
{
name: "StartEvent",
superClass: [
"CatchEvent"
],
properties: [
{
name: "isInterrupting",
"default": true,
isAttr: true,
type: "Boolean"
}
]
},
{
name: "ThrowEvent",
isAbstract: true,
superClass: [
"Event"
],
properties: [
{
name: "dataInputs",
type: "DataInput",
isMany: true
},
{
name: "dataInputAssociations",
type: "DataInputAssociation",
isMany: true
},
{
name: "inputSet",
type: "InputSet"
},
{
name: "eventDefinitions",
type: "EventDefinition",
isMany: true
},
{
name: "eventDefinitionRef",
type: "EventDefinition",
isMany: true,
isReference: true
}
]
},
{
name: "CatchEvent",
isAbstract: true,
superClass: [
"Event"
],
properties: [
{
name: "parallelMultiple",
isAttr: true,
type: "Boolean",
"default": false
},
{
name: "dataOutputs",
type: "DataOutput",
isMany: true
},
{
name: "dataOutputAssociations",
type: "DataOutputAssociation",
isMany: true
},
{
name: "outputSet",
type: "OutputSet"
},
{
name: "eventDefinitions",
type: "EventDefinition",
isMany: true
},
{
name: "eventDefinitionRef",
type: "EventDefinition",
isMany: true,
isReference: true
}
]
},
{
name: "BoundaryEvent",
superClass: [
"CatchEvent"
],
properties: [
{
name: "cancelActivity",
"default": true,
isAttr: true,
type: "Boolean"
},
{
name: "attachedToRef",
type: "Activity",
isAttr: true,
isReference: true
}
]
},
{
name: "EventDefinition",
isAbstract: true,
superClass: [
"RootElement"
]
},
{
name: "CancelEventDefinition",
superClass: [
"EventDefinition"
]
},
{
name: "ErrorEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "errorRef",
type: "Error",
isAttr: true,
isReference: true
}
]
},
{
name: "TerminateEventDefinition",
superClass: [
"EventDefinition"
]
},
{
name: "EscalationEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "escalationRef",
type: "Escalation",
isAttr: true,
isReference: true
}
]
},
{
name: "Escalation",
properties: [
{
name: "structureRef",
type: "ItemDefinition",
isAttr: true,
isReference: true
},
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "escalationCode",
isAttr: true,
type: "String"
}
],
superClass: [
"RootElement"
]
},
{
name: "CompensateEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "waitForCompletion",
isAttr: true,
type: "Boolean",
"default": true
},
{
name: "activityRef",
type: "Activity",
isAttr: true,
isReference: true
}
]
},
{
name: "TimerEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "timeDate",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "timeCycle",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "timeDuration",
type: "Expression",
xml: {
serialize: "xsi:type"
}
}
]
},
{
name: "LinkEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "target",
type: "LinkEventDefinition",
isAttr: true,
isReference: true
},
{
name: "source",
type: "LinkEventDefinition",
isMany: true,
isReference: true
}
]
},
{
name: "MessageEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "messageRef",
type: "Message",
isAttr: true,
isReference: true
},
{
name: "operationRef",
type: "Operation",
isAttr: true,
isReference: true
}
]
},
{
name: "ConditionalEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "condition",
type: "Expression",
xml: {
serialize: "xsi:type"
}
}
]
},
{
name: "SignalEventDefinition",
superClass: [
"EventDefinition"
],
properties: [
{
name: "signalRef",
type: "Signal",
isAttr: true,
isReference: true
}
]
},
{
name: "Signal",
superClass: [
"RootElement"
],
properties: [
{
name: "structureRef",
type: "ItemDefinition",
isAttr: true,
isReference: true
},
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "ImplicitThrowEvent",
superClass: [
"ThrowEvent"
]
},
{
name: "DataState",
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "ItemAwareElement",
superClass: [
"BaseElement"
],
properties: [
{
name: "itemSubjectRef",
type: "ItemDefinition",
isAttr: true,
isReference: true
},
{
name: "dataState",
type: "DataState"
}
]
},
{
name: "DataAssociation",
superClass: [
"BaseElement"
],
properties: [
{
name: "sourceRef",
type: "ItemAwareElement",
isMany: true,
isReference: true
},
{
name: "targetRef",
type: "ItemAwareElement",
isReference: true
},
{
name: "transformation",
type: "FormalExpression",
xml: {
serialize: "property"
}
},
{
name: "assignment",
type: "Assignment",
isMany: true
}
]
},
{
name: "DataInput",
superClass: [
"ItemAwareElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "isCollection",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "inputSetRef",
type: "InputSet",
isMany: true,
isVirtual: true,
isReference: true
},
{
name: "inputSetWithOptional",
type: "InputSet",
isMany: true,
isVirtual: true,
isReference: true
},
{
name: "inputSetWithWhileExecuting",
type: "InputSet",
isMany: true,
isVirtual: true,
isReference: true
}
]
},
{
name: "DataOutput",
superClass: [
"ItemAwareElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "isCollection",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "outputSetRef",
type: "OutputSet",
isMany: true,
isVirtual: true,
isReference: true
},
{
name: "outputSetWithOptional",
type: "OutputSet",
isMany: true,
isVirtual: true,
isReference: true
},
{
name: "outputSetWithWhileExecuting",
type: "OutputSet",
isMany: true,
isVirtual: true,
isReference: true
}
]
},
{
name: "InputSet",
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "dataInputRefs",
type: "DataInput",
isMany: true,
isReference: true
},
{
name: "optionalInputRefs",
type: "DataInput",
isMany: true,
isReference: true
},
{
name: "whileExecutingInputRefs",
type: "DataInput",
isMany: true,
isReference: true
},
{
name: "outputSetRefs",
type: "OutputSet",
isMany: true,
isReference: true
}
]
},
{
name: "OutputSet",
superClass: [
"BaseElement"
],
properties: [
{
name: "dataOutputRefs",
type: "DataOutput",
isMany: true,
isReference: true
},
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "inputSetRefs",
type: "InputSet",
isMany: true,
isReference: true
},
{
name: "optionalOutputRefs",
type: "DataOutput",
isMany: true,
isReference: true
},
{
name: "whileExecutingOutputRefs",
type: "DataOutput",
isMany: true,
isReference: true
}
]
},
{
name: "Property",
superClass: [
"ItemAwareElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "DataInputAssociation",
superClass: [
"DataAssociation"
]
},
{
name: "DataOutputAssociation",
superClass: [
"DataAssociation"
]
},
{
name: "InputOutputSpecification",
superClass: [
"BaseElement"
],
properties: [
{
name: "dataInputs",
type: "DataInput",
isMany: true
},
{
name: "dataOutputs",
type: "DataOutput",
isMany: true
},
{
name: "inputSets",
type: "InputSet",
isMany: true
},
{
name: "outputSets",
type: "OutputSet",
isMany: true
}
]
},
{
name: "DataObject",
superClass: [
"FlowElement",
"ItemAwareElement"
],
properties: [
{
name: "isCollection",
"default": false,
isAttr: true,
type: "Boolean"
}
]
},
{
name: "InputOutputBinding",
properties: [
{
name: "inputDataRef",
type: "InputSet",
isAttr: true,
isReference: true
},
{
name: "outputDataRef",
type: "OutputSet",
isAttr: true,
isReference: true
},
{
name: "operationRef",
type: "Operation",
isAttr: true,
isReference: true
}
]
},
{
name: "Assignment",
superClass: [
"BaseElement"
],
properties: [
{
name: "from",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "to",
type: "Expression",
xml: {
serialize: "xsi:type"
}
}
]
},
{
name: "DataStore",
superClass: [
"RootElement",
"ItemAwareElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "capacity",
isAttr: true,
type: "Integer"
},
{
name: "isUnlimited",
"default": true,
isAttr: true,
type: "Boolean"
}
]
},
{
name: "DataStoreReference",
superClass: [
"ItemAwareElement",
"FlowElement"
],
properties: [
{
name: "dataStoreRef",
type: "DataStore",
isAttr: true,
isReference: true
}
]
},
{
name: "DataObjectReference",
superClass: [
"ItemAwareElement",
"FlowElement"
],
properties: [
{
name: "dataObjectRef",
type: "DataObject",
isAttr: true,
isReference: true
}
]
},
{
name: "ConversationLink",
superClass: [
"BaseElement"
],
properties: [
{
name: "sourceRef",
type: "InteractionNode",
isAttr: true,
isReference: true
},
{
name: "targetRef",
type: "InteractionNode",
isAttr: true,
isReference: true
},
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "ConversationAssociation",
superClass: [
"BaseElement"
],
properties: [
{
name: "innerConversationNodeRef",
type: "ConversationNode",
isAttr: true,
isReference: true
},
{
name: "outerConversationNodeRef",
type: "ConversationNode",
isAttr: true,
isReference: true
}
]
},
{
name: "CallConversation",
superClass: [
"ConversationNode"
],
properties: [
{
name: "calledCollaborationRef",
type: "Collaboration",
isAttr: true,
isReference: true
},
{
name: "participantAssociations",
type: "ParticipantAssociation",
isMany: true
}
]
},
{
name: "Conversation",
superClass: [
"ConversationNode"
]
},
{
name: "SubConversation",
superClass: [
"ConversationNode"
],
properties: [
{
name: "conversationNodes",
type: "ConversationNode",
isMany: true
}
]
},
{
name: "ConversationNode",
isAbstract: true,
superClass: [
"InteractionNode",
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "participantRef",
type: "Participant",
isMany: true,
isReference: true
},
{
name: "messageFlowRefs",
type: "MessageFlow",
isMany: true,
isReference: true
},
{
name: "correlationKeys",
type: "CorrelationKey",
isMany: true
}
]
},
{
name: "GlobalConversation",
superClass: [
"Collaboration"
]
},
{
name: "PartnerEntity",
superClass: [
"RootElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "participantRef",
type: "Participant",
isMany: true,
isReference: true
}
]
},
{
name: "PartnerRole",
superClass: [
"RootElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "participantRef",
type: "Participant",
isMany: true,
isReference: true
}
]
},
{
name: "CorrelationProperty",
superClass: [
"RootElement"
],
properties: [
{
name: "correlationPropertyRetrievalExpression",
type: "CorrelationPropertyRetrievalExpression",
isMany: true
},
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "type",
type: "ItemDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "Error",
superClass: [
"RootElement"
],
properties: [
{
name: "structureRef",
type: "ItemDefinition",
isAttr: true,
isReference: true
},
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "errorCode",
isAttr: true,
type: "String"
}
]
},
{
name: "CorrelationKey",
superClass: [
"BaseElement"
],
properties: [
{
name: "correlationPropertyRef",
type: "CorrelationProperty",
isMany: true,
isReference: true
},
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "Expression",
superClass: [
"BaseElement"
],
isAbstract: false,
properties: [
{
name: "body",
isBody: true,
type: "String"
}
]
},
{
name: "FormalExpression",
superClass: [
"Expression"
],
properties: [
{
name: "language",
isAttr: true,
type: "String"
},
{
name: "evaluatesToTypeRef",
type: "ItemDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "Message",
superClass: [
"RootElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "itemRef",
type: "ItemDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "ItemDefinition",
superClass: [
"RootElement"
],
properties: [
{
name: "itemKind",
type: "ItemKind",
isAttr: true
},
{
name: "structureRef",
isAttr: true,
type: "String"
},
{
name: "isCollection",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "import",
type: "Import",
isAttr: true,
isReference: true
}
]
},
{
name: "FlowElement",
isAbstract: true,
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "auditing",
type: "Auditing"
},
{
name: "monitoring",
type: "Monitoring"
},
{
name: "categoryValueRef",
type: "CategoryValue",
isMany: true,
isReference: true
}
]
},
{
name: "SequenceFlow",
superClass: [
"FlowElement"
],
properties: [
{
name: "isImmediate",
isAttr: true,
type: "Boolean"
},
{
name: "conditionExpression",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "sourceRef",
type: "FlowNode",
isAttr: true,
isReference: true
},
{
name: "targetRef",
type: "FlowNode",
isAttr: true,
isReference: true
}
]
},
{
name: "FlowElementsContainer",
isAbstract: true,
superClass: [
"BaseElement"
],
properties: [
{
name: "laneSets",
type: "LaneSet",
isMany: true
},
{
name: "flowElements",
type: "FlowElement",
isMany: true
}
]
},
{
name: "CallableElement",
isAbstract: true,
superClass: [
"RootElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "ioSpecification",
type: "InputOutputSpecification",
xml: {
serialize: "property"
}
},
{
name: "supportedInterfaceRef",
type: "Interface",
isMany: true,
isReference: true
},
{
name: "ioBinding",
type: "InputOutputBinding",
isMany: true,
xml: {
serialize: "property"
}
}
]
},
{
name: "FlowNode",
isAbstract: true,
superClass: [
"FlowElement"
],
properties: [
{
name: "incoming",
type: "SequenceFlow",
isMany: true,
isReference: true
},
{
name: "outgoing",
type: "SequenceFlow",
isMany: true,
isReference: true
},
{
name: "lanes",
type: "Lane",
isMany: true,
isVirtual: true,
isReference: true
}
]
},
{
name: "CorrelationPropertyRetrievalExpression",
superClass: [
"BaseElement"
],
properties: [
{
name: "messagePath",
type: "FormalExpression"
},
{
name: "messageRef",
type: "Message",
isAttr: true,
isReference: true
}
]
},
{
name: "CorrelationPropertyBinding",
superClass: [
"BaseElement"
],
properties: [
{
name: "dataPath",
type: "FormalExpression"
},
{
name: "correlationPropertyRef",
type: "CorrelationProperty",
isAttr: true,
isReference: true
}
]
},
{
name: "Resource",
superClass: [
"RootElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "resourceParameters",
type: "ResourceParameter",
isMany: true
}
]
},
{
name: "ResourceParameter",
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "isRequired",
isAttr: true,
type: "Boolean"
},
{
name: "type",
type: "ItemDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "CorrelationSubscription",
superClass: [
"BaseElement"
],
properties: [
{
name: "correlationKeyRef",
type: "CorrelationKey",
isAttr: true,
isReference: true
},
{
name: "correlationPropertyBinding",
type: "CorrelationPropertyBinding",
isMany: true
}
]
},
{
name: "MessageFlow",
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "sourceRef",
type: "InteractionNode",
isAttr: true,
isReference: true
},
{
name: "targetRef",
type: "InteractionNode",
isAttr: true,
isReference: true
},
{
name: "messageRef",
type: "Message",
isAttr: true,
isReference: true
}
]
},
{
name: "MessageFlowAssociation",
superClass: [
"BaseElement"
],
properties: [
{
name: "innerMessageFlowRef",
type: "MessageFlow",
isAttr: true,
isReference: true
},
{
name: "outerMessageFlowRef",
type: "MessageFlow",
isAttr: true,
isReference: true
}
]
},
{
name: "InteractionNode",
isAbstract: true,
properties: [
{
name: "incomingConversationLinks",
type: "ConversationLink",
isMany: true,
isVirtual: true,
isReference: true
},
{
name: "outgoingConversationLinks",
type: "ConversationLink",
isMany: true,
isVirtual: true,
isReference: true
}
]
},
{
name: "Participant",
superClass: [
"InteractionNode",
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "interfaceRef",
type: "Interface",
isMany: true,
isReference: true
},
{
name: "participantMultiplicity",
type: "ParticipantMultiplicity"
},
{
name: "endPointRefs",
type: "EndPoint",
isMany: true,
isReference: true
},
{
name: "processRef",
type: "Process",
isAttr: true,
isReference: true
}
]
},
{
name: "ParticipantAssociation",
superClass: [
"BaseElement"
],
properties: [
{
name: "innerParticipantRef",
type: "Participant",
isAttr: true,
isReference: true
},
{
name: "outerParticipantRef",
type: "Participant",
isAttr: true,
isReference: true
}
]
},
{
name: "ParticipantMultiplicity",
properties: [
{
name: "minimum",
"default": 0,
isAttr: true,
type: "Integer"
},
{
name: "maximum",
"default": 1,
isAttr: true,
type: "Integer"
}
],
superClass: [
"BaseElement"
]
},
{
name: "Collaboration",
superClass: [
"RootElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "isClosed",
isAttr: true,
type: "Boolean"
},
{
name: "participants",
type: "Participant",
isMany: true
},
{
name: "messageFlows",
type: "MessageFlow",
isMany: true
},
{
name: "artifacts",
type: "Artifact",
isMany: true
},
{
name: "conversations",
type: "ConversationNode",
isMany: true
},
{
name: "conversationAssociations",
type: "ConversationAssociation"
},
{
name: "participantAssociations",
type: "ParticipantAssociation",
isMany: true
},
{
name: "messageFlowAssociations",
type: "MessageFlowAssociation",
isMany: true
},
{
name: "correlationKeys",
type: "CorrelationKey",
isMany: true
},
{
name: "choreographyRef",
type: "Choreography",
isMany: true,
isReference: true
},
{
name: "conversationLinks",
type: "ConversationLink",
isMany: true
}
]
},
{
name: "ChoreographyActivity",
isAbstract: true,
superClass: [
"FlowNode"
],
properties: [
{
name: "participantRef",
type: "Participant",
isMany: true,
isReference: true
},
{
name: "initiatingParticipantRef",
type: "Participant",
isAttr: true,
isReference: true
},
{
name: "correlationKeys",
type: "CorrelationKey",
isMany: true
},
{
name: "loopType",
type: "ChoreographyLoopType",
"default": "None",
isAttr: true
}
]
},
{
name: "CallChoreography",
superClass: [
"ChoreographyActivity"
],
properties: [
{
name: "calledChoreographyRef",
type: "Choreography",
isAttr: true,
isReference: true
},
{
name: "participantAssociations",
type: "ParticipantAssociation",
isMany: true
}
]
},
{
name: "SubChoreography",
superClass: [
"ChoreographyActivity",
"FlowElementsContainer"
],
properties: [
{
name: "artifacts",
type: "Artifact",
isMany: true
}
]
},
{
name: "ChoreographyTask",
superClass: [
"ChoreographyActivity"
],
properties: [
{
name: "messageFlowRef",
type: "MessageFlow",
isMany: true,
isReference: true
}
]
},
{
name: "Choreography",
superClass: [
"Collaboration",
"FlowElementsContainer"
]
},
{
name: "GlobalChoreographyTask",
superClass: [
"Choreography"
],
properties: [
{
name: "initiatingParticipantRef",
type: "Participant",
isAttr: true,
isReference: true
}
]
},
{
name: "TextAnnotation",
superClass: [
"Artifact"
],
properties: [
{
name: "text",
type: "String"
},
{
name: "textFormat",
"default": "text/plain",
isAttr: true,
type: "String"
}
]
},
{
name: "Group",
superClass: [
"Artifact"
],
properties: [
{
name: "categoryValueRef",
type: "CategoryValue",
isAttr: true,
isReference: true
}
]
},
{
name: "Association",
superClass: [
"Artifact"
],
properties: [
{
name: "associationDirection",
type: "AssociationDirection",
isAttr: true
},
{
name: "sourceRef",
type: "BaseElement",
isAttr: true,
isReference: true
},
{
name: "targetRef",
type: "BaseElement",
isAttr: true,
isReference: true
}
]
},
{
name: "Category",
superClass: [
"RootElement"
],
properties: [
{
name: "categoryValue",
type: "CategoryValue",
isMany: true
},
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "Artifact",
isAbstract: true,
superClass: [
"BaseElement"
]
},
{
name: "CategoryValue",
superClass: [
"BaseElement"
],
properties: [
{
name: "categorizedFlowElements",
type: "FlowElement",
isMany: true,
isVirtual: true,
isReference: true
},
{
name: "value",
isAttr: true,
type: "String"
}
]
},
{
name: "Activity",
isAbstract: true,
superClass: [
"FlowNode"
],
properties: [
{
name: "isForCompensation",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "default",
type: "SequenceFlow",
isAttr: true,
isReference: true
},
{
name: "ioSpecification",
type: "InputOutputSpecification",
xml: {
serialize: "property"
}
},
{
name: "boundaryEventRefs",
type: "BoundaryEvent",
isMany: true,
isReference: true
},
{
name: "properties",
type: "Property",
isMany: true
},
{
name: "dataInputAssociations",
type: "DataInputAssociation",
isMany: true
},
{
name: "dataOutputAssociations",
type: "DataOutputAssociation",
isMany: true
},
{
name: "startQuantity",
"default": 1,
isAttr: true,
type: "Integer"
},
{
name: "resources",
type: "ResourceRole",
isMany: true
},
{
name: "completionQuantity",
"default": 1,
isAttr: true,
type: "Integer"
},
{
name: "loopCharacteristics",
type: "LoopCharacteristics"
}
]
},
{
name: "ServiceTask",
superClass: [
"Task"
],
properties: [
{
name: "implementation",
isAttr: true,
type: "String"
},
{
name: "operationRef",
type: "Operation",
isAttr: true,
isReference: true
}
]
},
{
name: "SubProcess",
superClass: [
"Activity",
"FlowElementsContainer",
"InteractionNode"
],
properties: [
{
name: "triggeredByEvent",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "artifacts",
type: "Artifact",
isMany: true
}
]
},
{
name: "LoopCharacteristics",
isAbstract: true,
superClass: [
"BaseElement"
]
},
{
name: "MultiInstanceLoopCharacteristics",
superClass: [
"LoopCharacteristics"
],
properties: [
{
name: "isSequential",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "behavior",
type: "MultiInstanceBehavior",
"default": "All",
isAttr: true
},
{
name: "loopCardinality",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "loopDataInputRef",
type: "ItemAwareElement",
isReference: true
},
{
name: "loopDataOutputRef",
type: "ItemAwareElement",
isReference: true
},
{
name: "inputDataItem",
type: "DataInput",
xml: {
serialize: "property"
}
},
{
name: "outputDataItem",
type: "DataOutput",
xml: {
serialize: "property"
}
},
{
name: "complexBehaviorDefinition",
type: "ComplexBehaviorDefinition",
isMany: true
},
{
name: "completionCondition",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "oneBehaviorEventRef",
type: "EventDefinition",
isAttr: true,
isReference: true
},
{
name: "noneBehaviorEventRef",
type: "EventDefinition",
isAttr: true,
isReference: true
}
]
},
{
name: "StandardLoopCharacteristics",
superClass: [
"LoopCharacteristics"
],
properties: [
{
name: "testBefore",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "loopCondition",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "loopMaximum",
type: "Integer",
isAttr: true
}
]
},
{
name: "CallActivity",
superClass: [
"Activity",
"InteractionNode"
],
properties: [
{
name: "calledElement",
type: "String",
isAttr: true
}
]
},
{
name: "Task",
superClass: [
"Activity",
"InteractionNode"
]
},
{
name: "SendTask",
superClass: [
"Task"
],
properties: [
{
name: "implementation",
isAttr: true,
type: "String"
},
{
name: "operationRef",
type: "Operation",
isAttr: true,
isReference: true
},
{
name: "messageRef",
type: "Message",
isAttr: true,
isReference: true
}
]
},
{
name: "ReceiveTask",
superClass: [
"Task"
],
properties: [
{
name: "implementation",
isAttr: true,
type: "String"
},
{
name: "instantiate",
"default": false,
isAttr: true,
type: "Boolean"
},
{
name: "operationRef",
type: "Operation",
isAttr: true,
isReference: true
},
{
name: "messageRef",
type: "Message",
isAttr: true,
isReference: true
}
]
},
{
name: "ScriptTask",
superClass: [
"Task"
],
properties: [
{
name: "scriptFormat",
isAttr: true,
type: "String"
},
{
name: "script",
type: "String"
}
]
},
{
name: "BusinessRuleTask",
superClass: [
"Task"
],
properties: [
{
name: "implementation",
isAttr: true,
type: "String"
}
]
},
{
name: "AdHocSubProcess",
superClass: [
"SubProcess"
],
properties: [
{
name: "completionCondition",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "ordering",
type: "AdHocOrdering",
isAttr: true
},
{
name: "cancelRemainingInstances",
"default": true,
isAttr: true,
type: "Boolean"
}
]
},
{
name: "Transaction",
superClass: [
"SubProcess"
],
properties: [
{
name: "protocol",
isAttr: true,
type: "String"
},
{
name: "method",
isAttr: true,
type: "String"
}
]
},
{
name: "GlobalScriptTask",
superClass: [
"GlobalTask"
],
properties: [
{
name: "scriptLanguage",
isAttr: true,
type: "String"
},
{
name: "script",
isAttr: true,
type: "String"
}
]
},
{
name: "GlobalBusinessRuleTask",
superClass: [
"GlobalTask"
],
properties: [
{
name: "implementation",
isAttr: true,
type: "String"
}
]
},
{
name: "ComplexBehaviorDefinition",
superClass: [
"BaseElement"
],
properties: [
{
name: "condition",
type: "FormalExpression"
},
{
name: "event",
type: "ImplicitThrowEvent"
}
]
},
{
name: "ResourceRole",
superClass: [
"BaseElement"
],
properties: [
{
name: "resourceRef",
type: "Resource",
isReference: true
},
{
name: "resourceParameterBindings",
type: "ResourceParameterBinding",
isMany: true
},
{
name: "resourceAssignmentExpression",
type: "ResourceAssignmentExpression"
},
{
name: "name",
isAttr: true,
type: "String"
}
]
},
{
name: "ResourceParameterBinding",
properties: [
{
name: "expression",
type: "Expression",
xml: {
serialize: "xsi:type"
}
},
{
name: "parameterRef",
type: "ResourceParameter",
isAttr: true,
isReference: true
}
],
superClass: [
"BaseElement"
]
},
{
name: "ResourceAssignmentExpression",
properties: [
{
name: "expression",
type: "Expression",
xml: {
serialize: "xsi:type"
}
}
],
superClass: [
"BaseElement"
]
},
{
name: "Import",
properties: [
{
name: "importType",
isAttr: true,
type: "String"
},
{
name: "location",
isAttr: true,
type: "String"
},
{
name: "namespace",
isAttr: true,
type: "String"
}
]
},
{
name: "Definitions",
superClass: [
"BaseElement"
],
properties: [
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "targetNamespace",
isAttr: true,
type: "String"
},
{
name: "expressionLanguage",
"default": "http://www.w3.org/1999/XPath",
isAttr: true,
type: "String"
},
{
name: "typeLanguage",
"default": "http://www.w3.org/2001/XMLSchema",
isAttr: true,
type: "String"
},
{
name: "imports",
type: "Import",
isMany: true
},
{
name: "extensions",
type: "Extension",
isMany: true
},
{
name: "rootElements",
type: "RootElement",
isMany: true
},
{
name: "diagrams",
isMany: true,
type: "bpmndi:BPMNDiagram"
},
{
name: "exporter",
isAttr: true,
type: "String"
},
{
name: "relationships",
type: "Relationship",
isMany: true
},
{
name: "exporterVersion",
isAttr: true,
type: "String"
}
]
}
];
var enumerations$3 = [
{
name: "ProcessType",
literalValues: [
{
name: "None"
},
{
name: "Public"
},
{
name: "Private"
}
]
},
{
name: "GatewayDirection",
literalValues: [
{
name: "Unspecified"
},
{
name: "Converging"
},
{
name: "Diverging"
},
{
name: "Mixed"
}
]
},
{
name: "EventBasedGatewayType",
literalValues: [
{
name: "Parallel"
},
{
name: "Exclusive"
}
]
},
{
name: "RelationshipDirection",
literalValues: [
{
name: "None"
},
{
name: "Forward"
},
{
name: "Backward"
},
{
name: "Both"
}
]
},
{
name: "ItemKind",
literalValues: [
{
name: "Physical"
},
{
name: "Information"
}
]
},
{
name: "ChoreographyLoopType",
literalValues: [
{
name: "None"
},
{
name: "Standard"
},
{
name: "MultiInstanceSequential"
},
{
name: "MultiInstanceParallel"
}
]
},
{
name: "AssociationDirection",
literalValues: [
{
name: "None"
},
{
name: "One"
},
{
name: "Both"
}
]
},
{
name: "MultiInstanceBehavior",
literalValues: [
{
name: "None"
},
{
name: "One"
},
{
name: "All"
},
{
name: "Complex"
}
]
},
{
name: "AdHocOrdering",
literalValues: [
{
name: "Parallel"
},
{
name: "Sequential"
}
]
}
];
var xml$1 = {
tagAlias: "lowerCase",
typePrefix: "t"
};
var BpmnPackage = {
name: name$5,
uri: uri$5,
prefix: prefix$5,
associations: associations$5,
types: types$5,
enumerations: enumerations$3,
xml: xml$1
};
var name$4 = "BPMNDI";
var uri$4 = "http://www.omg.org/spec/BPMN/20100524/DI";
var prefix$4 = "bpmndi";
var types$4 = [
{
name: "BPMNDiagram",
properties: [
{
name: "plane",
type: "BPMNPlane",
redefines: "di:Diagram#rootElement"
},
{
name: "labelStyle",
type: "BPMNLabelStyle",
isMany: true
}
],
superClass: [
"di:Diagram"
]
},
{
name: "BPMNPlane",
properties: [
{
name: "bpmnElement",
isAttr: true,
isReference: true,
type: "bpmn:BaseElement",
redefines: "di:DiagramElement#modelElement"
}
],
superClass: [
"di:Plane"
]
},
{
name: "BPMNShape",
properties: [
{
name: "bpmnElement",
isAttr: true,
isReference: true,
type: "bpmn:BaseElement",
redefines: "di:DiagramElement#modelElement"
},
{
name: "isHorizontal",
isAttr: true,
type: "Boolean"
},
{
name: "isExpanded",
isAttr: true,
type: "Boolean"
},
{
name: "isMarkerVisible",
isAttr: true,
type: "Boolean"
},
{
name: "label",
type: "BPMNLabel"
},
{
name: "isMessageVisible",
isAttr: true,
type: "Boolean"
},
{
name: "participantBandKind",
type: "ParticipantBandKind",
isAttr: true
},
{
name: "choreographyActivityShape",
type: "BPMNShape",
isAttr: true,
isReference: true
}
],
superClass: [
"di:LabeledShape"
]
},
{
name: "BPMNEdge",
properties: [
{
name: "label",
type: "BPMNLabel"
},
{
name: "bpmnElement",
isAttr: true,
isReference: true,
type: "bpmn:BaseElement",
redefines: "di:DiagramElement#modelElement"
},
{
name: "sourceElement",
isAttr: true,
isReference: true,
type: "di:DiagramElement",
redefines: "di:Edge#source"
},
{
name: "targetElement",
isAttr: true,
isReference: true,
type: "di:DiagramElement",
redefines: "di:Edge#target"
},
{
name: "messageVisibleKind",
type: "MessageVisibleKind",
isAttr: true,
"default": "initiating"
}
],
superClass: [
"di:LabeledEdge"
]
},
{
name: "BPMNLabel",
properties: [
{
name: "labelStyle",
type: "BPMNLabelStyle",
isAttr: true,
isReference: true,
redefines: "di:DiagramElement#style"
}
],
superClass: [
"di:Label"
]
},
{
name: "BPMNLabelStyle",
properties: [
{
name: "font",
type: "dc:Font"
}
],
superClass: [
"di:Style"
]
}
];
var enumerations$2 = [
{
name: "ParticipantBandKind",
literalValues: [
{
name: "top_initiating"
},
{
name: "middle_initiating"
},
{
name: "bottom_initiating"
},
{
name: "top_non_initiating"
},
{
name: "middle_non_initiating"
},
{
name: "bottom_non_initiating"
}
]
},
{
name: "MessageVisibleKind",
literalValues: [
{
name: "initiating"
},
{
name: "non_initiating"
}
]
}
];
var associations$4 = [
];
var BpmnDiPackage = {
name: name$4,
uri: uri$4,
prefix: prefix$4,
types: types$4,
enumerations: enumerations$2,
associations: associations$4
};
var name$3 = "DC";
var uri$3 = "http://www.omg.org/spec/DD/20100524/DC";
var prefix$3 = "dc";
var types$3 = [
{
name: "Boolean"
},
{
name: "Integer"
},
{
name: "Real"
},
{
name: "String"
},
{
name: "Font",
properties: [
{
name: "name",
type: "String",
isAttr: true
},
{
name: "size",
type: "Real",
isAttr: true
},
{
name: "isBold",
type: "Boolean",
isAttr: true
},
{
name: "isItalic",
type: "Boolean",
isAttr: true
},
{
name: "isUnderline",
type: "Boolean",
isAttr: true
},
{
name: "isStrikeThrough",
type: "Boolean",
isAttr: true
}
]
},
{
name: "Point",
properties: [
{
name: "x",
type: "Real",
"default": "0",
isAttr: true
},
{
name: "y",
type: "Real",
"default": "0",
isAttr: true
}
]
},
{
name: "Bounds",
properties: [
{
name: "x",
type: "Real",
"default": "0",
isAttr: true
},
{
name: "y",
type: "Real",
"default": "0",
isAttr: true
},
{
name: "width",
type: "Real",
isAttr: true
},
{
name: "height",
type: "Real",
isAttr: true
}
]
}
];
var associations$3 = [
];
var DcPackage = {
name: name$3,
uri: uri$3,
prefix: prefix$3,
types: types$3,
associations: associations$3
};
var name$2 = "DI";
var uri$2 = "http://www.omg.org/spec/DD/20100524/DI";
var prefix$2 = "di";
var types$2 = [
{
name: "DiagramElement",
isAbstract: true,
properties: [
{
name: "id",
isAttr: true,
isId: true,
type: "String"
},
{
name: "extension",
type: "Extension"
},
{
name: "owningDiagram",
type: "Diagram",
isReadOnly: true,
isVirtual: true,
isReference: true
},
{
name: "owningElement",
type: "DiagramElement",
isReadOnly: true,
isVirtual: true,
isReference: true
},
{
name: "modelElement",
isReadOnly: true,
isVirtual: true,
isReference: true,
type: "Element"
},
{
name: "style",
type: "Style",
isReadOnly: true,
isVirtual: true,
isReference: true
},
{
name: "ownedElement",
type: "DiagramElement",
isReadOnly: true,
isMany: true,
isVirtual: true
}
]
},
{
name: "Node",
isAbstract: true,
superClass: [
"DiagramElement"
]
},
{
name: "Edge",
isAbstract: true,
superClass: [
"DiagramElement"
],
properties: [
{
name: "source",
type: "DiagramElement",
isReadOnly: true,
isVirtual: true,
isReference: true
},
{
name: "target",
type: "DiagramElement",
isReadOnly: true,
isVirtual: true,
isReference: true
},
{
name: "waypoint",
isUnique: false,
isMany: true,
type: "dc:Point",
xml: {
serialize: "xsi:type"
}
}
]
},
{
name: "Diagram",
isAbstract: true,
properties: [
{
name: "id",
isAttr: true,
isId: true,
type: "String"
},
{
name: "rootElement",
type: "DiagramElement",
isReadOnly: true,
isVirtual: true
},
{
name: "name",
isAttr: true,
type: "String"
},
{
name: "documentation",
isAttr: true,
type: "String"
},
{
name: "resolution",
isAttr: true,
type: "Real"
},
{
name: "ownedStyle",
type: "Style",
isReadOnly: true,
isMany: true,
isVirtual: true
}
]
},
{
name: "Shape",
isAbstract: true,
superClass: [
"Node"
],
properties: [
{
name: "bounds",
type: "dc:Bounds"
}
]
},
{
name: "Plane",
isAbstract: true,
superClass: [
"Node"
],
properties: [
{
name: "planeElement",
type: "DiagramElement",
subsettedProperty: "DiagramElement-ownedElement",
isMany: true
}
]
},
{
name: "LabeledEdge",
isAbstract: true,
superClass: [
"Edge"
],
properties: [
{
name: "ownedLabel",
type: "Label",
isReadOnly: true,
subsettedProperty: "DiagramElement-ownedElement",
isMany: true,
isVirtual: true
}
]
},
{
name: "LabeledShape",
isAbstract: true,
superClass: [
"Shape"
],
properties: [
{
name: "ownedLabel",
type: "Label",
isReadOnly: true,
subsettedProperty: "DiagramElement-ownedElement",
isMany: true,
isVirtual: true
}
]
},
{
name: "Label",
isAbstract: true,
superClass: [
"Node"
],
properties: [
{
name: "bounds",
type: "dc:Bounds"
}
]
},
{
name: "Style",
isAbstract: true,
properties: [
{
name: "id",
isAttr: true,
isId: true,
type: "String"
}
]
},
{
name: "Extension",
properties: [
{
name: "values",
isMany: true,
type: "Element"
}
]
}
];
var associations$2 = [
];
var xml = {
tagAlias: "lowerCase"
};
var DiPackage = {
name: name$2,
uri: uri$2,
prefix: prefix$2,
types: types$2,
associations: associations$2,
xml: xml
};
var name$1 = "bpmn.io colors for BPMN";
var uri$1 = "http://bpmn.io/schema/bpmn/biocolor/1.0";
var prefix$1 = "bioc";
var types$1 = [
{
name: "ColoredShape",
"extends": [
"bpmndi:BPMNShape"
],
properties: [
{
name: "stroke",
isAttr: true,
type: "String"
},
{
name: "fill",
isAttr: true,
type: "String"
}
]
},
{
name: "ColoredEdge",
"extends": [
"bpmndi:BPMNEdge"
],
properties: [
{
name: "stroke",
isAttr: true,
type: "String"
},
{
name: "fill",
isAttr: true,
type: "String"
}
]
}
];
var enumerations$1 = [
];
var associations$1 = [
];
var BiocPackage = {
name: name$1,
uri: uri$1,
prefix: prefix$1,
types: types$1,
enumerations: enumerations$1,
associations: associations$1
};
var name = "BPMN in Color";
var uri = "http://www.omg.org/spec/BPMN/non-normative/color/1.0";
var prefix = "color";
var types = [
{
name: "ColoredLabel",
"extends": [
"bpmndi:BPMNLabel"
],
properties: [
{
name: "color",
isAttr: true,
type: "String"
}
]
},
{
name: "ColoredShape",
"extends": [
"bpmndi:BPMNShape"
],
properties: [
{
name: "background-color",
isAttr: true,
type: "String"
},
{
name: "border-color",
isAttr: true,
type: "String"
}
]
},
{
name: "ColoredEdge",
"extends": [
"bpmndi:BPMNEdge"
],
properties: [
{
name: "border-color",
isAttr: true,
type: "String"
}
]
}
];
var enumerations = [
];
var associations = [
];
var BpmnInColorPackage = {
name: name,
uri: uri,
prefix: prefix,
types: types,
enumerations: enumerations,
associations: associations
};
var packages = {
bpmn: BpmnPackage,
bpmndi: BpmnDiPackage,
dc: DcPackage,
di: DiPackage,
bioc: BiocPackage,
color: BpmnInColorPackage
};
function simple(additionalPackages, options) {
var pks = assign({}, packages, additionalPackages);
return new BpmnModdle(pks, options);
}
function elementToString(e) {
if (!e) {
return '<null>';
}
return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />';
}
// TODO(nikku): remove with future bpmn-js version
/**
* Wraps APIs to check:
*
* 1) If a callback is passed -> Warn users about callback deprecation.
* 2) If Promise class is implemented in current environment.
*
* @private
*/
function wrapForCompatibility(api) {
return function() {
if (!window.Promise) {
throw new Error('Promises is not supported in this environment. Please polyfill Promise.');
}
var argLen = arguments.length;
if (argLen >= 1 && isFunction(arguments[argLen - 1])) {
var callback = arguments[argLen - 1];
console.warn(new Error(
'Passing callbacks to ' + api.name + ' is deprecated and will be removed in a future major release. ' +
'Please switch to promises: https://bpmn.io/l/moving-to-promises.html'
));
var argsWithoutCallback = Array.prototype.slice.call(arguments, 0, -1);
api.apply(this, argsWithoutCallback).then(function(result) {
var firstKey = Object.keys(result)[0];
// The APIs we are wrapping all resolve a single item depending on the API.
// For instance, importXML resolves { warnings } and saveXML returns { xml }.
// That's why we can call the callback with the first item of result.
return callback(null, result[firstKey]);
// Passing a second paramter instead of catch because we don't want to
// catch errors thrown by callback().
}, function(err) {
return callback(err, err.warnings);
});
} else {
return api.apply(this, arguments);
}
};
}
// TODO(nikku): remove with future bpmn-js version
var DI_ERROR_MESSAGE = 'Tried to access di from the businessObject. The di is available through the diagram element only. For more information, see https://github.com/bpmn-io/bpmn-js/issues/1472';
function ensureCompatDiRef(businessObject) {
// bpmnElement can have multiple independent DIs
if (!has$1(businessObject, 'di')) {
Object.defineProperty(businessObject, 'di', {
enumerable: false,
get: function() {
throw new Error(DI_ERROR_MESSAGE);
}
});
}
}
/**
* Returns true if an element has the given meta-model type
*
* @param {ModdleElement} element
* @param {string} type
*
* @return {boolean}
*/
function is$2(element, type) {
return element.$instanceOf(type);
}
/**
* Find a suitable display candidate for definitions where the DI does not
* correctly specify one.
*/
function findDisplayCandidate(definitions) {
return find(definitions.rootElements, function(e) {
return is$2(e, 'bpmn:Process') || is$2(e, 'bpmn:Collaboration');
});
}
function BpmnTreeWalker(handler, translate) {
// list of containers already walked
var handledElements = {};
// list of elements to handle deferred to ensure
// prerequisites are drawn
var deferred = [];
var diMap = {};
// Helpers //////////////////////
function contextual(fn, ctx) {
return function(e) {
fn(e, ctx);
};
}
function handled(element) {
handledElements[element.id] = element;
}
function isHandled(element) {
return handledElements[element.id];
}
function visit(element, ctx) {
var gfx = element.gfx;
// avoid multiple rendering of elements
if (gfx) {
throw new Error(
translate('already rendered {element}', { element: elementToString(element) })
);
}
// call handler
return handler.element(element, diMap[element.id], ctx);
}
function visitRoot(element, diagram) {
return handler.root(element, diMap[element.id], diagram);
}
function visitIfDi(element, ctx) {
try {
var gfx = diMap[element.id] && visit(element, ctx);
handled(element);
return gfx;
} catch (e) {
logError(e.message, { element: element, error: e });
console.error(translate('failed to import {element}', { element: elementToString(element) }));
console.error(e);
}
}
function logError(message, context) {
handler.error(message, context);
}
// DI handling //////////////////////
function registerDi(di) {
var bpmnElement = di.bpmnElement;
if (bpmnElement) {
if (diMap[bpmnElement.id]) {
logError(
translate('multiple DI elements defined for {element}', {
element: elementToString(bpmnElement)
}),
{ element: bpmnElement }
);
} else {
diMap[bpmnElement.id] = di;
ensureCompatDiRef(bpmnElement);
}
} else {
logError(
translate('no bpmnElement referenced in {element}', {
element: elementToString(di)
}),
{ element: di }
);
}
}
function handleDiagram(diagram) {
handlePlane(diagram.plane);
}
function handlePlane(plane) {
registerDi(plane);
forEach$1(plane.planeElement, handlePlaneElement);
}
function handlePlaneElement(planeElement) {
registerDi(planeElement);
}
// Semantic handling //////////////////////
/**
* Handle definitions and return the rendered diagram (if any)
*
* @param {ModdleElement} definitions to walk and import
* @param {ModdleElement} [diagram] specific diagram to import and display
*
* @throws {Error} if no diagram to display could be found
*/
function handleDefinitions(definitions, diagram) {
// make sure we walk the correct bpmnElement
var diagrams = definitions.diagrams;
if (diagram && diagrams.indexOf(diagram) === -1) {
throw new Error(translate('diagram not part of bpmn:Definitions'));
}
if (!diagram && diagrams && diagrams.length) {
diagram = diagrams[0];
}
// no diagram -> nothing to import
if (!diagram) {
throw new Error(translate('no diagram to display'));
}
// load DI from selected diagram only
diMap = {};
handleDiagram(diagram);
var plane = diagram.plane;
if (!plane) {
throw new Error(translate(
'no plane for {element}',
{ element: elementToString(diagram) }
));
}
var rootElement = plane.bpmnElement;
// ensure we default to a suitable display candidate (process or collaboration),
// even if non is specified in DI
if (!rootElement) {
rootElement = findDisplayCandidate(definitions);
if (!rootElement) {
throw new Error(translate('no process or collaboration to display'));
} else {
logError(
translate('correcting missing bpmnElement on {plane} to {rootElement}', {
plane: elementToString(plane),
rootElement: elementToString(rootElement)
})
);
// correct DI on the fly
plane.bpmnElement = rootElement;
registerDi(plane);
}
}
var ctx = visitRoot(rootElement, plane);
if (is$2(rootElement, 'bpmn:Process') || is$2(rootElement, 'bpmn:SubProcess')) {
handleProcess(rootElement, ctx);
} else if (is$2(rootElement, 'bpmn:Collaboration')) {
handleCollaboration(rootElement, ctx);
// force drawing of everything not yet drawn that is part of the target DI
handleUnhandledProcesses(definitions.rootElements, ctx);
} else {
throw new Error(
translate('unsupported bpmnElement for {plane}: {rootElement}', {
plane: elementToString(plane),
rootElement: elementToString(rootElement)
})
);
}
// handle all deferred elements
handleDeferred();
}
function handleDeferred() {
var fn;
// drain deferred until empty
while (deferred.length) {
fn = deferred.shift();
fn();
}
}
function handleProcess(process, context) {
handleFlowElementsContainer(process, context);
handleIoSpecification(process.ioSpecification, context);
handleArtifacts(process.artifacts, context);
// log process handled
handled(process);
}
function handleUnhandledProcesses(rootElements, ctx) {
// walk through all processes that have not yet been drawn and draw them
// if they contain lanes with DI information.
// we do this to pass the free-floating lane test cases in the MIWG test suite
var processes = filter(rootElements, function(e) {
return !isHandled(e) && is$2(e, 'bpmn:Process') && e.laneSets;
});
processes.forEach(contextual(handleProcess, ctx));
}
function handleMessageFlow(messageFlow, context) {
visitIfDi(messageFlow, context);
}
function handleMessageFlows(messageFlows, context) {
forEach$1(messageFlows, contextual(handleMessageFlow, context));
}
function handleDataAssociation(association, context) {
visitIfDi(association, context);
}
function handleDataInput(dataInput, context) {
visitIfDi(dataInput, context);
}
function handleDataOutput(dataOutput, context) {
visitIfDi(dataOutput, context);
}
function handleArtifact(artifact, context) {
// bpmn:TextAnnotation
// bpmn:Group
// bpmn:Association
visitIfDi(artifact, context);
}
function handleArtifacts(artifacts, context) {
forEach$1(artifacts, function(e) {
if (is$2(e, 'bpmn:Association')) {
deferred.push(function() {
handleArtifact(e, context);
});
} else {
handleArtifact(e, context);
}
});
}
function handleIoSpecification(ioSpecification, context) {
if (!ioSpecification) {
return;
}
forEach$1(ioSpecification.dataInputs, contextual(handleDataInput, context));
forEach$1(ioSpecification.dataOutputs, contextual(handleDataOutput, context));
}
function handleSubProcess(subProcess, context) {
handleFlowElementsContainer(subProcess, context);
handleArtifacts(subProcess.artifacts, context);
}
function handleFlowNode(flowNode, context) {
var childCtx = visitIfDi(flowNode, context);
if (is$2(flowNode, 'bpmn:SubProcess')) {
handleSubProcess(flowNode, childCtx || context);
}
if (is$2(flowNode, 'bpmn:Activity')) {
handleIoSpecification(flowNode.ioSpecification, context);
}
// defer handling of associations
// affected types:
//
// * bpmn:Activity
// * bpmn:ThrowEvent
// * bpmn:CatchEvent
//
deferred.push(function() {
forEach$1(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
forEach$1(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
});
}
function handleSequenceFlow(sequenceFlow, context) {
visitIfDi(sequenceFlow, context);
}
function handleDataElement(dataObject, context) {
visitIfDi(dataObject, context);
}
function handleLane(lane, context) {
deferred.push(function() {
var newContext = visitIfDi(lane, context);
if (lane.childLaneSet) {
handleLaneSet(lane.childLaneSet, newContext || context);
}
wireFlowNodeRefs(lane);
});
}
function handleLaneSet(laneSet, context) {
forEach$1(laneSet.lanes, contextual(handleLane, context));
}
function handleLaneSets(laneSets, context) {
forEach$1(laneSets, contextual(handleLaneSet, context));
}
function handleFlowElementsContainer(container, context) {
handleFlowElements(container.flowElements, context);
if (container.laneSets) {
handleLaneSets(container.laneSets, context);
}
}
function handleFlowElements(flowElements, context) {
forEach$1(flowElements, function(e) {
if (is$2(e, 'bpmn:SequenceFlow')) {
deferred.push(function() {
handleSequenceFlow(e, context);
});
} else if (is$2(e, 'bpmn:BoundaryEvent')) {
deferred.unshift(function() {
handleFlowNode(e, context);
});
} else if (is$2(e, 'bpmn:FlowNode')) {
handleFlowNode(e, context);
} else if (is$2(e, 'bpmn:DataObject')) ; else if (is$2(e, 'bpmn:DataStoreReference')) {
handleDataElement(e, context);
} else if (is$2(e, 'bpmn:DataObjectReference')) {
handleDataElement(e, context);
} else {
logError(
translate('unrecognized flowElement {element} in context {context}', {
element: elementToString(e),
context: (context ? elementToString(context.businessObject) : 'null')
}),
{ element: e, context: context }
);
}
});
}
function handleParticipant(participant, context) {
var newCtx = visitIfDi(participant, context);
var process = participant.processRef;
if (process) {
handleProcess(process, newCtx || context);
}
}
function handleCollaboration(collaboration, context) {
forEach$1(collaboration.participants, contextual(handleParticipant, context));
handleArtifacts(collaboration.artifacts, context);
// handle message flows latest in the process
deferred.push(function() {
handleMessageFlows(collaboration.messageFlows, context);
});
}
function wireFlowNodeRefs(lane) {
// wire the virtual flowNodeRefs <-> relationship
forEach$1(lane.flowNodeRef, function(flowNode) {
var lanes = flowNode.get('lanes');
if (lanes) {
lanes.push(lane);
}
});
}
// API //////////////////////
return {
handleDeferred: handleDeferred,
handleDefinitions: handleDefinitions,
handleSubProcess: handleSubProcess,
registerDi: registerDi
};
}
/**
* Is an element of the given BPMN type?
*
* @param {djs.model.Base|ModdleElement} element
* @param {string} type
*
* @return {boolean}
*/
function is$1(element, type) {
var bo = getBusinessObject(element);
return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type);
}
/**
* Return true if element has any of the given types.
*
* @param {djs.model.Base} element
* @param {Array<string>} types
*
* @return {boolean}
*/
function isAny(element, types) {
return some(types, function(t) {
return is$1(element, t);
});
}
/**
* Return the business object for a given element.
*
* @param {djs.model.Base|ModdleElement} element
*
* @return {ModdleElement}
*/
function getBusinessObject(element) {
return (element && element.businessObject) || element;
}
/**
* Return the di object for a given element.
*
* @param {djs.model.Base} element
*
* @return {ModdleElement}
*/
function getDi(element) {
return element && element.di;
}
/**
* The importBpmnDiagram result.
*
* @typedef {Object} ImportBPMNDiagramResult
*
* @property {Array<string>} warnings
*/
/**
* The importBpmnDiagram error.
*
* @typedef {Error} ImportBPMNDiagramError
*
* @property {Array<string>} warnings
*/
/**
* Import the definitions into a diagram.
*
* Errors and warnings are reported through the specified callback.
*
* @param {djs.Diagram} diagram
* @param {ModdleElement<Definitions>} definitions
* @param {ModdleElement<BPMNDiagram>} [bpmnDiagram] the diagram to be rendered
* (if not provided, the first one will be rendered)
*
* Returns {Promise<ImportBPMNDiagramResult, ImportBPMNDiagramError>}
*/
function importBpmnDiagram(diagram, definitions, bpmnDiagram) {
var importer,
eventBus,
translate,
canvas;
var error,
warnings = [];
/**
* Walk the diagram semantically, importing (=drawing)
* all elements you encounter.
*
* @param {ModdleElement<Definitions>} definitions
* @param {ModdleElement<BPMNDiagram>} bpmnDiagram
*/
function render(definitions, bpmnDiagram) {
var visitor = {
root: function(element, di) {
return importer.add(element, di);
},
element: function(element, di, parentShape) {
return importer.add(element, di, parentShape);
},
error: function(message, context) {
warnings.push({ message: message, context: context });
}
};
var walker = new BpmnTreeWalker(visitor, translate);
bpmnDiagram = bpmnDiagram || (definitions.diagrams && definitions.diagrams[0]);
var diagramsToImport = getDiagramsToImport(definitions, bpmnDiagram);
if (!diagramsToImport) {
throw new Error(translate('no diagram to display'));
}
// traverse BPMN 2.0 document model,
// starting at definitions
forEach$1(diagramsToImport, function(diagram) {
walker.handleDefinitions(definitions, diagram);
});
var rootId = bpmnDiagram.plane.bpmnElement.id;
// we do need to account for different ways we create root elements
// each nested imported <root> do have the `_plane` suffix, while
// the root <root> is found under the business object ID
canvas.setRootElement(
canvas.findRoot(rootId + '_plane') || canvas.findRoot(rootId)
);
}
return new Promise(function(resolve, reject) {
try {
importer = diagram.get('bpmnImporter');
eventBus = diagram.get('eventBus');
translate = diagram.get('translate');
canvas = diagram.get('canvas');
eventBus.fire('import.render.start', { definitions: definitions });
render(definitions, bpmnDiagram);
eventBus.fire('import.render.complete', {
error: error,
warnings: warnings
});
return resolve({ warnings: warnings });
} catch (e) {
e.warnings = warnings;
return reject(e);
}
});
}
/**
* Returns all diagrams in the same hierarchy as the requested diagram.
* Includes all parent and sub process diagrams.
*
* @param {Array} definitions
* @param {Object} bpmnDiagram
*
* @returns {Array<Object>}
*/
function getDiagramsToImport(definitions, bpmnDiagram) {
if (!bpmnDiagram) {
return;
}
var bpmnElement = bpmnDiagram.plane.bpmnElement,
rootElement = bpmnElement;
if (!is$1(bpmnElement, 'bpmn:Process') && !is$1(bpmnElement, 'bpmn:Collaboration')) {
rootElement = findRootProcess(bpmnElement);
}
// in case the process is part of a collaboration, the plane references the
// collaboration, not the process
var collaboration;
if (is$1(rootElement, 'bpmn:Collaboration')) {
collaboration = rootElement;
} else {
collaboration = find(definitions.rootElements, function(element) {
if (!is$1(element, 'bpmn:Collaboration')) {
return;
}
return find(element.participants, function(participant) {
return participant.processRef === rootElement;
});
});
}
var rootElements = [ rootElement ];
// all collaboration processes can contain sub-diagrams
if (collaboration) {
rootElements = map(collaboration.participants, function(participant) {
return participant.processRef;
});
rootElements.push(collaboration);
}
var allChildren = selfAndAllFlowElements(rootElements);
// if we have multiple diagrams referencing the same element, we
// use the first in the file
var diagramsToImport = [ bpmnDiagram ];
var handledElements = [ bpmnElement ];
forEach$1(definitions.diagrams, function(diagram) {
var businessObject = diagram.plane.bpmnElement;
if (
allChildren.indexOf(businessObject) !== -1 &&
handledElements.indexOf(businessObject) === -1
) {
diagramsToImport.push(diagram);
handledElements.push(businessObject);
}
});
return diagramsToImport;
}
function selfAndAllFlowElements(elements) {
var result = [];
forEach$1(elements, function(element) {
if (!element) {
return;
}
result.push(element);
result = result.concat(selfAndAllFlowElements(element.flowElements));
});
return result;
}
function findRootProcess(element) {
var parent = element;
while (parent) {
if (is$1(parent, 'bpmn:Process')) {
return parent;
}
parent = parent.$parent;
}
}
/**
* This file must not be changed or exchanged.
*
* @see http://bpmn.io/license for more information.
*/
// inlined ../../resources/logo.svg
var BPMNIO_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14.02 5.57" width="53" height="21"><path fill="currentColor" d="M1.88.92v.14c0 .41-.13.68-.4.8.33.14.46.44.46.86v.33c0 .61-.33.95-.95.95H0V0h.95c.65 0 .93.3.93.92zM.63.57v1.06h.24c.24 0 .38-.1.38-.43V.98c0-.28-.1-.4-.32-.4zm0 1.63v1.22h.36c.2 0 .32-.1.32-.39v-.35c0-.37-.12-.48-.4-.48H.63zM4.18.99v.52c0 .64-.31.98-.94.98h-.3V4h-.62V0h.92c.63 0 .94.35.94.99zM2.94.57v1.35h.3c.2 0 .3-.09.3-.37v-.6c0-.29-.1-.38-.3-.38h-.3zm2.89 2.27L6.25 0h.88v4h-.6V1.12L6.1 3.99h-.6l-.46-2.82v2.82h-.55V0h.87zM8.14 1.1V4h-.56V0h.79L9 2.4V0h.56v4h-.64zm2.49 2.29v.6h-.6v-.6zM12.12 1c0-.63.33-1 .95-1 .61 0 .95.37.95 1v2.04c0 .64-.34 1-.95 1-.62 0-.95-.37-.95-1zm.62 2.08c0 .28.13.39.33.39s.32-.1.32-.4V.98c0-.29-.12-.4-.32-.4s-.33.11-.33.4z"/><path fill="currentColor" d="M0 4.53h14.02v1.04H0zM11.08 0h.63v.62h-.63zm.63 4V1h-.63v2.98z"/></svg>';
var BPMNIO_IMG = BPMNIO_LOGO_SVG;
var LOGO_STYLES = {
verticalAlign: 'middle'
};
var LINK_STYLES = {
'color': '#404040'
};
var LIGHTBOX_STYLES = {
'zIndex': '1001',
'position': 'fixed',
'top': '0',
'left': '0',
'right': '0',
'bottom': '0'
};
var BACKDROP_STYLES = {
'width': '100%',
'height': '100%',
'background': 'rgba(40,40,40,0.2)'
};
var NOTICE_STYLES = {
'position': 'absolute',
'left': '50%',
'top': '40%',
'transform': 'translate(-50%)',
'width': '260px',
'padding': '10px',
'background': 'white',
'boxShadow': '0 1px 4px rgba(0,0,0,0.3)',
'fontFamily': 'Helvetica, Arial, sans-serif',
'fontSize': '14px',
'display': 'flex',
'lineHeight': '1.3'
};
var LIGHTBOX_MARKUP =
'<div class="bjs-powered-by-lightbox">' +
'<div class="backdrop"></div>' +
'<div class="notice">' +
'<a href="https://bpmn.io" target="_blank" rel="noopener" class="link">' +
BPMNIO_IMG +
'</a>' +
'<span>' +
'Web-based tooling for BPMN, DMN and CMMN diagrams ' +
'powered by <a href="https://bpmn.io" target="_blank" rel="noopener">bpmn.io</a>.' +
'</span>' +
'</div>' +
'</div>';
var lightbox;
function createLightbox() {
lightbox = domify(LIGHTBOX_MARKUP);
assign$1(lightbox, LIGHTBOX_STYLES);
assign$1(query('svg', lightbox), LOGO_STYLES);
assign$1(query('.backdrop', lightbox), BACKDROP_STYLES);
assign$1(query('.notice', lightbox), NOTICE_STYLES);
assign$1(query('.link', lightbox), LINK_STYLES, {
'margin': '15px 20px 15px 10px',
'alignSelf': 'center'
});
}
function open() {
if (!lightbox) {
createLightbox();
delegate.bind(lightbox, '.backdrop', 'click', function(event) {
document.body.removeChild(lightbox);
});
}
document.body.appendChild(lightbox);
}
/**
* The code in the <project-logo></project-logo> area
* must not be changed.
*
* @see http://bpmn.io/license for more information.
*/
/**
* A base viewer for BPMN 2.0 diagrams.
*
* Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for
* bundles that include actual features.
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {string|number} [options.width] the width of the viewer
* @param {string|number} [options.height] the height of the viewer
* @param {Object} [options.moddleExtensions] extension packages to provide
* @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
* @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
*/
function BaseViewer(options) {
options = assign({}, DEFAULT_OPTIONS, options);
this._moddle = this._createModdle(options);
this._container = this._createContainer(options);
/* <project-logo> */
addProjectLogo(this._container);
/* </project-logo> */
this._init(this._container, this._moddle, options);
}
e(BaseViewer, Diagram);
/**
* The importXML result.
*
* @typedef {Object} ImportXMLResult
*
* @property {Array<string>} warnings
*/
/**
* The importXML error.
*
* @typedef {Error} ImportXMLError
*
* @property {Array<string>} warnings
*/
/**
* Parse and render a BPMN 2.0 diagram.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During import the viewer will fire life-cycle events:
*
* * import.parse.start (about to read model from xml)
* * import.parse.complete (model read; may have worked or not)
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
* * import.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @param {string} xml the BPMN 2.0 xml
* @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
*
* Returns {Promise<ImportXMLResult, ImportXMLError>}
*/
BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bpmnDiagram) {
var self = this;
function ParseCompleteEvent(data) {
var event = self.get('eventBus').createEvent(data);
// TODO(nikku): remove with future bpmn-js version
Object.defineProperty(event, 'context', {
enumerable: true,
get: function() {
console.warn(new Error(
'import.parse.complete <context> is deprecated ' +
'and will be removed in future library versions'
));
return {
warnings: data.warnings,
references: data.references,
elementsById: data.elementsById
};
}
});
return event;
}
return new Promise(function(resolve, reject) {
// hook in pre-parse listeners +
// allow xml manipulation
xml = self._emit('import.parse.start', { xml: xml }) || xml;
self._moddle.fromXML(xml, 'bpmn:Definitions').then(function(result) {
var definitions = result.rootElement;
var references = result.references;
var parseWarnings = result.warnings;
var elementsById = result.elementsById;
// hook in post parse listeners +
// allow definitions manipulation
definitions = self._emit('import.parse.complete', ParseCompleteEvent({
error: null,
definitions: definitions,
elementsById: elementsById,
references: references,
warnings: parseWarnings
})) || definitions;
self.importDefinitions(definitions, bpmnDiagram).then(function(result) {
var allWarnings = [].concat(parseWarnings, result.warnings || []);
self._emit('import.done', { error: null, warnings: allWarnings });
return resolve({ warnings: allWarnings });
}).catch(function(err) {
var allWarnings = [].concat(parseWarnings, err.warnings || []);
self._emit('import.done', { error: err, warnings: allWarnings });
return reject(addWarningsToError(err, allWarnings));
});
}).catch(function(err) {
self._emit('import.parse.complete', {
error: err
});
err = checkValidationError(err);
self._emit('import.done', { error: err, warnings: err.warnings });
return reject(err);
});
});
});
/**
* The importDefinitions result.
*
* @typedef {Object} ImportDefinitionsResult
*
* @property {Array<string>} warnings
*/
/**
* The importDefinitions error.
*
* @typedef {Error} ImportDefinitionsError
*
* @property {Array<string>} warnings
*/
/**
* Import parsed definitions and render a BPMN 2.0 diagram.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During import the viewer will fire life-cycle events:
*
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
*
* You can use these events to hook into the life-cycle.
*
* @param {ModdleElement<Definitions>} definitions parsed BPMN 2.0 definitions
* @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
*
* Returns {Promise<ImportDefinitionsResult, ImportDefinitionsError>}
*/
BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDefinitions(definitions, bpmnDiagram) {
var self = this;
return new Promise(function(resolve, reject) {
self._setDefinitions(definitions);
self.open(bpmnDiagram).then(function(result) {
var warnings = result.warnings;
return resolve({ warnings: warnings });
}).catch(function(err) {
return reject(err);
});
});
});
/**
* The open result.
*
* @typedef {Object} OpenResult
*
* @property {Array<string>} warnings
*/
/**
* The open error.
*
* @typedef {Error} OpenError
*
* @property {Array<string>} warnings
*/
/**
* Open diagram of previously imported XML.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During switch the viewer will fire life-cycle events:
*
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
*
* You can use these events to hook into the life-cycle.
*
* @param {string|ModdleElement<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open
*
* Returns {Promise<OpenResult, OpenError>}
*/
BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId) {
var definitions = this._definitions;
var bpmnDiagram = bpmnDiagramOrId;
var self = this;
return new Promise(function(resolve, reject) {
if (!definitions) {
var err1 = new Error('no XML imported');
return reject(addWarningsToError(err1, []));
}
if (typeof bpmnDiagramOrId === 'string') {
bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
if (!bpmnDiagram) {
var err2 = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');
return reject(addWarningsToError(err2, []));
}
}
// clear existing rendered diagram
// catch synchronous exceptions during #clear()
try {
self.clear();
} catch (error) {
return reject(addWarningsToError(error, []));
}
// perform graphical import
importBpmnDiagram(self, definitions, bpmnDiagram).then(function(result) {
var warnings = result.warnings;
return resolve({ warnings: warnings });
}).catch(function(err) {
return reject(err);
});
});
});
/**
* The saveXML result.
*
* @typedef {Object} SaveXMLResult
*
* @property {string} xml
*/
/**
* Export the currently displayed BPMN 2.0 diagram as
* a BPMN 2.0 XML document.
*
* ## Life-Cycle Events
*
* During XML saving the viewer will fire life-cycle events:
*
* * saveXML.start (before serialization)
* * saveXML.serialized (after xml generation)
* * saveXML.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @param {Object} [options] export options
* @param {boolean} [options.format=false] output formatted XML
* @param {boolean} [options.preamble=true] output preamble
*
* Returns {Promise<SaveXMLResult, Error>}
*/
BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options) {
options = options || {};
var self = this;
var definitions = this._definitions;
return new Promise(function(resolve) {
if (!definitions) {
return resolve({
error: new Error('no definitions loaded')
});
}
// allow to fiddle around with definitions
definitions = self._emit('saveXML.start', {
definitions: definitions
}) || definitions;
self._moddle.toXML(definitions, options).then(function(result) {
var xml = result.xml;
xml = self._emit('saveXML.serialized', {
xml: xml
}) || xml;
return resolve({
xml: xml
});
});
}).catch(function(error) {
return { error: error };
}).then(function(result) {
self._emit('saveXML.done', result);
var error = result.error;
if (error) {
return Promise.reject(error);
}
return result;
});
});
/**
* The saveSVG result.
*
* @typedef {Object} SaveSVGResult
*
* @property {string} svg
*/
/**
* Export the currently displayed BPMN 2.0 diagram as
* an SVG image.
*
* ## Life-Cycle Events
*
* During SVG saving the viewer will fire life-cycle events:
*
* * saveSVG.start (before serialization)
* * saveSVG.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @param {Object} [options]
*
* Returns {Promise<SaveSVGResult, Error>}
*/
BaseViewer.prototype.saveSVG = wrapForCompatibility(function saveSVG(options) {
var self = this;
return new Promise(function(resolve, reject) {
self._emit('saveSVG.start');
var svg, err;
try {
var canvas = self.get('canvas');
var contentNode = canvas.getActiveLayer(),
defsNode = query('defs', canvas._svg);
var contents = innerSVG(contentNode),
defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
var bbox = contentNode.getBBox();
svg =
'<?xml version="1.0" encoding="utf-8"?>\n' +
'<!-- created with bpmn-js / http://bpmn.io -->\n' +
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
'width="' + bbox.width + '" height="' + bbox.height + '" ' +
'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
defs + contents +
'</svg>';
} catch (e) {
err = e;
}
self._emit('saveSVG.done', {
error: err,
svg: svg
});
if (!err) {
return resolve({ svg: svg });
}
return reject(err);
});
});
/**
* Get a named diagram service.
*
* @example
*
* var elementRegistry = viewer.get('elementRegistry');
* var startEventShape = elementRegistry.get('StartEvent_1');
*
* @param {string} name
*
* @return {Object} diagram service instance
*
* @method BaseViewer#get
*/
/**
* Invoke a function in the context of this viewer.
*
* @example
*
* viewer.invoke(function(elementRegistry) {
* var startEventShape = elementRegistry.get('StartEvent_1');
* });
*
* @param {Function} fn to be invoked
*
* @return {Object} the functions return value
*
* @method BaseViewer#invoke
*/
BaseViewer.prototype._setDefinitions = function(definitions) {
this._definitions = definitions;
};
BaseViewer.prototype.getModules = function() {
return this._modules;
};
/**
* Remove all drawn elements from the viewer.
*
* After calling this method the viewer can still
* be reused for opening another diagram.
*
* @method BaseViewer#clear
*/
BaseViewer.prototype.clear = function() {
if (!this.getDefinitions()) {
// no diagram to clear
return;
}
// remove drawn elements
Diagram.prototype.clear.call(this);
};
/**
* Destroy the viewer instance and remove all its
* remainders from the document tree.
*/
BaseViewer.prototype.destroy = function() {
// diagram destroy
Diagram.prototype.destroy.call(this);
// dom detach
remove$2(this._container);
};
/**
* Register an event listener
*
* Remove a previously added listener via {@link #off(event, callback)}.
*
* @param {string} event
* @param {number} [priority]
* @param {Function} callback
* @param {Object} [that]
*/
BaseViewer.prototype.on = function(event, priority, callback, target) {
return this.get('eventBus').on(event, priority, callback, target);
};
/**
* De-register an event listener
*
* @param {string} event
* @param {Function} callback
*/
BaseViewer.prototype.off = function(event, callback) {
this.get('eventBus').off(event, callback);
};
BaseViewer.prototype.attachTo = function(parentNode) {
if (!parentNode) {
throw new Error('parentNode required');
}
// ensure we detach from the
// previous, old parent
this.detach();
// unwrap jQuery if provided
if (parentNode.get && parentNode.constructor.prototype.jquery) {
parentNode = parentNode.get(0);
}
if (typeof parentNode === 'string') {
parentNode = query(parentNode);
}
parentNode.appendChild(this._container);
this._emit('attach', {});
this.get('canvas').resized();
};
BaseViewer.prototype.getDefinitions = function() {
return this._definitions;
};
BaseViewer.prototype.detach = function() {
var container = this._container,
parentNode = container.parentNode;
if (!parentNode) {
return;
}
this._emit('detach', {});
parentNode.removeChild(container);
};
BaseViewer.prototype._init = function(container, moddle, options) {
var baseModules = options.modules || this.getModules(),
additionalModules = options.additionalModules || [],
staticModules = [
{
bpmnjs: [ 'value', this ],
moddle: [ 'value', moddle ]
}
];
var diagramModules = [].concat(staticModules, baseModules, additionalModules);
var diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
canvas: assign({}, options.canvas, { container: container }),
modules: diagramModules
});
// invoke diagram constructor
Diagram.call(this, diagramOptions);
if (options && options.container) {
this.attachTo(options.container);
}
};
/**
* Emit an event on the underlying {@link EventBus}
*
* @param {string} type
* @param {Object} event
*
* @return {Object} event processing result (if any)
*/
BaseViewer.prototype._emit = function(type, event) {
return this.get('eventBus').fire(type, event);
};
BaseViewer.prototype._createContainer = function(options) {
var container = domify('<div class="bjs-container"></div>');
assign$1(container, {
width: ensureUnit(options.width),
height: ensureUnit(options.height),
position: options.position
});
return container;
};
BaseViewer.prototype._createModdle = function(options) {
var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
return new simple(moddleOptions);
};
BaseViewer.prototype._modules = [];
// helpers ///////////////
function addWarningsToError(err, warningsAry) {
err.warnings = warningsAry;
return err;
}
function checkValidationError(err) {
// check if we can help the user by indicating wrong BPMN 2.0 xml
// (in case he or the exporting tool did not get that right)
var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
var match = pattern.exec(err.message);
if (match) {
err.message =
'unparsable content <' + match[1] + '> detected; ' +
'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
}
return err;
}
var DEFAULT_OPTIONS = {
width: '100%',
height: '100%',
position: 'relative'
};
/**
* Ensure the passed argument is a proper unit (defaulting to px)
*/
function ensureUnit(val) {
return val + (isNumber(val) ? 'px' : '');
}
/**
* Find BPMNDiagram in definitions by ID
*
* @param {ModdleElement<Definitions>} definitions
* @param {string} diagramId
*
* @return {ModdleElement<BPMNDiagram>|null}
*/
function findBPMNDiagram(definitions, diagramId) {
if (!diagramId) {
return null;
}
return find(definitions.diagrams, function(element) {
return element.id === diagramId;
}) || null;
}
/**
* Adds the project logo to the diagram container as
* required by the bpmn.io license.
*
* @see http://bpmn.io/license
*
* @param {Element} container
*/
function addProjectLogo(container) {
var img = BPMNIO_IMG;
var linkMarkup =
'<a href="http://bpmn.io" ' +
'target="_blank" ' +
'class="bjs-powered-by" ' +
'title="Powered by bpmn.io" ' +
'>' +
img +
'</a>';
var linkElement = domify(linkMarkup);
assign$1(query('svg', linkElement), LOGO_STYLES);
assign$1(linkElement, LINK_STYLES, {
position: 'absolute',
bottom: '15px',
right: '15px',
zIndex: '100'
});
container.appendChild(linkElement);
componentEvent.bind(linkElement, 'click', function(event) {
open();
event.preventDefault();
});
}
/* </project-logo> */
/**
* A base modeler for BPMN 2.0 diagrams.
*
* Have a look at {@link Modeler} for a bundle that includes actual features.
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {string|number} [options.width] the width of the viewer
* @param {string|number} [options.height] the height of the viewer
* @param {Object} [options.moddleExtensions] extension packages to provide
* @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
* @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
*/
function BaseModeler(options) {
BaseViewer.call(this, options);
// hook ID collection into the modeler
this.on('import.parse.complete', function(event) {
if (!event.error) {
this._collectIds(event.definitions, event.elementsById);
}
}, this);
this.on('diagram.destroy', function() {
this.get('moddle').ids.clear();
}, this);
}
e(BaseModeler, BaseViewer);
/**
* Create a moddle instance, attaching ids to it.
*
* @param {Object} options
*/
BaseModeler.prototype._createModdle = function(options) {
var moddle = BaseViewer.prototype._createModdle.call(this, options);
// attach ids to moddle to be able to track
// and validated ids in the BPMN 2.0 XML document
// tree
moddle.ids = new Ids([ 32, 36, 1 ]);
return moddle;
};
/**
* Collect ids processed during parsing of the
* definitions object.
*
* @param {ModdleElement} definitions
* @param {Context} context
*/
BaseModeler.prototype._collectIds = function(definitions, elementsById) {
var moddle = definitions.$model,
ids = moddle.ids,
id;
// remove references from previous import
ids.clear();
for (id in elementsById) {
ids.claim(id, elementsById[id]);
}
};
function isExpanded(element, di) {
if (is$1(element, 'bpmn:CallActivity')) {
return false;
}
if (is$1(element, 'bpmn:SubProcess')) {
di = di || getDi(element);
if (di && is$1(di, 'bpmndi:BPMNPlane')) {
return true;
}
return di && !!di.isExpanded;
}
if (is$1(element, 'bpmn:Participant')) {
return !!getBusinessObject(element).processRef;
}
return true;
}
function isInterrupting(element) {
return element && getBusinessObject(element).isInterrupting !== false;
}
function isEventSubProcess(element) {
return element && !!getBusinessObject(element).triggeredByEvent;
}
function hasEventDefinition$2(element, eventType) {
var bo = getBusinessObject(element),
hasEventDefinition = false;
if (bo.eventDefinitions) {
forEach$1(bo.eventDefinitions, function(event) {
if (is$1(event, eventType)) {
hasEventDefinition = true;
}
});
}
return hasEventDefinition;
}
function hasErrorEventDefinition(element) {
return hasEventDefinition$2(element, 'bpmn:ErrorEventDefinition');
}
function hasEscalationEventDefinition(element) {
return hasEventDefinition$2(element, 'bpmn:EscalationEventDefinition');
}
function hasCompensateEventDefinition(element) {
return hasEventDefinition$2(element, 'bpmn:CompensateEventDefinition');
}
function getLabelAttr(semantic) {
if (
is$1(semantic, 'bpmn:FlowElement') ||
is$1(semantic, 'bpmn:Participant') ||
is$1(semantic, 'bpmn:Lane') ||
is$1(semantic, 'bpmn:SequenceFlow') ||
is$1(semantic, 'bpmn:MessageFlow') ||
is$1(semantic, 'bpmn:DataInput') ||
is$1(semantic, 'bpmn:DataOutput')
) {
return 'name';
}
if (is$1(semantic, 'bpmn:TextAnnotation')) {
return 'text';
}
if (is$1(semantic, 'bpmn:Group')) {
return 'categoryValueRef';
}
}
function getCategoryValue(semantic) {
var categoryValueRef = semantic['categoryValueRef'];
if (!categoryValueRef) {
return '';
}
return categoryValueRef.value || '';
}
function getLabel(element) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
return getCategoryValue(semantic);
}
return semantic[attr] || '';
}
}
function setLabel(element, text, isExternal) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
semantic['categoryValueRef'].value = text;
} else {
semantic[attr] = text;
}
}
return element;
}
var black = 'hsl(225, 10%, 15%)';
// element utils //////////////////////
/**
* Checks if eventDefinition of the given element matches with semantic type.
*
* @return {boolean} true if element is of the given semantic type
*/
function isTypedEvent(event, eventDefinitionType, filter) {
function matches(definition, filter) {
return every(filter, function(val, key) {
// we want a == conversion here, to be able to catch
// undefined == false and friends
/* jshint -W116 */
return definition[key] == val;
});
}
return some(event.eventDefinitions, function(definition) {
return definition.$type === eventDefinitionType && matches(event, filter);
});
}
function isThrowEvent(event) {
return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent');
}
function isCollection(element) {
var dataObject = element.dataObjectRef;
return element.isCollection || (dataObject && dataObject.isCollection);
}
function getSemantic(element) {
return element.businessObject;
}
// color access //////////////////////
function getFillColor(element, defaultColor) {
var di = getDi(element);
return di.get('color:background-color') || di.get('bioc:fill') || defaultColor || 'white';
}
function getStrokeColor$1(element, defaultColor) {
var di = getDi(element);
return di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || black;
}
function getLabelColor(element, defaultColor, defaultStrokeColor) {
var di = getDi(element),
label = di.get('label');
return label && label.get('color:color') || defaultColor ||
getStrokeColor$1(element, defaultStrokeColor);
}
// cropping path customizations //////////////////////
function getCirclePath(shape) {
var cx = shape.x + shape.width / 2,
cy = shape.y + shape.height / 2,
radius = shape.width / 2;
var circlePath = [
[ 'M', cx, cy ],
[ 'm', 0, -radius ],
[ 'a', radius, radius, 0, 1, 1, 0, 2 * radius ],
[ 'a', radius, radius, 0, 1, 1, 0, -2 * radius ],
[ 'z' ]
];
return componentsToPath(circlePath);
}
function getRoundRectPath(shape, borderRadius) {
var x = shape.x,
y = shape.y,
width = shape.width,
height = shape.height;
var roundRectPath = [
[ 'M', x + borderRadius, y ],
[ 'l', width - borderRadius * 2, 0 ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius ],
[ 'l', 0, height - borderRadius * 2 ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius ],
[ 'l', borderRadius * 2 - width, 0 ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius ],
[ 'l', 0, borderRadius * 2 - height ],
[ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius ],
[ 'z' ]
];
return componentsToPath(roundRectPath);
}
function getDiamondPath(shape) {
var width = shape.width,
height = shape.height,
x = shape.x,
y = shape.y,
halfWidth = width / 2,
halfHeight = height / 2;
var diamondPath = [
[ 'M', x + halfWidth, y ],
[ 'l', halfWidth, halfHeight ],
[ 'l', -halfWidth, halfHeight ],
[ 'l', -halfWidth, -halfHeight ],
[ 'z' ]
];
return componentsToPath(diamondPath);
}
function getRectPath(shape) {
var x = shape.x,
y = shape.y,
width = shape.width,
height = shape.height;
var rectPath = [
[ 'M', x, y ],
[ 'l', width, 0 ],
[ 'l', 0, height ],
[ 'l', -width, 0 ],
[ 'z' ]
];
return componentsToPath(rectPath);
}
var RENDERER_IDS = new Ids();
var TASK_BORDER_RADIUS = 10;
var INNER_OUTER_DIST = 3;
var DEFAULT_FILL_OPACITY = .95,
HIGH_FILL_OPACITY = .35;
var ELEMENT_LABEL_DISTANCE$1 = 10;
function BpmnRenderer(
config, eventBus, styles, pathMap,
canvas, textRenderer, priority) {
BaseRenderer.call(this, eventBus, priority);
var defaultFillColor = config && config.defaultFillColor,
defaultStrokeColor = config && config.defaultStrokeColor,
defaultLabelColor = config && config.defaultLabelColor;
var rendererId = RENDERER_IDS.next();
var markers = {};
var computeStyle = styles.computeStyle;
function addMarker(id, options) {
var attrs = assign({
fill: black,
strokeWidth: 1,
strokeLinecap: 'round',
strokeDasharray: 'none'
}, options.attrs);
var ref = options.ref || { x: 0, y: 0 };
var scale = options.scale || 1;
// fix for safari / chrome / firefox bug not correctly
// resetting stroke dash array
if (attrs.strokeDasharray === 'none') {
attrs.strokeDasharray = [ 10000, 1 ];
}
var marker = create$1('marker');
attr(options.element, attrs);
append(marker, options.element);
attr(marker, {
id: id,
viewBox: '0 0 20 20',
refX: ref.x,
refY: ref.y,
markerWidth: 20 * scale,
markerHeight: 20 * scale,
orient: 'auto'
});
var defs = query('defs', canvas._svg);
if (!defs) {
defs = create$1('defs');
append(canvas._svg, defs);
}
append(defs, marker);
markers[id] = marker;
}
function colorEscape(str) {
// only allow characters and numbers
return str.replace(/[^0-9a-zA-z]+/g, '_');
}
function marker(type, fill, stroke) {
var id = type + '-' + colorEscape(fill) + '-' + colorEscape(stroke) + '-' + rendererId;
if (!markers[id]) {
createMarker(id, type, fill, stroke);
}
return 'url(#' + id + ')';
}
function createMarker(id, type, fill, stroke) {
if (type === 'sequenceflow-end') {
var sequenceflowEnd = create$1('path');
attr(sequenceflowEnd, { d: 'M 1 5 L 11 10 L 1 15 Z' });
addMarker(id, {
element: sequenceflowEnd,
ref: { x: 11, y: 10 },
scale: 0.5,
attrs: {
fill: stroke,
stroke: stroke
}
});
}
if (type === 'messageflow-start') {
var messageflowStart = create$1('circle');
attr(messageflowStart, { cx: 6, cy: 6, r: 3.5 });
addMarker(id, {
element: messageflowStart,
attrs: {
fill: fill,
stroke: stroke
},
ref: { x: 6, y: 6 }
});
}
if (type === 'messageflow-end') {
var messageflowEnd = create$1('path');
attr(messageflowEnd, { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z' });
addMarker(id, {
element: messageflowEnd,
attrs: {
fill: fill,
stroke: stroke,
strokeLinecap: 'butt'
},
ref: { x: 8.5, y: 5 }
});
}
if (type === 'association-start') {
var associationStart = create$1('path');
attr(associationStart, { d: 'M 11 5 L 1 10 L 11 15' });
addMarker(id, {
element: associationStart,
attrs: {
fill: 'none',
stroke: stroke,
strokeWidth: 1.5
},
ref: { x: 1, y: 10 },
scale: 0.5
});
}
if (type === 'association-end') {
var associationEnd = create$1('path');
attr(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' });
addMarker(id, {
element: associationEnd,
attrs: {
fill: 'none',
stroke: stroke,
strokeWidth: 1.5
},
ref: { x: 12, y: 10 },
scale: 0.5
});
}
if (type === 'conditional-flow-marker') {
var conditionalflowMarker = create$1('path');
attr(conditionalflowMarker, { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z' });
addMarker(id, {
element: conditionalflowMarker,
attrs: {
fill: fill,
stroke: stroke
},
ref: { x: -1, y: 10 },
scale: 0.5
});
}
if (type === 'conditional-default-flow-marker') {
var conditionaldefaultflowMarker = create$1('path');
attr(conditionaldefaultflowMarker, { d: 'M 6 4 L 10 16' });
addMarker(id, {
element: conditionaldefaultflowMarker,
attrs: {
stroke: stroke
},
ref: { x: 0, y: 10 },
scale: 0.5
});
}
}
function drawCircle(parentGfx, width, height, offset, attrs) {
if (isObject(offset)) {
attrs = offset;
offset = 0;
}
offset = offset || 0;
attrs = computeStyle(attrs, {
stroke: black,
strokeWidth: 2,
fill: 'white'
});
if (attrs.fill === 'none') {
delete attrs.fillOpacity;
}
var cx = width / 2,
cy = height / 2;
var circle = create$1('circle');
attr(circle, {
cx: cx,
cy: cy,
r: Math.round((width + height) / 4 - offset)
});
attr(circle, attrs);
append(parentGfx, circle);
return circle;
}
function drawRect(parentGfx, width, height, r, offset, attrs) {
if (isObject(offset)) {
attrs = offset;
offset = 0;
}
offset = offset || 0;
attrs = computeStyle(attrs, {
stroke: black,
strokeWidth: 2,
fill: 'white'
});
var rect = create$1('rect');
attr(rect, {
x: offset,
y: offset,
width: width - offset * 2,
height: height - offset * 2,
rx: r,
ry: r
});
attr(rect, attrs);
append(parentGfx, rect);
return rect;
}
function drawDiamond(parentGfx, width, height, attrs) {
var x_2 = width / 2;
var y_2 = height / 2;
var points = [ { x: x_2, y: 0 }, { x: width, y: y_2 }, { x: x_2, y: height }, { x: 0, y: y_2 } ];
var pointsString = points.map(function(point) {
return point.x + ',' + point.y;
}).join(' ');
attrs = computeStyle(attrs, {
stroke: black,
strokeWidth: 2,
fill: 'white'
});
var polygon = create$1('polygon');
attr(polygon, {
points: pointsString
});
attr(polygon, attrs);
append(parentGfx, polygon);
return polygon;
}
function drawLine(parentGfx, waypoints, attrs) {
attrs = computeStyle(attrs, [ 'no-fill' ], {
stroke: black,
strokeWidth: 2,
fill: 'none'
});
var line = createLine(waypoints, attrs);
append(parentGfx, line);
return line;
}
function drawPath(parentGfx, d, attrs) {
attrs = computeStyle(attrs, [ 'no-fill' ], {
strokeWidth: 2,
stroke: black
});
var path = create$1('path');
attr(path, { d: d });
attr(path, attrs);
append(parentGfx, path);
return path;
}
function drawMarker(type, parentGfx, path, attrs) {
return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs));
}
function renderer(type) {
return handlers[type];
}
function as(type) {
return function(parentGfx, element) {
return renderer(type)(parentGfx, element);
};
}
function renderEventContent(element, parentGfx) {
var event = getSemantic(element);
var isThrowing = isThrowEvent(event);
if (event.eventDefinitions && event.eventDefinitions.length > 1) {
if (event.parallelMultiple) {
return renderer('bpmn:ParallelMultipleEventDefinition')(parentGfx, element, isThrowing);
}
else {
return renderer('bpmn:MultipleEventDefinition')(parentGfx, element, isThrowing);
}
}
if (isTypedEvent(event, 'bpmn:MessageEventDefinition')) {
return renderer('bpmn:MessageEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:TimerEventDefinition')) {
return renderer('bpmn:TimerEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:ConditionalEventDefinition')) {
return renderer('bpmn:ConditionalEventDefinition')(parentGfx, element);
}
if (isTypedEvent(event, 'bpmn:SignalEventDefinition')) {
return renderer('bpmn:SignalEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:EscalationEventDefinition')) {
return renderer('bpmn:EscalationEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:LinkEventDefinition')) {
return renderer('bpmn:LinkEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:ErrorEventDefinition')) {
return renderer('bpmn:ErrorEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:CancelEventDefinition')) {
return renderer('bpmn:CancelEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:CompensateEventDefinition')) {
return renderer('bpmn:CompensateEventDefinition')(parentGfx, element, isThrowing);
}
if (isTypedEvent(event, 'bpmn:TerminateEventDefinition')) {
return renderer('bpmn:TerminateEventDefinition')(parentGfx, element, isThrowing);
}
return null;
}
function renderLabel(parentGfx, label, options) {
options = assign({
size: {
width: 100
}
}, options);
var text = textRenderer.createText(label || '', options);
classes(text).add('djs-label');
append(parentGfx, text);
return text;
}
function renderEmbeddedLabel(parentGfx, element, align) {
var semantic = getSemantic(element);
return renderLabel(parentGfx, semantic.name, {
box: element,
align: align,
padding: 5,
style: {
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
}
});
}
function renderExternalLabel(parentGfx, element) {
var box = {
width: 90,
height: 30,
x: element.width / 2 + element.x,
y: element.height / 2 + element.y
};
return renderLabel(parentGfx, getLabel(element), {
box: box,
fitBox: true,
style: assign(
{},
textRenderer.getExternalStyle(),
{
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
}
)
});
}
function renderLaneLabel(parentGfx, text, element) {
var textBox = renderLabel(parentGfx, text, {
box: {
height: 30,
width: element.height
},
align: 'center-middle',
style: {
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
}
});
var top = -1 * element.height;
transform(textBox, 0, -top, 270);
}
function createPathFromConnection(connection) {
var waypoints = connection.waypoints;
var pathData = 'm ' + waypoints[0].x + ',' + waypoints[0].y;
for (var i = 1; i < waypoints.length; i++) {
pathData += 'L' + waypoints[i].x + ',' + waypoints[i].y + ' ';
}
return pathData;
}
var handlers = this.handlers = {
'bpmn:Event': function(parentGfx, element, attrs) {
if (!('fillOpacity' in attrs)) {
attrs.fillOpacity = DEFAULT_FILL_OPACITY;
}
return drawCircle(parentGfx, element.width, element.height, attrs);
},
'bpmn:StartEvent': function(parentGfx, element) {
var attrs = {
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
var semantic = getSemantic(element);
if (!semantic.isInterrupting) {
attrs = {
strokeDasharray: '6',
strokeLinecap: 'round',
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
}
var circle = renderer('bpmn:Event')(parentGfx, element, attrs);
renderEventContent(element, parentGfx);
return circle;
},
'bpmn:MessageEventDefinition': function(parentGfx, element, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_MESSAGE', {
xScaleFactor: 0.9,
yScaleFactor: 0.9,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.235,
my: 0.315
}
});
var fill = isThrowing ? getStrokeColor$1(element, defaultStrokeColor) : getFillColor(element, defaultFillColor);
var stroke = isThrowing ? getFillColor(element, defaultFillColor) : getStrokeColor$1(element, defaultStrokeColor);
var messagePath = drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill,
stroke: stroke
});
return messagePath;
},
'bpmn:TimerEventDefinition': function(parentGfx, element) {
var circle = drawCircle(parentGfx, element.width, element.height, 0.2 * element.height, {
strokeWidth: 2,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
xScaleFactor: 0.75,
yScaleFactor: 0.75,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.5,
my: 0.5
}
});
drawPath(parentGfx, pathData, {
strokeWidth: 2,
strokeLinecap: 'square',
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
for (var i = 0;i < 12; i++) {
var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
xScaleFactor: 0.75,
yScaleFactor: 0.75,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.5,
my: 0.5
}
});
var width = element.width / 2;
var height = element.height / 2;
drawPath(parentGfx, linePathData, {
strokeWidth: 1,
strokeLinecap: 'square',
transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')',
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
}
return circle;
},
'bpmn:EscalationEventDefinition': function(parentGfx, event, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_ESCALATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.5,
my: 0.2
}
});
var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill,
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
},
'bpmn:ConditionalEventDefinition': function(parentGfx, event) {
var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.5,
my: 0.222
}
});
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
},
'bpmn:LinkEventDefinition': function(parentGfx, event, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_LINK', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.57,
my: 0.263
}
});
var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill,
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
},
'bpmn:ErrorEventDefinition': function(parentGfx, event, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_ERROR', {
xScaleFactor: 1.1,
yScaleFactor: 1.1,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.2,
my: 0.722
}
});
var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill,
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
},
'bpmn:CancelEventDefinition': function(parentGfx, event, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', {
xScaleFactor: 1.0,
yScaleFactor: 1.0,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.638,
my: -0.055
}
});
var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';
var path = drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill,
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
rotate(path, 45);
return path;
},
'bpmn:CompensateEventDefinition': function(parentGfx, event, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.22,
my: 0.5
}
});
var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill,
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
},
'bpmn:SignalEventDefinition': function(parentGfx, event, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_SIGNAL', {
xScaleFactor: 0.9,
yScaleFactor: 0.9,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.5,
my: 0.2
}
});
var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill,
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
},
'bpmn:MultipleEventDefinition': function(parentGfx, event, isThrowing) {
var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', {
xScaleFactor: 1.1,
yScaleFactor: 1.1,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.222,
my: 0.36
}
});
var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none';
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: fill
});
},
'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event) {
var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', {
xScaleFactor: 1.2,
yScaleFactor: 1.2,
containerWidth: event.width,
containerHeight: event.height,
position: {
mx: 0.458,
my: 0.194
}
});
return drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: getStrokeColor$1(event, defaultStrokeColor),
stroke: getStrokeColor$1(event, defaultStrokeColor)
});
},
'bpmn:EndEvent': function(parentGfx, element) {
var circle = renderer('bpmn:Event')(parentGfx, element, {
strokeWidth: 4,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
renderEventContent(element, parentGfx);
return circle;
},
'bpmn:TerminateEventDefinition': function(parentGfx, element) {
var circle = drawCircle(parentGfx, element.width, element.height, 8, {
strokeWidth: 4,
fill: getStrokeColor$1(element, defaultStrokeColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return circle;
},
'bpmn:IntermediateEvent': function(parentGfx, element) {
var outer = renderer('bpmn:Event')(parentGfx, element, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
/* inner */
drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, {
strokeWidth: 1,
fill: getFillColor(element, 'none'),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
renderEventContent(element, parentGfx);
return outer;
},
'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'),
'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'),
'bpmn:Activity': function(parentGfx, element, attrs) {
attrs = attrs || {};
if (!('fillOpacity' in attrs)) {
attrs.fillOpacity = DEFAULT_FILL_OPACITY;
}
return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, attrs);
},
'bpmn:Task': function(parentGfx, element) {
var attrs = {
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);
renderEmbeddedLabel(parentGfx, element, 'center-middle');
attachTaskMarkers(parentGfx, element);
return rect;
},
'bpmn:ServiceTask': function(parentGfx, element) {
var task = renderer('bpmn:Task')(parentGfx, element);
var pathDataBG = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
abspos: {
x: 12,
y: 18
}
});
/* service bg */ drawPath(parentGfx, pathDataBG, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var fillPathData = pathMap.getScaledPath('TASK_TYPE_SERVICE_FILL', {
abspos: {
x: 17.2,
y: 18
}
});
/* service fill */ drawPath(parentGfx, fillPathData, {
strokeWidth: 0,
fill: getFillColor(element, defaultFillColor)
});
var pathData = pathMap.getScaledPath('TASK_TYPE_SERVICE', {
abspos: {
x: 17,
y: 22
}
});
/* service */ drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return task;
},
'bpmn:UserTask': function(parentGfx, element) {
var task = renderer('bpmn:Task')(parentGfx, element);
var x = 15;
var y = 12;
var pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', {
abspos: {
x: x,
y: y
}
});
/* user path */ drawPath(parentGfx, pathData, {
strokeWidth: 0.5,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
abspos: {
x: x,
y: y
}
});
/* user2 path */ drawPath(parentGfx, pathData2, {
strokeWidth: 0.5,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
abspos: {
x: x,
y: y
}
});
/* user3 path */ drawPath(parentGfx, pathData3, {
strokeWidth: 0.5,
fill: getStrokeColor$1(element, defaultStrokeColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return task;
},
'bpmn:ManualTask': function(parentGfx, element) {
var task = renderer('bpmn:Task')(parentGfx, element);
var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
abspos: {
x: 17,
y: 15
}
});
/* manual path */ drawPath(parentGfx, pathData, {
strokeWidth: 0.5, // 0.25,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return task;
},
'bpmn:SendTask': function(parentGfx, element) {
var task = renderer('bpmn:Task')(parentGfx, element);
var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: 21,
containerHeight: 14,
position: {
mx: 0.285,
my: 0.357
}
});
/* send path */ drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: getStrokeColor$1(element, defaultStrokeColor),
stroke: getFillColor(element, defaultFillColor)
});
return task;
},
'bpmn:ReceiveTask' : function(parentGfx, element) {
var semantic = getSemantic(element);
var task = renderer('bpmn:Task')(parentGfx, element);
var pathData;
if (semantic.instantiate) {
drawCircle(parentGfx, 28, 28, 20 * 0.22, { strokeWidth: 1 });
pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', {
abspos: {
x: 7.77,
y: 9.52
}
});
} else {
pathData = pathMap.getScaledPath('TASK_TYPE_SEND', {
xScaleFactor: 0.9,
yScaleFactor: 0.9,
containerWidth: 21,
containerHeight: 14,
position: {
mx: 0.3,
my: 0.4
}
});
}
/* receive path */ drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return task;
},
'bpmn:ScriptTask': function(parentGfx, element) {
var task = renderer('bpmn:Task')(parentGfx, element);
var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', {
abspos: {
x: 15,
y: 20
}
});
/* script path */ drawPath(parentGfx, pathData, {
strokeWidth: 1,
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return task;
},
'bpmn:BusinessRuleTask': function(parentGfx, element) {
var task = renderer('bpmn:Task')(parentGfx, element);
var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
abspos: {
x: 8,
y: 8
}
});
var businessHeaderPath = drawPath(parentGfx, headerPathData);
attr(businessHeaderPath, {
strokeWidth: 1,
fill: getFillColor(element, '#aaaaaa'),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
abspos: {
x: 8,
y: 8
}
});
var businessPath = drawPath(parentGfx, headerData);
attr(businessPath, {
strokeWidth: 1,
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return task;
},
'bpmn:SubProcess': function(parentGfx, element, attrs) {
attrs = assign({
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
}, attrs);
var rect = renderer('bpmn:Activity')(parentGfx, element, attrs);
var expanded = isExpanded(element);
if (isEventSubProcess(element)) {
attr(rect, {
strokeDasharray: '1,2'
});
}
renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle');
if (expanded) {
attachTaskMarkers(parentGfx, element);
} else {
attachTaskMarkers(parentGfx, element, [ 'SubProcessMarker' ]);
}
return rect;
},
'bpmn:AdHocSubProcess': function(parentGfx, element) {
return renderer('bpmn:SubProcess')(parentGfx, element);
},
'bpmn:Transaction': function(parentGfx, element) {
var outer = renderer('bpmn:SubProcess')(parentGfx, element);
var innerAttrs = styles.style([ 'no-fill', 'no-events' ], {
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
/* inner path */ drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST, innerAttrs);
return outer;
},
'bpmn:CallActivity': function(parentGfx, element) {
return renderer('bpmn:SubProcess')(parentGfx, element, {
strokeWidth: 5
});
},
'bpmn:Participant': function(parentGfx, element) {
var attrs = {
fillOpacity: DEFAULT_FILL_OPACITY,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
var lane = renderer('bpmn:Lane')(parentGfx, element, attrs);
var expandedPool = isExpanded(element);
if (expandedPool) {
drawLine(parentGfx, [
{ x: 30, y: 0 },
{ x: 30, y: element.height }
], {
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var text = getSemantic(element).name;
renderLaneLabel(parentGfx, text, element);
} else {
// Collapsed pool draw text inline
var text2 = getSemantic(element).name;
renderLabel(parentGfx, text2, {
box: element, align: 'center-middle',
style: {
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
}
});
}
var participantMultiplicity = !!(getSemantic(element).participantMultiplicity);
if (participantMultiplicity) {
renderer('ParticipantMultiplicityMarker')(parentGfx, element);
}
return lane;
},
'bpmn:Lane': function(parentGfx, element, attrs) {
var rect = drawRect(parentGfx, element.width, element.height, 0, assign({
fill: getFillColor(element, defaultFillColor),
fillOpacity: HIGH_FILL_OPACITY,
stroke: getStrokeColor$1(element, defaultStrokeColor)
}, attrs));
var semantic = getSemantic(element);
if (semantic.$type === 'bpmn:Lane') {
var text = semantic.name;
renderLaneLabel(parentGfx, text, element);
}
return rect;
},
'bpmn:InclusiveGateway': function(parentGfx, element) {
var diamond = renderer('bpmn:Gateway')(parentGfx, element);
/* circle path */
drawCircle(parentGfx, element.width, element.height, element.height * 0.24, {
strokeWidth: 2.5,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return diamond;
},
'bpmn:ExclusiveGateway': function(parentGfx, element) {
var diamond = renderer('bpmn:Gateway')(parentGfx, element);
var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', {
xScaleFactor: 0.4,
yScaleFactor: 0.4,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.32,
my: 0.3
}
});
if ((getDi(element).isMarkerVisible)) {
drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: getStrokeColor$1(element, defaultStrokeColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
}
return diamond;
},
'bpmn:ComplexGateway': function(parentGfx, element) {
var diamond = renderer('bpmn:Gateway')(parentGfx, element);
var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', {
xScaleFactor: 0.5,
yScaleFactor:0.5,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.46,
my: 0.26
}
});
/* complex path */ drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: getStrokeColor$1(element, defaultStrokeColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return diamond;
},
'bpmn:ParallelGateway': function(parentGfx, element) {
var diamond = renderer('bpmn:Gateway')(parentGfx, element);
var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
xScaleFactor: 0.6,
yScaleFactor:0.6,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.46,
my: 0.2
}
});
/* parallel path */ drawPath(parentGfx, pathData, {
strokeWidth: 1,
fill: getStrokeColor$1(element, defaultStrokeColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return diamond;
},
'bpmn:EventBasedGateway': function(parentGfx, element) {
var semantic = getSemantic(element);
var diamond = renderer('bpmn:Gateway')(parentGfx, element);
/* outer circle path */ drawCircle(parentGfx, element.width, element.height, element.height * 0.20, {
strokeWidth: 1,
fill: 'none',
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var type = semantic.eventGatewayType;
var instantiate = !!semantic.instantiate;
function drawEvent() {
var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', {
xScaleFactor: 0.18,
yScaleFactor: 0.18,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.36,
my: 0.44
}
});
var attrs = {
strokeWidth: 2,
fill: getFillColor(element, 'none'),
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
/* event path */ drawPath(parentGfx, pathData, attrs);
}
if (type === 'Parallel') {
var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', {
xScaleFactor: 0.4,
yScaleFactor:0.4,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.474,
my: 0.296
}
});
var parallelPath = drawPath(parentGfx, pathData);
attr(parallelPath, {
strokeWidth: 1,
fill: 'none'
});
} else if (type === 'Exclusive') {
if (!instantiate) {
var innerCircle = drawCircle(parentGfx, element.width, element.height, element.height * 0.26);
attr(innerCircle, {
strokeWidth: 1,
fill: 'none',
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
}
drawEvent();
}
return diamond;
},
'bpmn:Gateway': function(parentGfx, element) {
var attrs = {
fill: getFillColor(element, defaultFillColor),
fillOpacity: DEFAULT_FILL_OPACITY,
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
return drawDiamond(parentGfx, element.width, element.height, attrs);
},
'bpmn:SequenceFlow': function(parentGfx, element) {
var pathData = createPathFromConnection(element);
var fill = getFillColor(element, defaultFillColor),
stroke = getStrokeColor$1(element, defaultStrokeColor);
var attrs = {
strokeLinejoin: 'round',
markerEnd: marker('sequenceflow-end', fill, stroke),
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
var path = drawPath(parentGfx, pathData, attrs);
var sequenceFlow = getSemantic(element);
var source;
if (element.source) {
source = element.source.businessObject;
// conditional flow marker
if (sequenceFlow.conditionExpression && source.$instanceOf('bpmn:Activity')) {
attr(path, {
markerStart: marker('conditional-flow-marker', fill, stroke)
});
}
// default marker
if (source.default && (source.$instanceOf('bpmn:Gateway') || source.$instanceOf('bpmn:Activity')) &&
source.default === sequenceFlow) {
attr(path, {
markerStart: marker('conditional-default-flow-marker', fill, stroke)
});
}
}
return path;
},
'bpmn:Association': function(parentGfx, element, attrs) {
var semantic = getSemantic(element);
var fill = getFillColor(element, defaultFillColor),
stroke = getStrokeColor$1(element, defaultStrokeColor);
attrs = assign({
strokeDasharray: '0.5, 5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: getStrokeColor$1(element, defaultStrokeColor)
}, attrs || {});
if (semantic.associationDirection === 'One' ||
semantic.associationDirection === 'Both') {
attrs.markerEnd = marker('association-end', fill, stroke);
}
if (semantic.associationDirection === 'Both') {
attrs.markerStart = marker('association-start', fill, stroke);
}
return drawLine(parentGfx, element.waypoints, attrs);
},
'bpmn:DataInputAssociation': function(parentGfx, element) {
var fill = getFillColor(element, defaultFillColor),
stroke = getStrokeColor$1(element, defaultStrokeColor);
return renderer('bpmn:Association')(parentGfx, element, {
markerEnd: marker('association-end', fill, stroke)
});
},
'bpmn:DataOutputAssociation': function(parentGfx, element) {
var fill = getFillColor(element, defaultFillColor),
stroke = getStrokeColor$1(element, defaultStrokeColor);
return renderer('bpmn:Association')(parentGfx, element, {
markerEnd: marker('association-end', fill, stroke)
});
},
'bpmn:MessageFlow': function(parentGfx, element) {
var semantic = getSemantic(element),
di = getDi(element);
var fill = getFillColor(element, defaultFillColor),
stroke = getStrokeColor$1(element, defaultStrokeColor);
var pathData = createPathFromConnection(element);
var attrs = {
markerEnd: marker('messageflow-end', fill, stroke),
markerStart: marker('messageflow-start', fill, stroke),
strokeDasharray: '10, 12',
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeWidth: '1.5px',
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
var path = drawPath(parentGfx, pathData, attrs);
if (semantic.messageRef) {
var midPoint = path.getPointAtLength(path.getTotalLength() / 2);
var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', {
abspos: {
x: midPoint.x,
y: midPoint.y
}
});
var messageAttrs = { strokeWidth: 1 };
if (di.messageVisibleKind === 'initiating') {
messageAttrs.fill = 'white';
messageAttrs.stroke = black;
} else {
messageAttrs.fill = '#888';
messageAttrs.stroke = 'white';
}
var message = drawPath(parentGfx, markerPathData, messageAttrs);
var labelText = semantic.messageRef.name;
var label = renderLabel(parentGfx, labelText, {
align: 'center-top',
fitBox: true,
style: {
fill: getStrokeColor$1(element, defaultLabelColor)
}
});
var messageBounds = message.getBBox(),
labelBounds = label.getBBox();
var translateX = midPoint.x - labelBounds.width / 2,
translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE$1;
transform(label, translateX, translateY, 0);
}
return path;
},
'bpmn:DataObject': function(parentGfx, element) {
var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.474,
my: 0.296
}
});
var elementObject = drawPath(parentGfx, pathData, {
fill: getFillColor(element, defaultFillColor),
fillOpacity: DEFAULT_FILL_OPACITY,
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var semantic = getSemantic(element);
if (isCollection(semantic)) {
renderDataItemCollection(parentGfx, element);
}
return elementObject;
},
'bpmn:DataObjectReference': as('bpmn:DataObject'),
'bpmn:DataInput': function(parentGfx, element) {
var arrowPathData = pathMap.getRawPath('DATA_ARROW');
// page
var elementObject = renderer('bpmn:DataObject')(parentGfx, element);
/* input arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1 });
return elementObject;
},
'bpmn:DataOutput': function(parentGfx, element) {
var arrowPathData = pathMap.getRawPath('DATA_ARROW');
// page
var elementObject = renderer('bpmn:DataObject')(parentGfx, element);
/* output arrow path */ drawPath(parentGfx, arrowPathData, {
strokeWidth: 1,
fill: black
});
return elementObject;
},
'bpmn:DataStoreReference': function(parentGfx, element) {
var DATA_STORE_PATH = pathMap.getScaledPath('DATA_STORE', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0,
my: 0.133
}
});
var elementStore = drawPath(parentGfx, DATA_STORE_PATH, {
strokeWidth: 2,
fill: getFillColor(element, defaultFillColor),
fillOpacity: DEFAULT_FILL_OPACITY,
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
return elementStore;
},
'bpmn:BoundaryEvent': function(parentGfx, element) {
var semantic = getSemantic(element),
cancel = semantic.cancelActivity;
var attrs = {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
};
if (!cancel) {
attrs.strokeDasharray = '6';
attrs.strokeLinecap = 'round';
}
// apply fillOpacity
var outerAttrs = assign({}, attrs, {
fillOpacity: 1
});
// apply no-fill
var innerAttrs = assign({}, attrs, {
fill: 'none'
});
var outer = renderer('bpmn:Event')(parentGfx, element, outerAttrs);
/* inner path */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, innerAttrs);
renderEventContent(element, parentGfx);
return outer;
},
'bpmn:Group': function(parentGfx, element) {
var group = drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, {
stroke: getStrokeColor$1(element, defaultStrokeColor),
strokeWidth: 1,
strokeDasharray: '8,3,1,3',
fill: 'none',
pointerEvents: 'none'
});
return group;
},
'label': function(parentGfx, element) {
return renderExternalLabel(parentGfx, element);
},
'bpmn:TextAnnotation': function(parentGfx, element) {
var style = {
'fill': 'none',
'stroke': 'none'
};
var textElement = drawRect(parentGfx, element.width, element.height, 0, 0, style);
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.0,
my: 0.0
}
});
drawPath(parentGfx, textPathData, {
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
var text = getSemantic(element).text || '';
renderLabel(parentGfx, text, {
box: element,
align: 'left-top',
padding: 5,
style: {
fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor)
}
});
return textElement;
},
'ParticipantMultiplicityMarker': function(parentGfx, element) {
var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2) / element.width),
my: (element.height - 15) / element.height
}
});
drawMarker('participant-multiplicity', parentGfx, markerPath, {
strokeWidth: 2,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
},
'SubProcessMarker': function(parentGfx, element) {
var markerRect = drawRect(parentGfx, 14, 14, 0, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
// Process marker is placed in the middle of the box
// therefore fixed values can be used here
translate$2(markerRect, element.width / 2 - 7.5, element.height - 20);
var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', {
xScaleFactor: 1.5,
yScaleFactor: 1.5,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: (element.width / 2 - 7.5) / element.width,
my: (element.height - 20) / element.height
}
});
drawMarker('sub-process', parentGfx, markerPath, {
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
},
'ParallelMarker': function(parentGfx, element, position) {
var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position.parallel) / element.width),
my: (element.height - 20) / element.height
}
});
drawMarker('parallel', parentGfx, markerPath, {
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
},
'SequentialMarker': function(parentGfx, element, position) {
var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position.seq) / element.width),
my: (element.height - 19) / element.height
}
});
drawMarker('sequential', parentGfx, markerPath, {
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
},
'CompensationMarker': function(parentGfx, element, position) {
var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position.compensation) / element.width),
my: (element.height - 13) / element.height
}
});
drawMarker('compensation', parentGfx, markerMath, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
},
'LoopMarker': function(parentGfx, element, position) {
var markerPath = pathMap.getScaledPath('MARKER_LOOP', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position.loop) / element.width),
my: (element.height - 7) / element.height
}
});
drawMarker('loop', parentGfx, markerPath, {
strokeWidth: 1,
fill: getFillColor(element, defaultFillColor),
stroke: getStrokeColor$1(element, defaultStrokeColor),
strokeLinecap: 'round',
strokeMiterlimit: 0.5
});
},
'AdhocMarker': function(parentGfx, element, position) {
var markerPath = pathMap.getScaledPath('MARKER_ADHOC', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position.adhoc) / element.width),
my: (element.height - 15) / element.height
}
});
drawMarker('adhoc', parentGfx, markerPath, {
strokeWidth: 1,
fill: getStrokeColor$1(element, defaultStrokeColor),
stroke: getStrokeColor$1(element, defaultStrokeColor)
});
}
};
function attachTaskMarkers(parentGfx, element, taskMarkers) {
var obj = getSemantic(element);
var subprocess = taskMarkers && taskMarkers.indexOf('SubProcessMarker') !== -1;
var position;
if (subprocess) {
position = {
seq: -21,
parallel: -22,
compensation: -42,
loop: -18,
adhoc: 10
};
} else {
position = {
seq: -3,
parallel: -6,
compensation: -27,
loop: 0,
adhoc: 10
};
}
forEach$1(taskMarkers, function(marker) {
renderer(marker)(parentGfx, element, position);
});
if (obj.isForCompensation) {
renderer('CompensationMarker')(parentGfx, element, position);
}
if (obj.$type === 'bpmn:AdHocSubProcess') {
renderer('AdhocMarker')(parentGfx, element, position);
}
var loopCharacteristics = obj.loopCharacteristics,
isSequential = loopCharacteristics && loopCharacteristics.isSequential;
if (loopCharacteristics) {
if (isSequential === undefined) {
renderer('LoopMarker')(parentGfx, element, position);
}
if (isSequential === false) {
renderer('ParallelMarker')(parentGfx, element, position);
}
if (isSequential === true) {
renderer('SequentialMarker')(parentGfx, element, position);
}
}
}
function renderDataItemCollection(parentGfx, element) {
var yPosition = (element.height - 18) / element.height;
var pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.33,
my: yPosition
}
});
/* collection path */ drawPath(parentGfx, pathData, {
strokeWidth: 2
});
}
// extension API, use at your own risk
this._drawPath = drawPath;
this._renderer = renderer;
}
e(BpmnRenderer, BaseRenderer);
BpmnRenderer.$inject = [
'config.bpmnRenderer',
'eventBus',
'styles',
'pathMap',
'canvas',
'textRenderer'
];
BpmnRenderer.prototype.canRender = function(element) {
return is$1(element, 'bpmn:BaseElement');
};
BpmnRenderer.prototype.drawShape = function(parentGfx, element) {
var type = element.type;
var h = this._renderer(type);
/* jshint -W040 */
return h(parentGfx, element);
};
BpmnRenderer.prototype.drawConnection = function(parentGfx, element) {
var type = element.type;
var h = this._renderer(type);
/* jshint -W040 */
return h(parentGfx, element);
};
BpmnRenderer.prototype.getShapePath = function(element) {
if (is$1(element, 'bpmn:Event')) {
return getCirclePath(element);
}
if (is$1(element, 'bpmn:Activity')) {
return getRoundRectPath(element, TASK_BORDER_RADIUS);
}
if (is$1(element, 'bpmn:Gateway')) {
return getDiamondPath(element);
}
return getRectPath(element);
};
var DEFAULT_BOX_PADDING = 0;
var DEFAULT_LABEL_SIZE$1 = {
width: 150,
height: 50
};
function parseAlign(align) {
var parts = align.split('-');
return {
horizontal: parts[0] || 'center',
vertical: parts[1] || 'top'
};
}
function parsePadding(padding) {
if (isObject(padding)) {
return assign({ top: 0, left: 0, right: 0, bottom: 0 }, padding);
} else {
return {
top: padding,
left: padding,
right: padding,
bottom: padding
};
}
}
function getTextBBox(text, fakeText) {
fakeText.textContent = text;
var textBBox;
try {
var bbox,
emptyLine = text === '';
// add dummy text, when line is empty to
// determine correct height
fakeText.textContent = emptyLine ? 'dummy' : text;
textBBox = fakeText.getBBox();
// take text rendering related horizontal
// padding into account
bbox = {
width: textBBox.width + textBBox.x * 2,
height: textBBox.height
};
if (emptyLine) {
// correct width
bbox.width = 0;
}
return bbox;
} catch (e) {
return { width: 0, height: 0 };
}
}
/**
* Layout the next line and return the layouted element.
*
* Alters the lines passed.
*
* @param {Array<string>} lines
* @return {Object} the line descriptor, an object { width, height, text }
*/
function layoutNext(lines, maxWidth, fakeText) {
var originalLine = lines.shift(),
fitLine = originalLine;
var textBBox;
for (;;) {
textBBox = getTextBBox(fitLine, fakeText);
textBBox.width = fitLine ? textBBox.width : 0;
// try to fit
if (fitLine === ' ' || fitLine === '' || textBBox.width < Math.round(maxWidth) || fitLine.length < 2) {
return fit(lines, fitLine, originalLine, textBBox);
}
fitLine = shortenLine(fitLine, textBBox.width, maxWidth);
}
}
function fit(lines, fitLine, originalLine, textBBox) {
if (fitLine.length < originalLine.length) {
var remainder = originalLine.slice(fitLine.length).trim();
lines.unshift(remainder);
}
return {
width: textBBox.width,
height: textBBox.height,
text: fitLine
};
}
var SOFT_BREAK = '\u00AD';
/**
* Shortens a line based on spacing and hyphens.
* Returns the shortened result on success.
*
* @param {string} line
* @param {number} maxLength the maximum characters of the string
* @return {string} the shortened string
*/
function semanticShorten(line, maxLength) {
var parts = line.split(/(\s|-|\u00AD)/g),
part,
shortenedParts = [],
length = 0;
// try to shorten via break chars
if (parts.length > 1) {
while ((part = parts.shift())) {
if (part.length + length < maxLength) {
shortenedParts.push(part);
length += part.length;
} else {
// remove previous part, too if hyphen does not fit anymore
if (part === '-' || part === SOFT_BREAK) {
shortenedParts.pop();
}
break;
}
}
}
var last = shortenedParts[shortenedParts.length - 1];
// translate trailing soft break to actual hyphen
if (last && last === SOFT_BREAK) {
shortenedParts[shortenedParts.length - 1] = '-';
}
return shortenedParts.join('');
}
function shortenLine(line, width, maxWidth) {
var length = Math.max(line.length * (maxWidth / width), 1);
// try to shorten semantically (i.e. based on spaces and hyphens)
var shortenedLine = semanticShorten(line, length);
if (!shortenedLine) {
// force shorten by cutting the long word
shortenedLine = line.slice(0, Math.max(Math.round(length - 1), 1));
}
return shortenedLine;
}
function getHelperSvg() {
var helperSvg = document.getElementById('helper-svg');
if (!helperSvg) {
helperSvg = create$1('svg');
attr(helperSvg, {
id: 'helper-svg'
});
assign$1(helperSvg, {
visibility: 'hidden',
position: 'fixed',
width: 0,
height: 0
});
document.body.appendChild(helperSvg);
}
return helperSvg;
}
/**
* Creates a new label utility
*
* @param {Object} config
* @param {Dimensions} config.size
* @param {number} config.padding
* @param {Object} config.style
* @param {string} config.align
*/
function Text(config) {
this._config = assign({}, {
size: DEFAULT_LABEL_SIZE$1,
padding: DEFAULT_BOX_PADDING,
style: {},
align: 'center-top'
}, config || {});
}
/**
* Returns the layouted text as an SVG element.
*
* @param {string} text
* @param {Object} options
*
* @return {SVGElement}
*/
Text.prototype.createText = function(text, options) {
return this.layoutText(text, options).element;
};
/**
* Returns a labels layouted dimensions.
*
* @param {string} text to layout
* @param {Object} options
*
* @return {Dimensions}
*/
Text.prototype.getDimensions = function(text, options) {
return this.layoutText(text, options).dimensions;
};
/**
* Creates and returns a label and its bounding box.
*
* @method Text#createText
*
* @param {string} text the text to render on the label
* @param {Object} options
* @param {string} options.align how to align in the bounding box.
* Any of { 'center-middle', 'center-top' },
* defaults to 'center-top'.
* @param {string} options.style style to be applied to the text
* @param {boolean} options.fitBox indicates if box will be recalculated to
* fit text
*
* @return {Object} { element, dimensions }
*/
Text.prototype.layoutText = function(text, options) {
var box = assign({}, this._config.size, options.box),
style = assign({}, this._config.style, options.style),
align = parseAlign(options.align || this._config.align),
padding = parsePadding(options.padding !== undefined ? options.padding : this._config.padding),
fitBox = options.fitBox || false;
var lineHeight = getLineHeight(style);
// we split text by lines and normalize
// {soft break} + {line break} => { line break }
var lines = text.split(/\u00AD?\r?\n/),
layouted = [];
var maxWidth = box.width - padding.left - padding.right;
// ensure correct rendering by attaching helper text node to invisible SVG
var helperText = create$1('text');
attr(helperText, { x: 0, y: 0 });
attr(helperText, style);
var helperSvg = getHelperSvg();
append(helperSvg, helperText);
while (lines.length) {
layouted.push(layoutNext(lines, maxWidth, helperText));
}
if (align.vertical === 'middle') {
padding.top = padding.bottom = 0;
}
var totalHeight = reduce(layouted, function(sum, line, idx) {
return sum + (lineHeight || line.height);
}, 0) + padding.top + padding.bottom;
var maxLineWidth = reduce(layouted, function(sum, line, idx) {
return line.width > sum ? line.width : sum;
}, 0);
// the y position of the next line
var y = padding.top;
if (align.vertical === 'middle') {
y += (box.height - totalHeight) / 2;
}
// magic number initial offset
y -= (lineHeight || layouted[0].height) / 4;
var textElement = create$1('text');
attr(textElement, style);
// layout each line taking into account that parent
// shape might resize to fit text size
forEach$1(layouted, function(line) {
var x;
y += (lineHeight || line.height);
switch (align.horizontal) {
case 'left':
x = padding.left;
break;
case 'right':
x = ((fitBox ? maxLineWidth : maxWidth)
- padding.right - line.width);
break;
default:
// aka center
x = Math.max((((fitBox ? maxLineWidth : maxWidth)
- line.width) / 2 + padding.left), 0);
}
var tspan = create$1('tspan');
attr(tspan, { x: x, y: y });
tspan.textContent = line.text;
append(textElement, tspan);
});
remove$1(helperText);
var dimensions = {
width: maxLineWidth,
height: totalHeight
};
return {
dimensions: dimensions,
element: textElement
};
};
function getLineHeight(style) {
if ('fontSize' in style && 'lineHeight' in style) {
return style.lineHeight * parseInt(style.fontSize, 10);
}
}
var DEFAULT_FONT_SIZE = 12;
var LINE_HEIGHT_RATIO = 1.2;
var MIN_TEXT_ANNOTATION_HEIGHT = 30;
function TextRenderer(config) {
var defaultStyle = assign({
fontFamily: 'Arial, sans-serif',
fontSize: DEFAULT_FONT_SIZE,
fontWeight: 'normal',
lineHeight: LINE_HEIGHT_RATIO
}, config && config.defaultStyle || {});
var fontSize = parseInt(defaultStyle.fontSize, 10) - 1;
var externalStyle = assign({}, defaultStyle, {
fontSize: fontSize
}, config && config.externalStyle || {});
var textUtil = new Text({
style: defaultStyle
});
/**
* Get the new bounds of an externally rendered,
* layouted label.
*
* @param {Bounds} bounds
* @param {string} text
*
* @return {Bounds}
*/
this.getExternalLabelBounds = function(bounds, text) {
var layoutedDimensions = textUtil.getDimensions(text, {
box: {
width: 90,
height: 30,
x: bounds.width / 2 + bounds.x,
y: bounds.height / 2 + bounds.y
},
style: externalStyle
});
// resize label shape to fit label text
return {
x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
y: Math.round(bounds.y),
width: Math.ceil(layoutedDimensions.width),
height: Math.ceil(layoutedDimensions.height)
};
};
/**
* Get the new bounds of text annotation.
*
* @param {Bounds} bounds
* @param {string} text
*
* @return {Bounds}
*/
this.getTextAnnotationBounds = function(bounds, text) {
var layoutedDimensions = textUtil.getDimensions(text, {
box: bounds,
style: defaultStyle,
align: 'left-top',
padding: 5
});
return {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height))
};
};
/**
* Create a layouted text element.
*
* @param {string} text
* @param {Object} [options]
*
* @return {SVGElement} rendered text
*/
this.createText = function(text, options) {
return textUtil.createText(text, options || {});
};
/**
* Get default text style.
*/
this.getDefaultStyle = function() {
return defaultStyle;
};
/**
* Get the external text style.
*/
this.getExternalStyle = function() {
return externalStyle;
};
}
TextRenderer.$inject = [
'config.textRenderer'
];
/**
* Map containing SVG paths needed by BpmnRenderer.
*/
function PathMap() {
/**
* Contains a map of path elements
*
* <h1>Path definition</h1>
* A parameterized path is defined like this:
* <pre>
* 'GATEWAY_PARALLEL': {
* d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
'-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
* height: 17.5,
* width: 17.5,
* heightElements: [2.5, 7.5],
* widthElements: [2.5, 7.5]
* }
* </pre>
* <p>It's important to specify a correct <b>height and width</b> for the path as the scaling
* is based on the ratio between the specified height and width in this object and the
* height and width that is set as scale target (Note x,y coordinates will be scaled with
* individual ratios).</p>
* <p>The '<b>heightElements</b>' and '<b>widthElements</b>' array must contain the values that will be scaled.
* The scaling is based on the computed ratios.
* Coordinates on the y axis should be in the <b>heightElement</b>'s array, they will be scaled using
* the computed ratio coefficient.
* In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets.
* <ul>
* <li>The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....</li>
* <li>The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....</li>
* </ul>
* The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index.
* </p>
*/
this.pathMap = {
'EVENT_MESSAGE': {
d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
height: 36,
width: 36,
heightElements: [ 6, 14 ],
widthElements: [ 10.5, 21 ]
},
'EVENT_SIGNAL': {
d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z',
height: 36,
width: 36,
heightElements: [ 18 ],
widthElements: [ 10, 20 ]
},
'EVENT_ESCALATION': {
d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z',
height: 36,
width: 36,
heightElements: [ 20, 7 ],
widthElements: [ 8 ]
},
'EVENT_CONDITIONAL': {
d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' +
'M {e.x2},{e.y3} l {e.x0},0 ' +
'M {e.x2},{e.y4} l {e.x0},0 ' +
'M {e.x2},{e.y5} l {e.x0},0 ' +
'M {e.x2},{e.y6} l {e.x0},0 ' +
'M {e.x2},{e.y7} l {e.x0},0 ' +
'M {e.x2},{e.y8} l {e.x0},0 ',
height: 36,
width: 36,
heightElements: [ 8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5 ],
widthElements: [ 10.5, 14.5, 12.5 ]
},
'EVENT_LINK': {
d: 'm {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z',
height: 36,
width: 36,
heightElements: [ 4.4375, 6.75, 7.8125 ],
widthElements: [ 9.84375, 13.5 ]
},
'EVENT_ERROR': {
d: 'm {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z',
height: 36,
width: 36,
heightElements: [ 0.023, 8.737, 8.151, 16.564, 10.591, 8.714 ],
widthElements: [ 0.085, 6.672, 6.97, 4.273, 5.337, 6.636 ]
},
'EVENT_CANCEL_45': {
d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
'0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
height: 36,
width: 36,
heightElements: [ 4.75, 8.5 ],
widthElements: [ 4.75, 8.5 ]
},
'EVENT_COMPENSATION': {
d: 'm {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z',
height: 36,
width: 36,
heightElements: [ 6.5, 13, 0.4, 6.1 ],
widthElements: [ 9, 9.3, 8.7 ]
},
'EVENT_TIMER_WH': {
d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ',
height: 36,
width: 36,
heightElements: [ 10, 2 ],
widthElements: [ 3, 7 ]
},
'EVENT_TIMER_LINE': {
d: 'M {mx},{my} ' +
'm {e.x0},{e.y0} l -{e.x1},{e.y1} ',
height: 36,
width: 36,
heightElements: [ 10, 3 ],
widthElements: [ 0, 0 ]
},
'EVENT_MULTIPLE': {
d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z',
height: 36,
width: 36,
heightElements: [ 6.28099, 12.56199 ],
widthElements: [ 3.1405, 9.42149, 12.56198 ]
},
'EVENT_PARALLEL_MULTIPLE': {
d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
'-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
height: 36,
width: 36,
heightElements: [ 2.56228, 7.68683 ],
widthElements: [ 2.56228, 7.68683 ]
},
'GATEWAY_EXCLUSIVE': {
d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' +
'{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' +
'{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z',
height: 17.5,
width: 17.5,
heightElements: [ 8.5, 6.5312, -6.5312, -8.5 ],
widthElements: [ 6.5, -6.5, 3, -3, 5, -5 ]
},
'GATEWAY_PARALLEL': {
d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
'0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
height: 30,
width: 30,
heightElements: [ 5, 12.5 ],
widthElements: [ 5, 12.5 ]
},
'GATEWAY_EVENT_BASED': {
d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z',
height: 11,
width: 11,
heightElements: [ -6, 6, 12, -12 ],
widthElements: [ 9, -3, -12 ]
},
'GATEWAY_COMPLEX': {
d:'m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} ' +
'{e.x2},0 -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} ' +
'{e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} ' +
'-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z',
height: 17.125,
width: 17.125,
heightElements: [ 4.875, 3.4375, 2.125, 3 ],
widthElements: [ 3.4375, 2.125, 4.875, 3 ]
},
'DATA_OBJECT_PATH': {
d:'m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0',
height: 61,
width: 51,
heightElements: [ 10, 50, 60 ],
widthElements: [ 10, 40, 50, 60 ]
},
'DATA_OBJECT_COLLECTION_PATH': {
d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'DATA_ARROW': {
d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z',
height: 61,
width: 51,
heightElements: [],
widthElements: []
},
'DATA_STORE': {
d:'m {mx},{my} ' +
'l 0,{e.y2} ' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
'l 0,-{e.y2} ' +
'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
'm -{e.x2},{e.y0}' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' +
'm -{e.x2},{e.y0}' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0',
height: 61,
width: 61,
heightElements: [ 7, 10, 45 ],
widthElements: [ 2, 58, 60 ]
},
'TEXT_ANNOTATION': {
d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0',
height: 30,
width: 10,
heightElements: [ 30 ],
widthElements: [ 10 ]
},
'MARKER_SUB_PROCESS': {
d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_PARALLEL': {
d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_SEQUENTIAL': {
d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_COMPENSATION': {
d: 'm {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z',
height: 10,
width: 21,
heightElements: [],
widthElements: []
},
'MARKER_LOOP': {
d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' +
'-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' +
'0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' +
'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902',
height: 13.9,
width: 13.7,
heightElements: [],
widthElements: []
},
'MARKER_ADHOC': {
d: 'm {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 ' +
'3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' +
'1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 ' +
'-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' +
'-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z',
height: 4,
width: 15,
heightElements: [],
widthElements: []
},
'TASK_TYPE_SEND': {
d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
height: 14,
width: 21,
heightElements: [ 6, 14 ],
widthElements: [ 10.5, 21 ]
},
'TASK_TYPE_SCRIPT': {
d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' +
'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' +
'm -7,-12 l 5,0 ' +
'm -4.5,3 l 4.5,0 ' +
'm -3,3 l 5,0' +
'm -4,3 l 5,0',
height: 15,
width: 12.6,
heightElements: [ 6, 14 ],
widthElements: [ 10.5, 21 ]
},
'TASK_TYPE_USER_1': {
d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' +
'-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' +
'0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' +
'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' +
'm -8,6 l 0,5.5 m 11,0 l 0,-5'
},
'TASK_TYPE_USER_2': {
d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' +
'-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 '
},
'TASK_TYPE_USER_3': {
d: 'm {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 ' +
'4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' +
'-4.20799998,3.36699999 -4.20699998,4.34799999 z'
},
'TASK_TYPE_MANUAL': {
d: 'm {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 ' +
'-0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 ' +
'0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 ' +
'-1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 ' +
'0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 ' +
'-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' +
'2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' +
'-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' +
'-1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 ' +
'-0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 ' +
'0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 ' +
'-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z'
},
'TASK_TYPE_INSTANTIATING_SEND': {
d: 'm {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6'
},
'TASK_TYPE_SERVICE': {
d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' +
'0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' +
'-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' +
'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' +
'-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' +
'-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' +
'h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 ' +
'-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' +
'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' +
'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' +
'0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' +
'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' +
'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
'0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
'0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
},
'TASK_TYPE_SERVICE_FILL': {
d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
'0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
'0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
},
'TASK_TYPE_BUSINESS_RULE_HEADER': {
d: 'm {mx},{my} 0,4 20,0 0,-4 z'
},
'TASK_TYPE_BUSINESS_RULE_MAIN': {
d: 'm {mx},{my} 0,12 20,0 0,-12 z' +
'm 0,8 l 20,0 ' +
'm -13,-4 l 0,8'
},
'MESSAGE_FLOW_MARKER': {
d: 'm {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6'
}
};
this.getRawPath = function getRawPath(pathId) {
return this.pathMap[pathId].d;
};
/**
* Scales the path to the given height and width.
* <h1>Use case</h1>
* <p>Use case is to scale the content of elements (event, gateways) based
* on the element bounding box's size.
* </p>
* <h1>Why not transform</h1>
* <p>Scaling a path with transform() will also scale the stroke and IE does not support
* the option 'non-scaling-stroke' to prevent this.
* Also there are use cases where only some parts of a path should be
* scaled.</p>
*
* @param {string} pathId The ID of the path.
* @param {Object} param <p>
* Example param object scales the path to 60% size of the container (data.width, data.height).
* <pre>
* {
* xScaleFactor: 0.6,
* yScaleFactor:0.6,
* containerWidth: data.width,
* containerHeight: data.height,
* position: {
* mx: 0.46,
* my: 0.2,
* }
* }
* </pre>
* <ul>
* <li>targetpathwidth = xScaleFactor * containerWidth</li>
* <li>targetpathheight = yScaleFactor * containerHeight</li>
* <li>Position is used to set the starting coordinate of the path. M is computed:
* <ul>
* <li>position.x * containerWidth</li>
* <li>position.y * containerHeight</li>
* </ul>
* Center of the container <pre> position: {
* mx: 0.5,
* my: 0.5,
* }</pre>
* Upper left corner of the container
* <pre> position: {
* mx: 0.0,
* my: 0.0,
* }</pre>
* </li>
* </ul>
* </p>
*
*/
this.getScaledPath = function getScaledPath(pathId, param) {
var rawPath = this.pathMap[pathId];
// positioning
// compute the start point of the path
var mx, my;
if (param.abspos) {
mx = param.abspos.x;
my = param.abspos.y;
} else {
mx = param.containerWidth * param.position.mx;
my = param.containerHeight * param.position.my;
}
var coordinates = {}; // map for the scaled coordinates
if (param.position) {
// path
var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor;
var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor;
// Apply height ratio
for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) {
coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio;
}
// Apply width ratio
for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) {
coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio;
}
}
// Apply value to raw path
var path = format(
rawPath.d, {
mx: mx,
my: my,
e: coordinates
}
);
return path;
};
}
// helpers //////////////////////
// copied and adjusted from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js
var tokenRegex = /\{([^{}]+)\}/g,
objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties
function replacer(all, key, obj) {
var res = obj;
key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) {
name = name || quotedName;
if (res) {
if (name in res) {
res = res[name];
}
typeof res == 'function' && isFunc && (res = res());
}
});
res = (res == null || res == obj ? all : res) + '';
return res;
}
function format(str, obj) {
return String(str).replace(tokenRegex, function(all, key) {
return replacer(all, key, obj);
});
}
var DrawModule = {
__init__: [ 'bpmnRenderer' ],
bpmnRenderer: [ 'type', BpmnRenderer ],
textRenderer: [ 'type', TextRenderer ],
pathMap: [ 'type', PathMap ]
};
/**
* A simple translation stub to be used for multi-language support
* in diagrams. Can be easily replaced with a more sophisticated
* solution.
*
* @example
*
* // use it inside any diagram component by injecting `translate`.
*
* function MyService(translate) {
* alert(translate('HELLO {you}', { you: 'You!' }));
* }
*
* @param {string} template to interpolate
* @param {Object} [replacements] a map with substitutes
*
* @return {string} the translated string
*/
function translate$1(template, replacements) {
replacements = replacements || {};
return template.replace(/{([^}]+)}/g, function(_, key) {
return replacements[key] || '{' + key + '}';
});
}
var translate = {
translate: [ 'value', translate$1 ]
};
var DEFAULT_LABEL_SIZE = {
width: 90,
height: 20
};
var FLOW_LABEL_INDENT = 15;
/**
* Returns true if the given semantic has an external label
*
* @param {BpmnElement} semantic
* @return {boolean} true if has label
*/
function isLabelExternal(semantic) {
return is$1(semantic, 'bpmn:Event') ||
is$1(semantic, 'bpmn:Gateway') ||
is$1(semantic, 'bpmn:DataStoreReference') ||
is$1(semantic, 'bpmn:DataObjectReference') ||
is$1(semantic, 'bpmn:DataInput') ||
is$1(semantic, 'bpmn:DataOutput') ||
is$1(semantic, 'bpmn:SequenceFlow') ||
is$1(semantic, 'bpmn:MessageFlow') ||
is$1(semantic, 'bpmn:Group');
}
/**
* Returns true if the given element has an external label
*
* @param {djs.model.shape} element
* @return {boolean} true if has label
*/
function hasExternalLabel(element) {
return isLabel$6(element.label);
}
/**
* Get the position for sequence flow labels
*
* @param {Array<Point>} waypoints
* @return {Point} the label position
*/
function getFlowLabelPosition(waypoints) {
// get the waypoints mid
var mid = waypoints.length / 2 - 1;
var first = waypoints[Math.floor(mid)];
var second = waypoints[Math.ceil(mid + 0.01)];
// get position
var position = getWaypointsMid(waypoints);
// calculate angle
var angle = Math.atan((second.y - first.y) / (second.x - first.x));
var x = position.x,
y = position.y;
if (Math.abs(angle) < Math.PI / 2) {
y -= FLOW_LABEL_INDENT;
} else {
x += FLOW_LABEL_INDENT;
}
return { x: x, y: y };
}
/**
* Get the middle of a number of waypoints
*
* @param {Array<Point>} waypoints
* @return {Point} the mid point
*/
function getWaypointsMid(waypoints) {
var mid = waypoints.length / 2 - 1;
var first = waypoints[Math.floor(mid)];
var second = waypoints[Math.ceil(mid + 0.01)];
return {
x: first.x + (second.x - first.x) / 2,
y: first.y + (second.y - first.y) / 2
};
}
function getExternalLabelMid(element) {
if (element.waypoints) {
return getFlowLabelPosition(element.waypoints);
} else if (is$1(element, 'bpmn:Group')) {
return {
x: element.x + element.width / 2,
y: element.y + DEFAULT_LABEL_SIZE.height / 2
};
} else {
return {
x: element.x + element.width / 2,
y: element.y + element.height + DEFAULT_LABEL_SIZE.height / 2
};
}
}
/**
* Returns the bounds of an elements label, parsed from the elements DI or
* generated from its bounds.
*
* @param {BpmndDi} di
* @param {djs.model.Base} element
*/
function getExternalLabelBounds(di, element) {
var mid,
size,
bounds,
label = di.label;
if (label && label.bounds) {
bounds = label.bounds;
size = {
width: Math.max(DEFAULT_LABEL_SIZE.width, bounds.width),
height: bounds.height
};
mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y + bounds.height / 2
};
} else {
mid = getExternalLabelMid(element);
size = DEFAULT_LABEL_SIZE;
}
return assign({
x: mid.x - size.width / 2,
y: mid.y - size.height / 2
}, size);
}
function isLabel$6(element) {
return element && !!element.labelTarget;
}
/**
* @param {ModdleElement} semantic
* @param {ModdleElement} di
* @param {Object} [attrs=null]
*
* @return {Object}
*/
function elementData(semantic, di, attrs) {
return assign({
id: semantic.id,
type: semantic.$type,
businessObject: semantic,
di: di
}, attrs);
}
function getWaypoints(di, source, target) {
var waypoints = di.waypoint;
if (!waypoints || waypoints.length < 2) {
return [ getMid(source), getMid(target) ];
}
return waypoints.map(function(p) {
return { x: p.x, y: p.y };
});
}
function notYetDrawn(translate, semantic, refSemantic, property) {
return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', {
element: elementToString(refSemantic),
referenced: elementToString(semantic),
property: property
}));
}
/**
* An importer that adds bpmn elements to the canvas
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {ElementFactory} elementFactory
* @param {ElementRegistry} elementRegistry
* @param {Function} translate
* @param {TextRenderer} textRenderer
*/
function BpmnImporter(
eventBus, canvas, elementFactory,
elementRegistry, translate, textRenderer) {
this._eventBus = eventBus;
this._canvas = canvas;
this._elementFactory = elementFactory;
this._elementRegistry = elementRegistry;
this._translate = translate;
this._textRenderer = textRenderer;
}
BpmnImporter.$inject = [
'eventBus',
'canvas',
'elementFactory',
'elementRegistry',
'translate',
'textRenderer'
];
/**
* Add bpmn element (semantic) to the canvas onto the
* specified parent shape.
*/
BpmnImporter.prototype.add = function(semantic, di, parentElement) {
var element,
translate = this._translate,
hidden;
var parentIndex;
// ROOT ELEMENT
// handle the special case that we deal with a
// invisible root element (process, subprocess or collaboration)
if (is$1(di, 'bpmndi:BPMNPlane')) {
var attrs = is$1(semantic, 'bpmn:SubProcess')
? { id: semantic.id + '_plane' }
: {};
// add a virtual element (not being drawn)
element = this._elementFactory.createRoot(elementData(semantic, di, attrs));
this._canvas.addRootElement(element);
}
// SHAPE
else if (is$1(di, 'bpmndi:BPMNShape')) {
var collapsed = !isExpanded(semantic, di),
isFrame = isFrameElement(semantic);
hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
var bounds = di.bounds;
element = this._elementFactory.createShape(elementData(semantic, di, {
collapsed: collapsed,
hidden: hidden,
x: Math.round(bounds.x),
y: Math.round(bounds.y),
width: Math.round(bounds.width),
height: Math.round(bounds.height),
isFrame: isFrame
}));
if (is$1(semantic, 'bpmn:BoundaryEvent')) {
this._attachBoundary(semantic, element);
}
// insert lanes behind other flow nodes (cf. #727)
if (is$1(semantic, 'bpmn:Lane')) {
parentIndex = 0;
}
if (is$1(semantic, 'bpmn:DataStoreReference')) {
// check whether data store is inside our outside of its semantic parent
if (!isPointInsideBBox$1(parentElement, getMid(bounds))) {
parentElement = this._canvas.findRoot(parentElement);
}
}
this._canvas.addShape(element, parentElement, parentIndex);
}
// CONNECTION
else if (is$1(di, 'bpmndi:BPMNEdge')) {
var source = this._getSource(semantic),
target = this._getTarget(semantic);
hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
element = this._elementFactory.createConnection(elementData(semantic, di, {
hidden: hidden,
source: source,
target: target,
waypoints: getWaypoints(di, source, target)
}));
if (is$1(semantic, 'bpmn:DataAssociation')) {
// render always on top; this ensures DataAssociations
// are rendered correctly across different "hacks" people
// love to model such as cross participant / sub process
// associations
parentElement = this._canvas.findRoot(parentElement);
}
this._canvas.addConnection(element, parentElement, parentIndex);
} else {
throw new Error(translate('unknown di {di} for element {semantic}', {
di: elementToString(di),
semantic: elementToString(semantic)
}));
}
// (optional) LABEL
if (isLabelExternal(semantic) && getLabel(element)) {
this.addLabel(semantic, di, element);
}
this._eventBus.fire('bpmnElement.added', { element: element });
return element;
};
/**
* Attach the boundary element to the given host
*
* @param {ModdleElement} boundarySemantic
* @param {djs.model.Base} boundaryElement
*/
BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) {
var translate = this._translate;
var hostSemantic = boundarySemantic.attachedToRef;
if (!hostSemantic) {
throw new Error(translate('missing {semantic}#attachedToRef', {
semantic: elementToString(boundarySemantic)
}));
}
var host = this._elementRegistry.get(hostSemantic.id),
attachers = host && host.attachers;
if (!host) {
throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef');
}
// wire element.host <> host.attachers
boundaryElement.host = host;
if (!attachers) {
host.attachers = attachers = [];
}
if (attachers.indexOf(boundaryElement) === -1) {
attachers.push(boundaryElement);
}
};
/**
* add label for an element
*/
BpmnImporter.prototype.addLabel = function(semantic, di, element) {
var bounds,
text,
label;
bounds = getExternalLabelBounds(di, element);
text = getLabel(element);
if (text) {
// get corrected bounds from actual layouted text
bounds = this._textRenderer.getExternalLabelBounds(bounds, text);
}
label = this._elementFactory.createLabel(elementData(semantic, di, {
id: semantic.id + '_label',
labelTarget: element,
type: 'label',
hidden: element.hidden || !getLabel(element),
x: Math.round(bounds.x),
y: Math.round(bounds.y),
width: Math.round(bounds.width),
height: Math.round(bounds.height)
}));
return this._canvas.addShape(label, element.parent);
};
/**
* Return the drawn connection end based on the given side.
*
* @throws {Error} if the end is not yet drawn
*/
BpmnImporter.prototype._getEnd = function(semantic, side) {
var element,
refSemantic,
type = semantic.$type,
translate = this._translate;
refSemantic = semantic[side + 'Ref'];
// handle mysterious isMany DataAssociation#sourceRef
if (side === 'source' && type === 'bpmn:DataInputAssociation') {
refSemantic = refSemantic && refSemantic[0];
}
// fix source / target for DataInputAssociation / DataOutputAssociation
if (side === 'source' && type === 'bpmn:DataOutputAssociation' ||
side === 'target' && type === 'bpmn:DataInputAssociation') {
refSemantic = semantic.$parent;
}
element = refSemantic && this._getElement(refSemantic);
if (element) {
return element;
}
if (refSemantic) {
throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref');
} else {
throw new Error(translate('{semantic}#{side} Ref not specified', {
semantic: elementToString(semantic),
side: side
}));
}
};
BpmnImporter.prototype._getSource = function(semantic) {
return this._getEnd(semantic, 'source');
};
BpmnImporter.prototype._getTarget = function(semantic) {
return this._getEnd(semantic, 'target');
};
BpmnImporter.prototype._getElement = function(semantic) {
return this._elementRegistry.get(semantic.id);
};
// helpers ////////////////////
function isPointInsideBBox$1(bbox, point) {
var x = point.x,
y = point.y;
return x >= bbox.x &&
x <= bbox.x + bbox.width &&
y >= bbox.y &&
y <= bbox.y + bbox.height;
}
function isFrameElement(semantic) {
return is$1(semantic, 'bpmn:Group');
}
var ImportModule = {
__depends__: [
translate
],
bpmnImporter: [ 'type', BpmnImporter ]
};
var CoreModule = {
__depends__: [
DrawModule,
ImportModule
]
};
function __stopPropagation(event) {
if (!event || typeof event.stopPropagation !== 'function') {
return;
}
event.stopPropagation();
}
function getOriginal$1(event) {
return event.originalEvent || event.srcEvent;
}
function stopPropagation$1(event, immediate) {
__stopPropagation(event);
__stopPropagation(getOriginal$1(event));
}
function toPoint(event) {
if (event.pointers && event.pointers.length) {
event = event.pointers[0];
}
if (event.touches && event.touches.length) {
event = event.touches[0];
}
return event ? {
x: event.clientX,
y: event.clientY
} : null;
}
function isMac() {
return (/mac/i).test(navigator.platform);
}
function isButton(event, button) {
return (getOriginal$1(event) || event).button === button;
}
function isPrimaryButton(event) {
// button === 0 -> left áka primary mouse button
return isButton(event, 0);
}
function isAuxiliaryButton(event) {
// button === 1 -> auxiliary áka wheel button
return isButton(event, 1);
}
function hasPrimaryModifier(event) {
var originalEvent = getOriginal$1(event) || event;
if (!isPrimaryButton(event)) {
return false;
}
// Use cmd as primary modifier key for mac OS
if (isMac()) {
return originalEvent.metaKey;
} else {
return originalEvent.ctrlKey;
}
}
function hasSecondaryModifier(event) {
var originalEvent = getOriginal$1(event) || event;
return isPrimaryButton(event) && originalEvent.shiftKey;
}
function allowAll(event) { return true; }
function allowPrimaryAndAuxiliary(event) {
return isPrimaryButton(event) || isAuxiliaryButton(event);
}
var LOW_PRIORITY$q = 500;
/**
* A plugin that provides interaction events for diagram elements.
*
* It emits the following events:
*
* * element.click
* * element.contextmenu
* * element.dblclick
* * element.hover
* * element.mousedown
* * element.mousemove
* * element.mouseup
* * element.out
*
* Each event is a tuple { element, gfx, originalEvent }.
*
* Canceling the event via Event#preventDefault()
* prevents the original DOM operation.
*
* @param {EventBus} eventBus
*/
function InteractionEvents(eventBus, elementRegistry, styles) {
var self = this;
/**
* Fire an interaction event.
*
* @param {string} type local event name, e.g. element.click.
* @param {DOMEvent} event native event
* @param {djs.model.Base} [element] the diagram element to emit the event on;
* defaults to the event target
*/
function fire(type, event, element) {
if (isIgnored(type, event)) {
return;
}
var target, gfx, returnValue;
if (!element) {
target = event.delegateTarget || event.target;
if (target) {
gfx = target;
element = elementRegistry.get(gfx);
}
} else {
gfx = elementRegistry.getGraphics(element);
}
if (!gfx || !element) {
return;
}
returnValue = eventBus.fire(type, {
element: element,
gfx: gfx,
originalEvent: event
});
if (returnValue === false) {
event.stopPropagation();
event.preventDefault();
}
}
// TODO(nikku): document this
var handlers = {};
function mouseHandler(localEventName) {
return handlers[localEventName];
}
function isIgnored(localEventName, event) {
var filter = ignoredFilters[localEventName] || isPrimaryButton;
// only react on left mouse button interactions
// except for interaction events that are enabled
// for secundary mouse button
return !filter(event);
}
var bindings = {
click: 'element.click',
contextmenu: 'element.contextmenu',
dblclick: 'element.dblclick',
mousedown: 'element.mousedown',
mousemove: 'element.mousemove',
mouseover: 'element.hover',
mouseout: 'element.out',
mouseup: 'element.mouseup',
};
var ignoredFilters = {
'element.contextmenu': allowAll,
'element.mousedown': allowPrimaryAndAuxiliary,
'element.mouseup': allowPrimaryAndAuxiliary,
'element.click': allowPrimaryAndAuxiliary,
'element.dblclick': allowPrimaryAndAuxiliary
};
// manual event trigger //////////
/**
* Trigger an interaction event (based on a native dom event)
* on the target shape or connection.
*
* @param {string} eventName the name of the triggered DOM event
* @param {MouseEvent} event
* @param {djs.model.Base} targetElement
*/
function triggerMouseEvent(eventName, event, targetElement) {
// i.e. element.mousedown...
var localEventName = bindings[eventName];
if (!localEventName) {
throw new Error('unmapped DOM event name <' + eventName + '>');
}
return fire(localEventName, event, targetElement);
}
var ELEMENT_SELECTOR = 'svg, .djs-element';
// event handling ///////
function registerEvent(node, event, localEvent, ignoredFilter) {
var handler = handlers[localEvent] = function(event) {
fire(localEvent, event);
};
if (ignoredFilter) {
ignoredFilters[localEvent] = ignoredFilter;
}
handler.$delegate = delegate.bind(node, ELEMENT_SELECTOR, event, handler);
}
function unregisterEvent(node, event, localEvent) {
var handler = mouseHandler(localEvent);
if (!handler) {
return;
}
delegate.unbind(node, event, handler.$delegate);
}
function registerEvents(svg) {
forEach$1(bindings, function(val, key) {
registerEvent(svg, key, val);
});
}
function unregisterEvents(svg) {
forEach$1(bindings, function(val, key) {
unregisterEvent(svg, key, val);
});
}
eventBus.on('canvas.destroy', function(event) {
unregisterEvents(event.svg);
});
eventBus.on('canvas.init', function(event) {
registerEvents(event.svg);
});
// hit box updating ////////////////
eventBus.on([ 'shape.added', 'connection.added' ], function(event) {
var element = event.element,
gfx = event.gfx;
eventBus.fire('interactionEvents.createHit', { element: element, gfx: gfx });
});
// Update djs-hit on change.
// A low priortity is necessary, because djs-hit of labels has to be updated
// after the label bounds have been updated in the renderer.
eventBus.on([
'shape.changed',
'connection.changed'
], LOW_PRIORITY$q, function(event) {
var element = event.element,
gfx = event.gfx;
eventBus.fire('interactionEvents.updateHit', { element: element, gfx: gfx });
});
eventBus.on('interactionEvents.createHit', LOW_PRIORITY$q, function(event) {
var element = event.element,
gfx = event.gfx;
self.createDefaultHit(element, gfx);
});
eventBus.on('interactionEvents.updateHit', function(event) {
var element = event.element,
gfx = event.gfx;
self.updateDefaultHit(element, gfx);
});
// hit styles ////////////
var STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-stroke');
var CLICK_STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-click-stroke');
var ALL_HIT_STYLE = createHitStyle('djs-hit djs-hit-all');
var NO_MOVE_HIT_STYLE = createHitStyle('djs-hit djs-hit-no-move');
var HIT_TYPES = {
'all': ALL_HIT_STYLE,
'click-stroke': CLICK_STROKE_HIT_STYLE,
'stroke': STROKE_HIT_STYLE,
'no-move': NO_MOVE_HIT_STYLE
};
function createHitStyle(classNames, attrs) {
attrs = assign({
stroke: 'white',
strokeWidth: 15
}, attrs || {});
return styles.cls(classNames, [ 'no-fill', 'no-border' ], attrs);
}
// style helpers ///////////////
function applyStyle(hit, type) {
var attrs = HIT_TYPES[type];
if (!attrs) {
throw new Error('invalid hit type <' + type + '>');
}
attr(hit, attrs);
return hit;
}
function appendHit(gfx, hit) {
append(gfx, hit);
}
// API
/**
* Remove hints on the given graphics.
*
* @param {SVGElement} gfx
*/
this.removeHits = function(gfx) {
var hits = all('.djs-hit', gfx);
forEach$1(hits, remove$1);
};
/**
* Create default hit for the given element.
*
* @param {djs.model.Base} element
* @param {SVGElement} gfx
*
* @return {SVGElement} created hit
*/
this.createDefaultHit = function(element, gfx) {
var waypoints = element.waypoints,
isFrame = element.isFrame,
boxType;
if (waypoints) {
return this.createWaypointsHit(gfx, waypoints);
} else {
boxType = isFrame ? 'stroke' : 'all';
return this.createBoxHit(gfx, boxType, {
width: element.width,
height: element.height
});
}
};
/**
* Create hits for the given waypoints.
*
* @param {SVGElement} gfx
* @param {Array<Point>} waypoints
*
* @return {SVGElement}
*/
this.createWaypointsHit = function(gfx, waypoints) {
var hit = createLine(waypoints);
applyStyle(hit, 'stroke');
appendHit(gfx, hit);
return hit;
};
/**
* Create hits for a box.
*
* @param {SVGElement} gfx
* @param {string} hitType
* @param {Object} attrs
*
* @return {SVGElement}
*/
this.createBoxHit = function(gfx, type, attrs) {
attrs = assign({
x: 0,
y: 0
}, attrs);
var hit = create$1('rect');
applyStyle(hit, type);
attr(hit, attrs);
appendHit(gfx, hit);
return hit;
};
/**
* Update default hit of the element.
*
* @param {djs.model.Base} element
* @param {SVGElement} gfx
*
* @return {SVGElement} updated hit
*/
this.updateDefaultHit = function(element, gfx) {
var hit = query('.djs-hit', gfx);
if (!hit) {
return;
}
if (element.waypoints) {
updateLine(hit, element.waypoints);
} else {
attr(hit, {
width: element.width,
height: element.height
});
}
return hit;
};
this.fire = fire;
this.triggerMouseEvent = triggerMouseEvent;
this.mouseHandler = mouseHandler;
this.registerEvent = registerEvent;
this.unregisterEvent = unregisterEvent;
}
InteractionEvents.$inject = [
'eventBus',
'elementRegistry',
'styles'
];
/**
* An event indicating that the mouse hovered over an element
*
* @event element.hover
*
* @type {Object}
* @property {djs.model.Base} element
* @property {SVGElement} gfx
* @property {Event} originalEvent
*/
/**
* An event indicating that the mouse has left an element
*
* @event element.out
*
* @type {Object}
* @property {djs.model.Base} element
* @property {SVGElement} gfx
* @property {Event} originalEvent
*/
/**
* An event indicating that the mouse has clicked an element
*
* @event element.click
*
* @type {Object}
* @property {djs.model.Base} element
* @property {SVGElement} gfx
* @property {Event} originalEvent
*/
/**
* An event indicating that the mouse has double clicked an element
*
* @event element.dblclick
*
* @type {Object}
* @property {djs.model.Base} element
* @property {SVGElement} gfx
* @property {Event} originalEvent
*/
/**
* An event indicating that the mouse has gone down on an element.
*
* @event element.mousedown
*
* @type {Object}
* @property {djs.model.Base} element
* @property {SVGElement} gfx
* @property {Event} originalEvent
*/
/**
* An event indicating that the mouse has gone up on an element.
*
* @event element.mouseup
*
* @type {Object}
* @property {djs.model.Base} element
* @property {SVGElement} gfx
* @property {Event} originalEvent
*/
/**
* An event indicating that the context menu action is triggered
* via mouse or touch controls.
*
* @event element.contextmenu
*
* @type {Object}
* @property {djs.model.Base} element
* @property {SVGElement} gfx
* @property {Event} originalEvent
*/
var InteractionEventsModule$1 = {
__init__: [ 'interactionEvents' ],
interactionEvents: [ 'type', InteractionEvents ]
};
var LOW_PRIORITY$p = 500;
/**
* @class
*
* A plugin that adds an outline to shapes and connections that may be activated and styled
* via CSS classes.
*
* @param {EventBus} eventBus
* @param {Styles} styles
* @param {ElementRegistry} elementRegistry
*/
function Outline(eventBus, styles, elementRegistry) {
this.offset = 6;
var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]);
var self = this;
function createOutline(gfx, bounds) {
var outline = create$1('rect');
attr(outline, assign({
x: 10,
y: 10,
rx: 3,
width: 100,
height: 100
}, OUTLINE_STYLE));
append(gfx, outline);
return outline;
}
// A low priortity is necessary, because outlines of labels have to be updated
// after the label bounds have been updated in the renderer.
eventBus.on([ 'shape.added', 'shape.changed' ], LOW_PRIORITY$p, function(event) {
var element = event.element,
gfx = event.gfx;
var outline = query('.djs-outline', gfx);
if (!outline) {
outline = createOutline(gfx);
}
self.updateShapeOutline(outline, element);
});
eventBus.on([ 'connection.added', 'connection.changed' ], function(event) {
var element = event.element,
gfx = event.gfx;
var outline = query('.djs-outline', gfx);
if (!outline) {
outline = createOutline(gfx);
}
self.updateConnectionOutline(outline, element);
});
}
/**
* Updates the outline of a shape respecting the dimension of the
* element and an outline offset.
*
* @param {SVGElement} outline
* @param {djs.model.Base} element
*/
Outline.prototype.updateShapeOutline = function(outline, element) {
attr(outline, {
x: -this.offset,
y: -this.offset,
width: element.width + this.offset * 2,
height: element.height + this.offset * 2
});
};
/**
* Updates the outline of a connection respecting the bounding box of
* the connection and an outline offset.
*
* @param {SVGElement} outline
* @param {djs.model.Base} element
*/
Outline.prototype.updateConnectionOutline = function(outline, connection) {
var bbox = getBBox(connection);
attr(outline, {
x: bbox.x - this.offset,
y: bbox.y - this.offset,
width: bbox.width + this.offset * 2,
height: bbox.height + this.offset * 2
});
};
Outline.$inject = [ 'eventBus', 'styles', 'elementRegistry' ];
var OutlineModule = {
__init__: [ 'outline' ],
outline: [ 'type', Outline ]
};
/**
* A service that offers the current selection in a diagram.
* Offers the api to control the selection, too.
*
* @class
*
* @param {EventBus} eventBus the event bus
*/
function Selection(eventBus, canvas) {
this._eventBus = eventBus;
this._canvas = canvas;
this._selectedElements = [];
var self = this;
eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
var element = e.element;
self.deselect(element);
});
eventBus.on([ 'diagram.clear', 'root.set' ], function(e) {
self.select(null);
});
}
Selection.$inject = [ 'eventBus', 'canvas' ];
Selection.prototype.deselect = function(element) {
var selectedElements = this._selectedElements;
var idx = selectedElements.indexOf(element);
if (idx !== -1) {
var oldSelection = selectedElements.slice();
selectedElements.splice(idx, 1);
this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
}
};
Selection.prototype.get = function() {
return this._selectedElements;
};
Selection.prototype.isSelected = function(element) {
return this._selectedElements.indexOf(element) !== -1;
};
/**
* This method selects one or more elements on the diagram.
*
* By passing an additional add parameter you can decide whether or not the element(s)
* should be added to the already existing selection or not.
*
* @method Selection#select
*
* @param {Object|Object[]} elements element or array of elements to be selected
* @param {boolean} [add] whether the element(s) should be appended to the current selection, defaults to false
*/
Selection.prototype.select = function(elements, add) {
var selectedElements = this._selectedElements,
oldSelection = selectedElements.slice();
if (!isArray$3(elements)) {
elements = elements ? [ elements ] : [];
}
var canvas = this._canvas;
var rootElement = canvas.getRootElement();
elements = elements.filter(function(element) {
var elementRoot = canvas.findRoot(element);
return rootElement === elementRoot;
});
// selection may be cleared by passing an empty array or null
// to the method
if (add) {
forEach$1(elements, function(element) {
if (selectedElements.indexOf(element) !== -1) {
// already selected
return;
} else {
selectedElements.push(element);
}
});
} else {
this._selectedElements = selectedElements = elements.slice();
}
this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements });
};
var MARKER_HOVER = 'hover',
MARKER_SELECTED = 'selected';
var SELECTION_OUTLINE_PADDING = 6;
/**
* A plugin that adds a visible selection UI to shapes and connections
* by appending the <code>hover</code> and <code>selected</code> classes to them.
*
* @class
*
* Makes elements selectable, too.
*
* @param {Canvas} canvas
* @param {EventBus} eventBus
*/
function SelectionVisuals(canvas, eventBus, selection) {
this._canvas = canvas;
var self = this;
this._multiSelectionBox = null;
function addMarker(e, cls) {
canvas.addMarker(e, cls);
}
function removeMarker(e, cls) {
canvas.removeMarker(e, cls);
}
eventBus.on('element.hover', function(event) {
addMarker(event.element, MARKER_HOVER);
});
eventBus.on('element.out', function(event) {
removeMarker(event.element, MARKER_HOVER);
});
eventBus.on('selection.changed', function(event) {
function deselect(s) {
removeMarker(s, MARKER_SELECTED);
}
function select(s) {
addMarker(s, MARKER_SELECTED);
}
var oldSelection = event.oldSelection,
newSelection = event.newSelection;
forEach$1(oldSelection, function(e) {
if (newSelection.indexOf(e) === -1) {
deselect(e);
}
});
forEach$1(newSelection, function(e) {
if (oldSelection.indexOf(e) === -1) {
select(e);
}
});
self._updateSelectionOutline(newSelection);
});
eventBus.on('element.changed', function(event) {
if (selection.isSelected(event.element)) {
self._updateSelectionOutline(selection.get());
}
});
}
SelectionVisuals.$inject = [
'canvas',
'eventBus',
'selection'
];
SelectionVisuals.prototype._updateSelectionOutline = function(selection) {
var layer = this._canvas.getLayer('selectionOutline');
clear(layer);
var enabled = selection.length > 1;
var container = this._canvas.getContainer();
classes(container)[enabled ? 'add' : 'remove']('djs-multi-select');
if (!enabled) {
return;
}
var bBox = addSelectionOutlinePadding(getBBox(selection));
var rect = create$1('rect');
attr(rect, assign({
rx: 3
}, bBox));
classes(rect).add('djs-selection-outline');
append(layer, rect);
};
// helpers //////////
function addSelectionOutlinePadding(bBox) {
return {
x: bBox.x - SELECTION_OUTLINE_PADDING,
y: bBox.y - SELECTION_OUTLINE_PADDING,
width: bBox.width + SELECTION_OUTLINE_PADDING * 2,
height: bBox.height + SELECTION_OUTLINE_PADDING * 2
};
}
function SelectionBehavior(eventBus, selection, canvas, elementRegistry) {
// Select elements on create
eventBus.on('create.end', 500, function(event) {
var context = event.context,
canExecute = context.canExecute,
elements = context.elements,
hints = context.hints || {},
autoSelect = hints.autoSelect;
if (canExecute) {
if (autoSelect === false) {
// Select no elements
return;
}
if (isArray$3(autoSelect)) {
selection.select(autoSelect);
} else {
// Select all elements by default
selection.select(elements.filter(isShown));
}
}
});
// Select connection targets on connect
eventBus.on('connect.end', 500, function(event) {
var context = event.context,
connection = context.connection;
if (connection) {
selection.select(connection);
}
});
// Select shapes on move
eventBus.on('shape.move.end', 500, function(event) {
var previousSelection = event.previousSelection || [];
var shape = elementRegistry.get(event.context.shape.id);
// Always select main shape on move
var isSelected = find(previousSelection, function(selectedShape) {
return shape.id === selectedShape.id;
});
if (!isSelected) {
selection.select(shape);
}
});
// Select elements on click
eventBus.on('element.click', function(event) {
if (!isPrimaryButton(event)) {
return;
}
var element = event.element;
if (element === canvas.getRootElement()) {
element = null;
}
var isSelected = selection.isSelected(element),
isMultiSelect = selection.get().length > 1;
// Add to selection if CTRL or SHIFT pressed
var add = hasPrimaryModifier(event) || hasSecondaryModifier(event);
if (isSelected && isMultiSelect) {
if (add) {
// Deselect element
return selection.deselect(element);
} else {
// Select element only
return selection.select(element);
}
} else if (!isSelected) {
// Select element
selection.select(element, add);
} else {
// Deselect element
selection.deselect(element);
}
});
}
SelectionBehavior.$inject = [
'eventBus',
'selection',
'canvas',
'elementRegistry'
];
function isShown(element) {
return !element.hidden;
}
var SelectionModule = {
__init__: [ 'selectionVisuals', 'selectionBehavior' ],
__depends__: [
InteractionEventsModule$1,
OutlineModule
],
selection: [ 'type', Selection ],
selectionVisuals: [ 'type', SelectionVisuals ],
selectionBehavior: [ 'type', SelectionBehavior ]
};
/**
* Util that provides unique IDs.
*
* @class djs.util.IdGenerator
* @constructor
* @memberOf djs.util
*
* The ids can be customized via a given prefix and contain a random value to avoid collisions.
*
* @param {string} prefix a prefix to prepend to generated ids (for better readability)
*/
function IdGenerator(prefix) {
this._counter = 0;
this._prefix = (prefix ? prefix + '-' : '') + Math.floor(Math.random() * 1000000000) + '-';
}
/**
* Returns a next unique ID.
*
* @method djs.util.IdGenerator#next
*
* @returns {string} the id
*/
IdGenerator.prototype.next = function() {
return this._prefix + (++this._counter);
};
// document wide unique overlay ids
var ids$1 = new IdGenerator('ov');
var LOW_PRIORITY$o = 500;
/**
* A service that allows users to attach overlays to diagram elements.
*
* The overlay service will take care of overlay positioning during updates.
*
* @example
*
* // add a pink badge on the top left of the shape
* overlays.add(someShape, {
* position: {
* top: -5,
* left: -5
* },
* html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
* });
*
* // or add via shape id
*
* overlays.add('some-element-id', {
* position: {
* top: -5,
* left: -5
* }
* html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
* });
*
* // or add with optional type
*
* overlays.add(someShape, 'badge', {
* position: {
* top: -5,
* left: -5
* }
* html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
* });
*
*
* // remove an overlay
*
* var id = overlays.add(...);
* overlays.remove(id);
*
*
* You may configure overlay defaults during tool by providing a `config` module
* with `overlays.defaults` as an entry:
*
* {
* overlays: {
* defaults: {
* show: {
* minZoom: 0.7,
* maxZoom: 5.0
* },
* scale: {
* min: 1
* }
* }
* }
*
* @param {Object} config
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {ElementRegistry} elementRegistry
*/
function Overlays(config, eventBus, canvas, elementRegistry) {
this._eventBus = eventBus;
this._canvas = canvas;
this._elementRegistry = elementRegistry;
this._ids = ids$1;
this._overlayDefaults = assign({
// no show constraints
show: null,
// always scale
scale: true
}, config && config.defaults);
/**
* Mapping overlayId -> overlay
*/
this._overlays = {};
/**
* Mapping elementId -> overlay container
*/
this._overlayContainers = [];
// root html element for all overlays
this._overlayRoot = createRoot$1(canvas.getContainer());
this._init();
}
Overlays.$inject = [
'config.overlays',
'eventBus',
'canvas',
'elementRegistry'
];
/**
* Returns the overlay with the specified id or a list of overlays
* for an element with a given type.
*
* @example
*
* // return the single overlay with the given id
* overlays.get('some-id');
*
* // return all overlays for the shape
* overlays.get({ element: someShape });
*
* // return all overlays on shape with type 'badge'
* overlays.get({ element: someShape, type: 'badge' });
*
* // shape can also be specified as id
* overlays.get({ element: 'element-id', type: 'badge' });
*
*
* @param {Object} search
* @param {string} [search.id]
* @param {string|djs.model.Base} [search.element]
* @param {string} [search.type]
*
* @return {Object|Array<Object>} the overlay(s)
*/
Overlays.prototype.get = function(search) {
if (isString(search)) {
search = { id: search };
}
if (isString(search.element)) {
search.element = this._elementRegistry.get(search.element);
}
if (search.element) {
var container = this._getOverlayContainer(search.element, true);
// return a list of overlays when searching by element (+type)
if (container) {
return search.type ? filter(container.overlays, matchPattern({ type: search.type })) : container.overlays.slice();
} else {
return [];
}
} else
if (search.type) {
return filter(this._overlays, matchPattern({ type: search.type }));
} else {
// return single element when searching by id
return search.id ? this._overlays[search.id] : null;
}
};
/**
* Adds a HTML overlay to an element.
*
* @param {string|djs.model.Base} element attach overlay to this shape
* @param {string} [type] optional type to assign to the overlay
* @param {Object} overlay the overlay configuration
*
* @param {string|DOMElement} overlay.html html element to use as an overlay
* @param {Object} [overlay.show] show configuration
* @param {number} [overlay.show.minZoom] minimal zoom level to show the overlay
* @param {number} [overlay.show.maxZoom] maximum zoom level to show the overlay
* @param {Object} overlay.position where to attach the overlay
* @param {number} [overlay.position.left] relative to element bbox left attachment
* @param {number} [overlay.position.top] relative to element bbox top attachment
* @param {number} [overlay.position.bottom] relative to element bbox bottom attachment
* @param {number} [overlay.position.right] relative to element bbox right attachment
* @param {boolean|Object} [overlay.scale=true] false to preserve the same size regardless of
* diagram zoom
* @param {number} [overlay.scale.min]
* @param {number} [overlay.scale.max]
*
* @return {string} id that may be used to reference the overlay for update or removal
*/
Overlays.prototype.add = function(element, type, overlay) {
if (isObject(type)) {
overlay = type;
type = null;
}
if (!element.id) {
element = this._elementRegistry.get(element);
}
if (!overlay.position) {
throw new Error('must specifiy overlay position');
}
if (!overlay.html) {
throw new Error('must specifiy overlay html');
}
if (!element) {
throw new Error('invalid element specified');
}
var id = this._ids.next();
overlay = assign({}, this._overlayDefaults, overlay, {
id: id,
type: type,
element: element,
html: overlay.html
});
this._addOverlay(overlay);
return id;
};
/**
* Remove an overlay with the given id or all overlays matching the given filter.
*
* @see Overlays#get for filter options.
*
* @param {string|object} [filter]
*/
Overlays.prototype.remove = function(filter) {
var overlays = this.get(filter) || [];
if (!isArray$3(overlays)) {
overlays = [ overlays ];
}
var self = this;
forEach$1(overlays, function(overlay) {
var container = self._getOverlayContainer(overlay.element, true);
if (overlay) {
remove$2(overlay.html);
remove$2(overlay.htmlContainer);
delete overlay.htmlContainer;
delete overlay.element;
delete self._overlays[overlay.id];
}
if (container) {
var idx = container.overlays.indexOf(overlay);
if (idx !== -1) {
container.overlays.splice(idx, 1);
}
}
});
};
Overlays.prototype.show = function() {
setVisible$1(this._overlayRoot);
};
Overlays.prototype.hide = function() {
setVisible$1(this._overlayRoot, false);
};
Overlays.prototype.clear = function() {
this._overlays = {};
this._overlayContainers = [];
clear$1(this._overlayRoot);
};
Overlays.prototype._updateOverlayContainer = function(container) {
var element = container.element,
html = container.html;
// update container left,top according to the elements x,y coordinates
// this ensures we can attach child elements relative to this container
var x = element.x,
y = element.y;
if (element.waypoints) {
var bbox = getBBox(element);
x = bbox.x;
y = bbox.y;
}
setPosition$1(html, x, y);
attr$1(container.html, 'data-container-id', element.id);
};
Overlays.prototype._updateOverlay = function(overlay) {
var position = overlay.position,
htmlContainer = overlay.htmlContainer,
element = overlay.element;
// update overlay html relative to shape because
// it is already positioned on the element
// update relative
var left = position.left,
top = position.top;
if (position.right !== undefined) {
var width;
if (element.waypoints) {
width = getBBox(element).width;
} else {
width = element.width;
}
left = position.right * -1 + width;
}
if (position.bottom !== undefined) {
var height;
if (element.waypoints) {
height = getBBox(element).height;
} else {
height = element.height;
}
top = position.bottom * -1 + height;
}
setPosition$1(htmlContainer, left || 0, top || 0);
this._updateOverlayVisibilty(overlay, this._canvas.viewbox());
};
Overlays.prototype._createOverlayContainer = function(element) {
var html = domify('<div class="djs-overlays" />');
assign$1(html, { position: 'absolute' });
this._overlayRoot.appendChild(html);
var container = {
html: html,
element: element,
overlays: []
};
this._updateOverlayContainer(container);
this._overlayContainers.push(container);
return container;
};
Overlays.prototype._updateRoot = function(viewbox) {
var scale = viewbox.scale || 1;
var matrix = 'matrix(' +
[
scale,
0,
0,
scale,
-1 * viewbox.x * scale,
-1 * viewbox.y * scale
].join(',') +
')';
setTransform$1(this._overlayRoot, matrix);
};
Overlays.prototype._getOverlayContainer = function(element, raw) {
var container = find(this._overlayContainers, function(c) {
return c.element === element;
});
if (!container && !raw) {
return this._createOverlayContainer(element);
}
return container;
};
Overlays.prototype._addOverlay = function(overlay) {
var id = overlay.id,
element = overlay.element,
html = overlay.html,
htmlContainer,
overlayContainer;
// unwrap jquery (for those who need it)
if (html.get && html.constructor.prototype.jquery) {
html = html.get(0);
}
// create proper html elements from
// overlay HTML strings
if (isString(html)) {
html = domify(html);
}
overlayContainer = this._getOverlayContainer(element);
htmlContainer = domify('<div class="djs-overlay" data-overlay-id="' + id + '">');
assign$1(htmlContainer, { position: 'absolute' });
htmlContainer.appendChild(html);
if (overlay.type) {
classes$1(htmlContainer).add('djs-overlay-' + overlay.type);
}
var elementRoot = this._canvas.findRoot(element);
var activeRoot = this._canvas.getRootElement();
setVisible$1(htmlContainer, elementRoot === activeRoot);
overlay.htmlContainer = htmlContainer;
overlayContainer.overlays.push(overlay);
overlayContainer.html.appendChild(htmlContainer);
this._overlays[id] = overlay;
this._updateOverlay(overlay);
this._updateOverlayVisibilty(overlay, this._canvas.viewbox());
};
Overlays.prototype._updateOverlayVisibilty = function(overlay, viewbox) {
var show = overlay.show,
rootElement = this._canvas.findRoot(overlay.element),
minZoom = show && show.minZoom,
maxZoom = show && show.maxZoom,
htmlContainer = overlay.htmlContainer,
activeRootElement = this._canvas.getRootElement(),
visible = true;
if (rootElement !== activeRootElement) {
visible = false;
} else if (show) {
if (
(isDefined(minZoom) && minZoom > viewbox.scale) ||
(isDefined(maxZoom) && maxZoom < viewbox.scale)
) {
visible = false;
}
}
setVisible$1(htmlContainer, visible);
this._updateOverlayScale(overlay, viewbox);
};
Overlays.prototype._updateOverlayScale = function(overlay, viewbox) {
var shouldScale = overlay.scale,
minScale,
maxScale,
htmlContainer = overlay.htmlContainer;
var scale, transform = '';
if (shouldScale !== true) {
if (shouldScale === false) {
minScale = 1;
maxScale = 1;
} else {
minScale = shouldScale.min;
maxScale = shouldScale.max;
}
if (isDefined(minScale) && viewbox.scale < minScale) {
scale = (1 / viewbox.scale || 1) * minScale;
}
if (isDefined(maxScale) && viewbox.scale > maxScale) {
scale = (1 / viewbox.scale || 1) * maxScale;
}
}
if (isDefined(scale)) {
transform = 'scale(' + scale + ',' + scale + ')';
}
setTransform$1(htmlContainer, transform);
};
Overlays.prototype._updateOverlaysVisibilty = function(viewbox) {
var self = this;
forEach$1(this._overlays, function(overlay) {
self._updateOverlayVisibilty(overlay, viewbox);
});
};
Overlays.prototype._init = function() {
var eventBus = this._eventBus;
var self = this;
// scroll/zoom integration
function updateViewbox(viewbox) {
self._updateRoot(viewbox);
self._updateOverlaysVisibilty(viewbox);
self.show();
}
eventBus.on('canvas.viewbox.changing', function(event) {
self.hide();
});
eventBus.on('canvas.viewbox.changed', function(event) {
updateViewbox(event.viewbox);
});
// remove integration
eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
var element = e.element;
var overlays = self.get({ element: element });
forEach$1(overlays, function(o) {
self.remove(o.id);
});
var container = self._getOverlayContainer(element);
if (container) {
remove$2(container.html);
var i = self._overlayContainers.indexOf(container);
if (i !== -1) {
self._overlayContainers.splice(i, 1);
}
}
});
// move integration
eventBus.on('element.changed', LOW_PRIORITY$o, function(e) {
var element = e.element;
var container = self._getOverlayContainer(element, true);
if (container) {
forEach$1(container.overlays, function(overlay) {
self._updateOverlay(overlay);
});
self._updateOverlayContainer(container);
}
});
// marker integration, simply add them on the overlays as classes, too.
eventBus.on('element.marker.update', function(e) {
var container = self._getOverlayContainer(e.element, true);
if (container) {
classes$1(container.html)[e.add ? 'add' : 'remove'](e.marker);
}
});
eventBus.on('root.set', function() {
self._updateOverlaysVisibilty(self._canvas.viewbox());
});
// clear overlays with diagram
eventBus.on('diagram.clear', this.clear, this);
};
// helpers /////////////////////////////
function createRoot$1(parentNode) {
var root = domify(
'<div class="djs-overlay-container" />'
);
assign$1(root, {
position: 'absolute',
width: 0,
height: 0
});
parentNode.insertBefore(root, parentNode.firstChild);
return root;
}
function setPosition$1(el, x, y) {
assign$1(el, { left: x + 'px', top: y + 'px' });
}
/**
* Set element visible
*
* @param {DOMElement} el
* @param {boolean} [visible=true]
*/
function setVisible$1(el, visible) {
el.style.display = visible === false ? 'none' : '';
}
function setTransform$1(el, transform) {
el.style['transform-origin'] = 'top left';
[ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
el.style[prefix + 'transform'] = transform;
});
}
var OverlaysModule = {
__init__: [ 'overlays' ],
overlays: [ 'type', Overlays ]
};
/**
* Adds change support to the diagram, including
*
* <ul>
* <li>redrawing shapes and connections on change</li>
* </ul>
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {ElementRegistry} elementRegistry
* @param {GraphicsFactory} graphicsFactory
*/
function ChangeSupport(
eventBus, canvas, elementRegistry,
graphicsFactory) {
// redraw shapes / connections on change
eventBus.on('element.changed', function(event) {
var element = event.element;
// element might have been deleted and replaced by new element with same ID
// thus check for parent of element except for root element
if (element.parent || element === canvas.getRootElement()) {
event.gfx = elementRegistry.getGraphics(element);
}
// shape + gfx may have been deleted
if (!event.gfx) {
return;
}
eventBus.fire(getType(element) + '.changed', event);
});
eventBus.on('elements.changed', function(event) {
var elements = event.elements;
elements.forEach(function(e) {
eventBus.fire('element.changed', { element: e });
});
graphicsFactory.updateContainments(elements);
});
eventBus.on('shape.changed', function(event) {
graphicsFactory.update('shape', event.element, event.gfx);
});
eventBus.on('connection.changed', function(event) {
graphicsFactory.update('connection', event.element, event.gfx);
});
}
ChangeSupport.$inject = [
'eventBus',
'canvas',
'elementRegistry',
'graphicsFactory'
];
var ChangeSupportModule = {
__init__: [ 'changeSupport' ],
changeSupport: [ 'type', ChangeSupport ]
};
var DEFAULT_PRIORITY$4 = 1000;
/**
* A utility that can be used to plug-in into the command execution for
* extension and/or validation.
*
* @param {EventBus} eventBus
*
* @example
*
* import inherits from 'inherits-browser';
*
* import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
*
* function CommandLogger(eventBus) {
* CommandInterceptor.call(this, eventBus);
*
* this.preExecute(function(event) {
* console.log('command pre-execute', event);
* });
* }
*
* inherits(CommandLogger, CommandInterceptor);
*
*/
function CommandInterceptor(eventBus) {
this._eventBus = eventBus;
}
CommandInterceptor.$inject = [ 'eventBus' ];
function unwrapEvent(fn, that) {
return function(event) {
return fn.call(that || null, event.context, event.command, event);
};
}
/**
* Register an interceptor for a command execution
*
* @param {string|Array<string>} [events] list of commands to register on
* @param {string} [hook] command hook, i.e. preExecute, executed to listen on
* @param {number} [priority] the priority on which to hook into the execution
* @param {Function} handlerFn interceptor to be invoked with (event)
* @param {boolean} unwrap if true, unwrap the event and pass (context, command, event) to the
* listener instead
* @param {Object} [that] Pass context (`this`) to the handler function
*/
CommandInterceptor.prototype.on = function(events, hook, priority, handlerFn, unwrap, that) {
if (isFunction(hook) || isNumber(hook)) {
that = unwrap;
unwrap = handlerFn;
handlerFn = priority;
priority = hook;
hook = null;
}
if (isFunction(priority)) {
that = unwrap;
unwrap = handlerFn;
handlerFn = priority;
priority = DEFAULT_PRIORITY$4;
}
if (isObject(unwrap)) {
that = unwrap;
unwrap = false;
}
if (!isFunction(handlerFn)) {
throw new Error('handlerFn must be a function');
}
if (!isArray$3(events)) {
events = [ events ];
}
var eventBus = this._eventBus;
forEach$1(events, function(event) {
// concat commandStack(.event)?(.hook)?
var fullEvent = [ 'commandStack', event, hook ].filter(function(e) { return e; }).join('.');
eventBus.on(fullEvent, priority, unwrap ? unwrapEvent(handlerFn, that) : handlerFn, that);
});
};
var hooks = [
'canExecute',
'preExecute',
'preExecuted',
'execute',
'executed',
'postExecute',
'postExecuted',
'revert',
'reverted'
];
/*
* Install hook shortcuts
*
* This will generate the CommandInterceptor#(preExecute|...|reverted) methods
* which will in term forward to CommandInterceptor#on.
*/
forEach$1(hooks, function(hook) {
/**
* {canExecute|preExecute|preExecuted|execute|executed|postExecute|postExecuted|revert|reverted}
*
* A named hook for plugging into the command execution
*
* @param {string|Array<string>} [events] list of commands to register on
* @param {number} [priority] the priority on which to hook into the execution
* @param {Function} handlerFn interceptor to be invoked with (event)
* @param {boolean} [unwrap=false] if true, unwrap the event and pass (context, command, event) to the
* listener instead
* @param {Object} [that] Pass context (`this`) to the handler function
*/
CommandInterceptor.prototype[hook] = function(events, priority, handlerFn, unwrap, that) {
if (isFunction(events) || isNumber(events)) {
that = unwrap;
unwrap = handlerFn;
handlerFn = priority;
priority = events;
events = null;
}
this.on(events, hook, priority, handlerFn, unwrap, that);
};
});
/**
* A modeling behavior that ensures we set the correct root element
* as we undo and redo commands.
*
* @param {Canvas} canvas
* @param {didi.Injector} injector
*/
function RootElementsBehavior(canvas, injector) {
injector.invoke(CommandInterceptor, this);
this.executed(function(event) {
var context = event.context;
if (context.rootElement) {
canvas.setRootElement(context.rootElement);
} else {
context.rootElement = canvas.getRootElement();
}
});
this.revert(function(event) {
var context = event.context;
if (context.rootElement) {
canvas.setRootElement(context.rootElement);
}
});
}
e(RootElementsBehavior, CommandInterceptor);
RootElementsBehavior.$inject = [ 'canvas', 'injector' ];
var RootElementsModule = {
__init__: [ 'rootElementsBehavior' ],
rootElementsBehavior: [ 'type', RootElementsBehavior ]
};
var css_escape = {exports: {}};
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
(function (module, exports) {
(function(root, factory) {
// https://github.com/umdjs/umd/blob/master/returnExports.js
{
// For Node.js.
module.exports = factory(root);
}
}(typeof commonjsGlobal != 'undefined' ? commonjsGlobal : commonjsGlobal, function(root) {
if (root.CSS && root.CSS.escape) {
return root.CSS.escape;
}
// https://drafts.csswg.org/cssom/#serialize-an-identifier
var cssEscape = function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: there’s no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
if (!root.CSS) {
root.CSS = {};
}
root.CSS.escape = cssEscape;
return cssEscape;
}));
} (css_escape));
var cssEscape = css_escape.exports;
var HTML_ESCAPE_MAP = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#39;'
};
function escapeHTML(str) {
str = '' + str;
return str && str.replace(/[&<>"']/g, function(match) {
return HTML_ESCAPE_MAP[match];
});
}
var planeSuffix = '_plane';
/**
* Get primary shape ID for a plane.
*
* @param {djs.model.Base|ModdleElement} element
*
* @returns {String}
*/
function getShapeIdFromPlane(element) {
var id = element.id;
return removePlaneSuffix(id);
}
/**
* Get plane ID for a primary shape.
*
* @param {djs.model.Base|ModdleElement} element
*
* @returns {String}
*/
function getPlaneIdFromShape(element) {
var id = element.id;
if (is$1(element, 'bpmn:SubProcess')) {
return addPlaneSuffix(id);
}
return id;
}
/**
* Get plane ID for primary shape ID.
*
* @param {String} id
*
* @returns {String}
*/
function toPlaneId(id) {
return addPlaneSuffix(id);
}
/**
* Check wether element is plane.
*
* @param {djs.model.Base|ModdleElement} element
*
* @returns {Boolean}
*/
function isPlane(element) {
var di = getDi(element);
return is$1(di, 'bpmndi:BPMNPlane');
}
function addPlaneSuffix(id) {
return id + planeSuffix;
}
function removePlaneSuffix(id) {
return id.replace(new RegExp(planeSuffix + '$'), '');
}
var OPEN_CLASS = 'bjs-breadcrumbs-shown';
/**
* Adds overlays that allow switching planes on collapsed subprocesses.
*
* @param {eventBus} eventBus
* @param {elementRegistry} elementRegistry
* @param {overlays} overlays
* @param {canvas} canvas
*/
function DrilldownBreadcrumbs(eventBus, elementRegistry, overlays, canvas) {
var breadcrumbs = domify('<ul class="bjs-breadcrumbs"></ul>');
var container = canvas.getContainer();
var containerClasses = classes$1(container);
container.appendChild(breadcrumbs);
var boParents = [];
// update breadcrumbs if name or ID of the primary shape changes
eventBus.on('element.changed', function(e) {
var shape = e.element,
bo = getBusinessObject(shape);
var isPresent = find(boParents, function(el) {
return el === bo;
});
if (!isPresent) {
return;
}
updateBreadcrumbs();
});
/**
* Updates the displayed breadcrumbs. If no element is provided, only the
* labels are updated.
*
* @param {djs.model.Base} [element]
*/
function updateBreadcrumbs(element) {
if (element) {
boParents = getBoParentChain(element);
}
var path = boParents.map(function(parent) {
var title = escapeHTML(parent.name || parent.id);
var link = domify('<li><span class="bjs-crumb"><a title="' + title + '">' + title + '</a></span></li>');
var parentPlane = canvas.findRoot(getPlaneIdFromShape(parent)) || canvas.findRoot(parent.id);
// when the root is a collaboration, the process does not have a corresponding
// element in the elementRegisty. Instead, we search for the corresponding participant
if (!parentPlane && is$1(parent, 'bpmn:Process')) {
var participant = elementRegistry.find(function(element) {
var bo = getBusinessObject(element);
return bo && bo.processRef && bo.processRef === parent;
});
parentPlane = canvas.findRoot(participant.id);
}
link.addEventListener('click', function() {
canvas.setRootElement(parentPlane);
});
return link;
});
breadcrumbs.innerHTML = '';
// show breadcrumbs and expose state to .djs-container
var visible = path.length > 1;
containerClasses.toggle(OPEN_CLASS, visible);
path.forEach(function(el) {
breadcrumbs.appendChild(el);
});
}
eventBus.on('root.set', function(event) {
updateBreadcrumbs(event.element);
});
}
DrilldownBreadcrumbs.$inject = [ 'eventBus', 'elementRegistry', 'overlays', 'canvas' ];
// helpers //////////
/**
* Returns the parents for the element using the business object chain,
* starting with the root element.
*
* @param {djs.model.Shape} child
*
* @returns {Array<djs.model.Shape>} parents
*/
function getBoParentChain(child) {
var bo = getBusinessObject(child);
var parents = [];
for (var element = bo; element; element = element.$parent) {
if (is$1(element, 'bpmn:SubProcess') || is$1(element, 'bpmn:Process')) {
parents.push(element);
}
}
return parents.reverse();
}
/**
* Move collapsed subprocesses into view when drilling down.
*
* Zoom and scroll are saved in a session.
*
* @param {eventBus} eventBus
* @param {canvas} canvas
*/
function DrilldownCentering(eventBus, canvas) {
var currentRoot = null;
var positionMap = new Map();
eventBus.on('root.set', function(event) {
var newRoot = event.element;
var currentViewbox = canvas.viewbox();
var storedViewbox = positionMap.get(newRoot);
positionMap.set(currentRoot, {
x: currentViewbox.x,
y: currentViewbox.y,
zoom: currentViewbox.scale
});
currentRoot = newRoot;
// current root was replaced with a collaboration, we don't update the viewbox
if (is$1(newRoot, 'bpmn:Collaboration') && !storedViewbox) {
return;
}
storedViewbox = storedViewbox || { x: 0, y: 0, zoom: 1 };
var dx = (currentViewbox.x - storedViewbox.x) * currentViewbox.scale,
dy = (currentViewbox.y - storedViewbox.y) * currentViewbox.scale;
if (dx !== 0 || dy !== 0) {
canvas.scroll({
dx: dx,
dy: dy
});
}
if (storedViewbox.zoom !== currentViewbox.scale) {
canvas.zoom(storedViewbox.zoom, { x: 0, y: 0 });
}
});
eventBus.on('diagram.clear', function() {
positionMap.clear();
currentRoot = null;
});
}
DrilldownCentering.$inject = [ 'eventBus', 'canvas' ];
/**
* ES5 Map implementation. Works.
*/
function Map() {
this._entries = [];
this.set = function(key, value) {
var found = false;
for (var k in this._entries) {
if (this._entries[k][0] === key) {
this._entries[k][1] = value;
found = true;
break;
}
}
if (!found) {
this._entries.push([ key, value ]);
}
};
this.get = function(key) {
for (var k in this._entries) {
if (this._entries[k][0] === key) {
return this._entries[k][1];
}
}
return null;
};
this.clear = function() {
this._entries.length = 0;
};
this.remove = function(key) {
var idx = -1;
for (var k in this._entries) {
if (this._entries[k][0] === key) {
idx = k;
break;
}
}
if (idx !== -1) {
this._entries.splice(idx, 1);
}
};
}
var DEFAULT_POSITION$1 = {
x: 180,
y: 160
};
/**
* Hook into `import.render.start` and create new planes for diagrams with
* collapsed subprocesses and all dis on the same plane.
*
* @param {eventBus} eventBus
* @param {moddle} moddle
*/
function SubprocessCompatibility(eventBus, moddle) {
this._eventBus = eventBus;
this._moddle = moddle;
var self = this;
eventBus.on('import.render.start', 1500, function(e, context) {
self.handleImport(context.definitions);
});
}
SubprocessCompatibility.prototype.handleImport = function(definitions) {
if (!definitions.diagrams) {
return;
}
var self = this;
this._definitions = definitions;
this._processToDiagramMap = {};
definitions.diagrams.forEach(function(diagram) {
if (!diagram.plane || !diagram.plane.bpmnElement) {
return;
}
self._processToDiagramMap[diagram.plane.bpmnElement.id] = diagram;
});
var newDiagrams = [];
definitions.diagrams.forEach(function(diagram) {
var createdDiagrams = self.createNewDiagrams(diagram.plane);
Array.prototype.push.apply(newDiagrams, createdDiagrams);
});
newDiagrams.forEach(function(diagram) {
self.movePlaneElementsToOrigin(diagram.plane);
});
};
/**
* Moves all DI elements from collapsed subprocesses to a new plane.
*
* @param {Object} plane
* @return {Array} new diagrams created for the collapsed subprocesses
*/
SubprocessCompatibility.prototype.createNewDiagrams = function(plane) {
var self = this;
var collapsedElements = [];
var elementsToMove = [];
plane.get('planeElement').forEach(function(diElement) {
var bo = diElement.bpmnElement;
if (!bo) {
return;
}
var parent = bo.$parent;
if (is$1(bo, 'bpmn:SubProcess') && !diElement.isExpanded) {
collapsedElements.push(bo);
}
if (shouldMoveToPlane(bo, plane)) {
// don't change the array while we iterate over it
elementsToMove.push({ diElement: diElement, parent: parent });
}
});
var newDiagrams = [];
// create new planes for all collapsed subprocesses, even when they are empty
collapsedElements.forEach(function(element) {
if (!self._processToDiagramMap[element.id]) {
var diagram = self.createDiagram(element);
self._processToDiagramMap[element.id] = diagram;
newDiagrams.push(diagram);
}
});
elementsToMove.forEach(function(element) {
var diElement = element.diElement;
var parent = element.parent;
// parent is expanded, get nearest collapsed parent
while (parent && collapsedElements.indexOf(parent) === -1) {
parent = parent.$parent;
}
// false positive, all parents are expanded
if (!parent) {
return;
}
var diagram = self._processToDiagramMap[parent.id];
self.moveToDiPlane(diElement, diagram.plane);
});
return newDiagrams;
};
SubprocessCompatibility.prototype.movePlaneElementsToOrigin = function(plane) {
var elements = plane.get('planeElement');
// get bounding box of all elements
var planeBounds = getPlaneBounds(plane);
var offset = {
x: planeBounds.x - DEFAULT_POSITION$1.x,
y: planeBounds.y - DEFAULT_POSITION$1.y
};
elements.forEach(function(diElement) {
if (diElement.waypoint) {
diElement.waypoint.forEach(function(waypoint) {
waypoint.x = waypoint.x - offset.x;
waypoint.y = waypoint.y - offset.y;
});
} else if (diElement.bounds) {
diElement.bounds.x = diElement.bounds.x - offset.x;
diElement.bounds.y = diElement.bounds.y - offset.y;
}
});
};
SubprocessCompatibility.prototype.moveToDiPlane = function(diElement, newPlane) {
var containingDiagram = findRootDiagram(diElement);
// remove DI from old Plane and add it to the new one
var parentPlaneElement = containingDiagram.plane.get('planeElement');
parentPlaneElement.splice(parentPlaneElement.indexOf(diElement), 1);
newPlane.get('planeElement').push(diElement);
};
SubprocessCompatibility.prototype.createDiagram = function(bo) {
var plane = this._moddle.create('bpmndi:BPMNPlane', { bpmnElement: bo });
var diagram = this._moddle.create('bpmndi:BPMNDiagram', {
plane: plane
});
plane.$parent = diagram;
plane.bpmnElement = bo;
diagram.$parent = this._definitions;
this._definitions.diagrams.push(diagram);
return diagram;
};
SubprocessCompatibility.$inject = [ 'eventBus', 'moddle' ];
// helpers //////////////////////////
function findRootDiagram(element) {
if (is$1(element, 'bpmndi:BPMNDiagram')) {
return element;
} else {
return findRootDiagram(element.$parent);
}
}
function getPlaneBounds(plane) {
var planeTrbl = {
top: Infinity,
right: -Infinity,
bottom: -Infinity,
left: Infinity
};
plane.planeElement.forEach(function(element) {
if (!element.bounds) {
return;
}
var trbl = asTRBL(element.bounds);
planeTrbl.top = Math.min(trbl.top, planeTrbl.top);
planeTrbl.left = Math.min(trbl.left, planeTrbl.left);
});
return asBounds(planeTrbl);
}
function shouldMoveToPlane(bo, plane) {
var parent = bo.$parent;
// don't move elements that are already on the plane
if (!is$1(parent, 'bpmn:SubProcess') || parent === plane.bpmnElement) {
return false;
}
// dataAssociations are children of the subprocess but rendered on process level
// cf. https://github.com/bpmn-io/bpmn-js/issues/1619
if (isAny(bo, [ 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation' ])) {
return false;
}
return true;
}
var LOW_PRIORITY$n = 250;
var ARROW_DOWN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4.81801948,3.50735931 L10.4996894,9.1896894 L10.5,4 L12,4 L12,12 L4,12 L4,10.5 L9.6896894,10.4996894 L3.75735931,4.56801948 C3.46446609,4.27512627 3.46446609,3.80025253 3.75735931,3.50735931 C4.05025253,3.21446609 4.52512627,3.21446609 4.81801948,3.50735931 Z"/></svg>';
var EMPTY_MARKER = 'bjs-drilldown-empty';
function DrilldownOverlayBehavior(
canvas, eventBus, elementRegistry, overlays
) {
CommandInterceptor.call(this, eventBus);
this._canvas = canvas;
this._eventBus = eventBus;
this._elementRegistry = elementRegistry;
this._overlays = overlays;
var self = this;
this.executed('shape.toggleCollapse', LOW_PRIORITY$n, function(context) {
var shape = context.shape;
// Add overlay to the collapsed shape
if (self.canDrillDown(shape)) {
self.addOverlay(shape);
} else {
self.removeOverlay(shape);
}
}, true);
this.reverted('shape.toggleCollapse', LOW_PRIORITY$n, function(context) {
var shape = context.shape;
// Add overlay to the collapsed shape
if (self.canDrillDown(shape)) {
self.addOverlay(shape);
} else {
self.removeOverlay(shape);
}
}, true);
this.executed([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY$n,
function(context) {
var oldParent = context.oldParent,
newParent = context.newParent || context.parent,
shape = context.shape;
// Add overlay to the collapsed shape
if (self.canDrillDown(shape)) {
self.addOverlay(shape);
}
self.updateDrilldownOverlay(oldParent);
self.updateDrilldownOverlay(newParent);
self.updateDrilldownOverlay(shape);
}, true);
this.reverted([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY$n,
function(context) {
var oldParent = context.oldParent,
newParent = context.newParent || context.parent,
shape = context.shape;
// Add overlay to the collapsed shape
if (self.canDrillDown(shape)) {
self.addOverlay(shape);
}
self.updateDrilldownOverlay(oldParent);
self.updateDrilldownOverlay(newParent);
self.updateDrilldownOverlay(shape);
}, true);
eventBus.on('import.render.complete', function() {
elementRegistry.filter(function(e) {
return self.canDrillDown(e);
}).map(function(el) {
self.addOverlay(el);
});
});
}
e(DrilldownOverlayBehavior, CommandInterceptor);
DrilldownOverlayBehavior.prototype.updateDrilldownOverlay = function(shape) {
var canvas = this._canvas;
if (!shape) {
return;
}
var root = canvas.findRoot(shape);
if (root) {
this.updateOverlayVisibility(root);
}
};
DrilldownOverlayBehavior.prototype.canDrillDown = function(element) {
var canvas = this._canvas;
return is$1(element, 'bpmn:SubProcess') && canvas.findRoot(getPlaneIdFromShape(element));
};
/**
* Updates visibility of the drilldown overlay. If the plane has no elements,
* the drilldown will be only shown when the element is selected.
*
* @param {djs.model.Shape|djs.model.Root} element collapsed shape or root element
*/
DrilldownOverlayBehavior.prototype.updateOverlayVisibility = function(element) {
var overlays = this._overlays;
var bo = element.businessObject;
var overlay = overlays.get({ element: bo.id, type: 'drilldown' })[0];
if (!overlay) {
return;
}
var hasContent = bo && bo.flowElements && bo.flowElements.length;
classes$1(overlay.html).toggle(EMPTY_MARKER, !hasContent);
};
/**
* Attaches a drilldown button to the given element. We assume that the plane has
* the same id as the element.
*
* @param {djs.model.Shape} element collapsed shape
*/
DrilldownOverlayBehavior.prototype.addOverlay = function(element) {
var canvas = this._canvas;
var overlays = this._overlays;
var existingOverlays = overlays.get({ element: element, type: 'drilldown' });
if (existingOverlays.length) {
this.removeOverlay(element);
}
var button = domify('<button class="bjs-drilldown">' + ARROW_DOWN_SVG + '</button>');
button.addEventListener('click', function() {
canvas.setRootElement(canvas.findRoot(getPlaneIdFromShape(element)));
});
overlays.add(element, 'drilldown', {
position: {
bottom: -7,
right: -8
},
html: button
});
this.updateOverlayVisibility(element);
};
DrilldownOverlayBehavior.prototype.removeOverlay = function(element) {
var overlays = this._overlays;
overlays.remove({
element: element,
type: 'drilldown'
});
};
DrilldownOverlayBehavior.$inject = [
'canvas',
'eventBus',
'elementRegistry',
'overlays'
];
var DrilldownModdule = {
__depends__: [ OverlaysModule, ChangeSupportModule, RootElementsModule ],
__init__: [ 'drilldownBreadcrumbs', 'drilldownOverlayBehavior', 'drilldownCentering', 'subprocessCompatibility' ],
drilldownBreadcrumbs: [ 'type', DrilldownBreadcrumbs ],
drilldownCentering: [ 'type', DrilldownCentering ],
drilldownOverlayBehavior: [ 'type', DrilldownOverlayBehavior ],
subprocessCompatibility: [ 'type', SubprocessCompatibility ]
};
/**
* A viewer for BPMN 2.0 diagrams.
*
* Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include
* additional features.
*
*
* ## Extending the Viewer
*
* In order to extend the viewer pass extension modules to bootstrap via the
* `additionalModules` option. An extension module is an object that exposes
* named services.
*
* The following example depicts the integration of a simple
* logging component that integrates with interaction events:
*
*
* ```javascript
*
* // logging component
* function InteractionLogger(eventBus) {
* eventBus.on('element.hover', function(event) {
* console.log()
* })
* }
*
* InteractionLogger.$inject = [ 'eventBus' ]; // minification save
*
* // extension module
* var extensionModule = {
* __init__: [ 'interactionLogger' ],
* interactionLogger: [ 'type', InteractionLogger ]
* };
*
* // extend the viewer
* var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] });
* bpmnViewer.importXML(...);
* ```
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {string|number} [options.width] the width of the viewer
* @param {string|number} [options.height] the height of the viewer
* @param {Object} [options.moddleExtensions] extension packages to provide
* @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
* @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
*/
function Viewer(options) {
BaseViewer.call(this, options);
}
e(Viewer, BaseViewer);
// modules the viewer is composed of
Viewer.prototype._modules = [
CoreModule,
translate,
SelectionModule,
OverlaysModule,
DrilldownModdule
];
// default moddle extensions the viewer is composed of
Viewer.prototype._moddleExtensions = {};
var KEYCODE_C = 67;
var KEYCODE_V = 86;
var KEYCODE_Y = 89;
var KEYCODE_Z = 90;
var KEYS_COPY = [ 'c', 'C', KEYCODE_C ];
var KEYS_PASTE = [ 'v', 'V', KEYCODE_V ];
var KEYS_REDO = [ 'y', 'Y', KEYCODE_Y ];
var KEYS_UNDO = [ 'z', 'Z', KEYCODE_Z ];
/**
* Returns true if event was triggered with any modifier
* @param {KeyboardEvent} event
*/
function hasModifier(event) {
return (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey);
}
/**
* @param {KeyboardEvent} event
*/
function isCmd(event) {
// ensure we don't react to AltGr
// (mapped to CTRL + ALT)
if (event.altKey) {
return false;
}
return event.ctrlKey || event.metaKey;
}
/**
* Checks if key pressed is one of provided keys.
*
* @param {string|Array<string>} keys
* @param {KeyboardEvent} event
*/
function isKey(keys, event) {
keys = isArray$3(keys) ? keys : [ keys ];
return keys.indexOf(event.key) !== -1 || keys.indexOf(event.keyCode) !== -1;
}
/**
* @param {KeyboardEvent} event
*/
function isShift(event) {
return event.shiftKey;
}
function isCopy(event) {
return isCmd(event) && isKey(KEYS_COPY, event);
}
function isPaste(event) {
return isCmd(event) && isKey(KEYS_PASTE, event);
}
function isUndo(event) {
return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
}
function isRedo(event) {
return isCmd(event) && (
isKey(KEYS_REDO, event) || (
isKey(KEYS_UNDO, event) && isShift(event)
)
);
}
var KEYDOWN_EVENT = 'keyboard.keydown',
KEYUP_EVENT = 'keyboard.keyup';
var HANDLE_MODIFIER_ATTRIBUTE = 'input-handle-modified-keys';
var DEFAULT_PRIORITY$3 = 1000;
/**
* A keyboard abstraction that may be activated and
* deactivated by users at will, consuming key events
* and triggering diagram actions.
*
* For keys pressed down, keyboard fires `keyboard.keydown` event.
* The event context contains one field which is `KeyboardEvent` event.
*
* The implementation fires the following key events that allow
* other components to hook into key handling:
*
* - keyboard.bind
* - keyboard.unbind
* - keyboard.init
* - keyboard.destroy
*
* All events contain one field which is node.
*
* A default binding for the keyboard may be specified via the
* `keyboard.bindTo` configuration option.
*
* @param {Config} config
* @param {EventBus} eventBus
*/
function Keyboard(config, eventBus) {
var self = this;
this._config = config || {};
this._eventBus = eventBus;
this._keydownHandler = this._keydownHandler.bind(this);
this._keyupHandler = this._keyupHandler.bind(this);
// properly clean dom registrations
eventBus.on('diagram.destroy', function() {
self._fire('destroy');
self.unbind();
});
eventBus.on('diagram.init', function() {
self._fire('init');
});
eventBus.on('attach', function() {
if (config && config.bindTo) {
self.bind(config.bindTo);
}
});
eventBus.on('detach', function() {
self.unbind();
});
}
Keyboard.$inject = [
'config.keyboard',
'eventBus'
];
Keyboard.prototype._keydownHandler = function(event) {
this._keyHandler(event, KEYDOWN_EVENT);
};
Keyboard.prototype._keyupHandler = function(event) {
this._keyHandler(event, KEYUP_EVENT);
};
Keyboard.prototype._keyHandler = function(event, type) {
var eventBusResult;
if (this._isEventIgnored(event)) {
return;
}
var context = {
keyEvent: event
};
eventBusResult = this._eventBus.fire(type || KEYDOWN_EVENT, context);
if (eventBusResult) {
event.preventDefault();
}
};
Keyboard.prototype._isEventIgnored = function(event) {
return isInput(event.target) && this._isModifiedKeyIgnored(event);
};
Keyboard.prototype._isModifiedKeyIgnored = function(event) {
if (!isCmd(event)) {
return true;
}
var allowedModifiers = this._getAllowedModifiers(event.target);
return allowedModifiers.indexOf(event.key) === -1;
};
Keyboard.prototype._getAllowedModifiers = function(element) {
var modifierContainer = closest(element, '[' + HANDLE_MODIFIER_ATTRIBUTE + ']', true);
if (!modifierContainer || (this._node && !this._node.contains(modifierContainer))) {
return [];
}
return modifierContainer.getAttribute(HANDLE_MODIFIER_ATTRIBUTE).split(',');
};
Keyboard.prototype.bind = function(node) {
// make sure that the keyboard is only bound once to the DOM
this.unbind();
this._node = node;
// bind key events
componentEvent.bind(node, 'keydown', this._keydownHandler, true);
componentEvent.bind(node, 'keyup', this._keyupHandler, true);
this._fire('bind');
};
Keyboard.prototype.getBinding = function() {
return this._node;
};
Keyboard.prototype.unbind = function() {
var node = this._node;
if (node) {
this._fire('unbind');
// unbind key events
componentEvent.unbind(node, 'keydown', this._keydownHandler, true);
componentEvent.unbind(node, 'keyup', this._keyupHandler, true);
}
this._node = null;
};
Keyboard.prototype._fire = function(event) {
this._eventBus.fire('keyboard.' + event, { node: this._node });
};
/**
* Add a listener function that is notified with `KeyboardEvent` whenever
* the keyboard is bound and the user presses a key. If no priority is
* provided, the default value of 1000 is used.
*
* @param {number} [priority]
* @param {Function} listener
* @param {string} type
*/
Keyboard.prototype.addListener = function(priority, listener, type) {
if (isFunction(priority)) {
type = listener;
listener = priority;
priority = DEFAULT_PRIORITY$3;
}
this._eventBus.on(type || KEYDOWN_EVENT, priority, listener);
};
Keyboard.prototype.removeListener = function(listener, type) {
this._eventBus.off(type || KEYDOWN_EVENT, listener);
};
Keyboard.prototype.hasModifier = hasModifier;
Keyboard.prototype.isCmd = isCmd;
Keyboard.prototype.isShift = isShift;
Keyboard.prototype.isKey = isKey;
// helpers ///////
function isInput(target) {
return target && (matchesSelector(target, 'input, textarea') || target.contentEditable === 'true');
}
var LOW_PRIORITY$m = 500;
/**
* Adds default keyboard bindings.
*
* This does not pull in any features will bind only actions that
* have previously been registered against the editorActions component.
*
* @param {EventBus} eventBus
* @param {Keyboard} keyboard
*/
function KeyboardBindings(eventBus, keyboard) {
var self = this;
eventBus.on('editorActions.init', LOW_PRIORITY$m, function(event) {
var editorActions = event.editorActions;
self.registerBindings(keyboard, editorActions);
});
}
KeyboardBindings.$inject = [
'eventBus',
'keyboard'
];
/**
* Register available keyboard bindings.
*
* @param {Keyboard} keyboard
* @param {EditorActions} editorActions
*/
KeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
/**
* Add keyboard binding if respective editor action
* is registered.
*
* @param {string} action name
* @param {Function} fn that implements the key binding
*/
function addListener(action, fn) {
if (editorActions.isRegistered(action)) {
keyboard.addListener(fn);
}
}
// undo
// (CTRL|CMD) + Z
addListener('undo', function(context) {
var event = context.keyEvent;
if (isUndo(event)) {
editorActions.trigger('undo');
return true;
}
});
// redo
// CTRL + Y
// CMD + SHIFT + Z
addListener('redo', function(context) {
var event = context.keyEvent;
if (isRedo(event)) {
editorActions.trigger('redo');
return true;
}
});
// copy
// CTRL/CMD + C
addListener('copy', function(context) {
var event = context.keyEvent;
if (isCopy(event)) {
editorActions.trigger('copy');
return true;
}
});
// paste
// CTRL/CMD + V
addListener('paste', function(context) {
var event = context.keyEvent;
if (isPaste(event)) {
editorActions.trigger('paste');
return true;
}
});
// zoom in one step
// CTRL/CMD + +
addListener('stepZoom', function(context) {
var event = context.keyEvent;
// quirk: it has to be triggered by `=` as well to work on international keyboard layout
// cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754
if (isKey([ '+', 'Add', '=' ], event) && isCmd(event)) {
editorActions.trigger('stepZoom', { value: 1 });
return true;
}
});
// zoom out one step
// CTRL + -
addListener('stepZoom', function(context) {
var event = context.keyEvent;
if (isKey([ '-', 'Subtract' ], event) && isCmd(event)) {
editorActions.trigger('stepZoom', { value: -1 });
return true;
}
});
// zoom to the default level
// CTRL + 0
addListener('zoom', function(context) {
var event = context.keyEvent;
if (isKey('0', event) && isCmd(event)) {
editorActions.trigger('zoom', { value: 1 });
return true;
}
});
// delete selected element
// DEL
addListener('removeSelection', function(context) {
var event = context.keyEvent;
if (isKey([ 'Backspace', 'Delete', 'Del' ], event)) {
editorActions.trigger('removeSelection');
return true;
}
});
};
var KeyboardModule$1 = {
__init__: [ 'keyboard', 'keyboardBindings' ],
keyboard: [ 'type', Keyboard ],
keyboardBindings: [ 'type', KeyboardBindings ]
};
var DEFAULT_CONFIG$1 = {
moveSpeed: 50,
moveSpeedAccelerated: 200
};
/**
* A feature that allows users to move the canvas using the keyboard.
*
* @param {Object} config
* @param {number} [config.moveSpeed=50]
* @param {number} [config.moveSpeedAccelerated=200]
* @param {Keyboard} keyboard
* @param {Canvas} canvas
*/
function KeyboardMove(
config,
keyboard,
canvas
) {
var self = this;
this._config = assign({}, DEFAULT_CONFIG$1, config || {});
keyboard.addListener(arrowsListener);
function arrowsListener(context) {
var event = context.keyEvent,
config = self._config;
if (!keyboard.isCmd(event)) {
return;
}
if (keyboard.isKey([
'ArrowLeft', 'Left',
'ArrowUp', 'Up',
'ArrowDown', 'Down',
'ArrowRight', 'Right'
], event)) {
var speed = (
keyboard.isShift(event) ?
config.moveSpeedAccelerated :
config.moveSpeed
);
var direction;
switch (event.key) {
case 'ArrowLeft':
case 'Left':
direction = 'left';
break;
case 'ArrowUp':
case 'Up':
direction = 'up';
break;
case 'ArrowRight':
case 'Right':
direction = 'right';
break;
case 'ArrowDown':
case 'Down':
direction = 'down';
break;
}
self.moveCanvas({
speed: speed,
direction: direction
});
return true;
}
}
this.moveCanvas = function(opts) {
var dx = 0,
dy = 0,
speed = opts.speed;
var actualSpeed = speed / Math.min(Math.sqrt(canvas.viewbox().scale), 1);
switch (opts.direction) {
case 'left': // Left
dx = actualSpeed;
break;
case 'up': // Up
dy = actualSpeed;
break;
case 'right': // Right
dx = -actualSpeed;
break;
case 'down': // Down
dy = -actualSpeed;
break;
}
canvas.scroll({
dx: dx,
dy: dy
});
};
}
KeyboardMove.$inject = [
'config.keyboardMove',
'keyboard',
'canvas'
];
var KeyboardMoveModule = {
__depends__: [
KeyboardModule$1
],
__init__: [ 'keyboardMove' ],
keyboardMove: [ 'type', KeyboardMove ]
};
var CURSOR_CLS_PATTERN = /^djs-cursor-.*$/;
function set(mode) {
var classes = classes$1(document.body);
classes.removeMatching(CURSOR_CLS_PATTERN);
if (mode) {
classes.add('djs-cursor-' + mode);
}
}
function unset() {
set(null);
}
var TRAP_PRIORITY = 5000;
/**
* Installs a click trap that prevents a ghost click following a dragging operation.
*
* @return {Function} a function to immediately remove the installed trap.
*/
function install(eventBus, eventName) {
eventName = eventName || 'element.click';
function trap() {
return false;
}
eventBus.once(eventName, TRAP_PRIORITY, trap);
return function() {
eventBus.off(eventName, trap);
};
}
function center(bounds) {
return {
x: bounds.x + (bounds.width / 2),
y: bounds.y + (bounds.height / 2)
};
}
function delta(a, b) {
return {
x: a.x - b.x,
y: a.y - b.y
};
}
var THRESHOLD$1 = 15;
/**
* Move the canvas via mouse.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function MoveCanvas(eventBus, canvas) {
var context;
// listen for move on element mouse down;
// allow others to hook into the event before us though
// (dragging / element moving will do this)
eventBus.on('element.mousedown', 500, function(e) {
return handleStart(e.originalEvent);
});
function handleMove(event) {
var start = context.start,
button = context.button,
position = toPoint(event),
delta$1 = delta(position, start);
if (!context.dragging && length(delta$1) > THRESHOLD$1) {
context.dragging = true;
if (button === 0) {
install(eventBus);
}
set('grab');
}
if (context.dragging) {
var lastPosition = context.last || context.start;
delta$1 = delta(position, lastPosition);
canvas.scroll({
dx: delta$1.x,
dy: delta$1.y
});
context.last = position;
}
// prevent select
event.preventDefault();
}
function handleEnd(event) {
componentEvent.unbind(document, 'mousemove', handleMove);
componentEvent.unbind(document, 'mouseup', handleEnd);
context = null;
unset();
}
function handleStart(event) {
// event is already handled by '.djs-draggable'
if (closest(event.target, '.djs-draggable')) {
return;
}
var button = event.button;
// reject right mouse button or modifier key
if (button >= 2 || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
context = {
button: button,
start: toPoint(event)
};
componentEvent.bind(document, 'mousemove', handleMove);
componentEvent.bind(document, 'mouseup', handleEnd);
// we've handled the event
return true;
}
this.isActive = function() {
return !!context;
};
}
MoveCanvas.$inject = [
'eventBus',
'canvas'
];
// helpers ///////
function length(point) {
return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
}
var MoveCanvasModule = {
__init__: [ 'moveCanvas' ],
moveCanvas: [ 'type', MoveCanvas ]
};
/**
* Get the logarithm of x with base 10
* @param {Integer} value
*/
function log10(x) {
return Math.log(x) / Math.log(10);
}
/**
* Get step size for given range and number of steps.
*
* @param {Object} range
* @param {number} range.min
* @param {number} range.max
*/
function getStepSize(range, steps) {
var minLinearRange = log10(range.min),
maxLinearRange = log10(range.max);
var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange);
return absoluteLinearRange / steps;
}
function cap(range, scale) {
return Math.max(range.min, Math.min(range.max, scale));
}
var sign = Math.sign || function(n) {
return n >= 0 ? 1 : -1;
};
var RANGE = { min: 0.2, max: 4 },
NUM_STEPS = 10;
var DELTA_THRESHOLD = 0.1;
var DEFAULT_SCALE = 0.75;
/**
* An implementation of zooming and scrolling within the
* {@link Canvas} via the mouse wheel.
*
* Mouse wheel zooming / scrolling may be disabled using
* the {@link toggle(enabled)} method.
*
* @param {Object} [config]
* @param {boolean} [config.enabled=true] default enabled state
* @param {number} [config.scale=.75] scroll sensivity
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function ZoomScroll(config, eventBus, canvas) {
config = config || {};
this._enabled = false;
this._canvas = canvas;
this._container = canvas._container;
this._handleWheel = bind(this._handleWheel, this);
this._totalDelta = 0;
this._scale = config.scale || DEFAULT_SCALE;
var self = this;
eventBus.on('canvas.init', function(e) {
self._init(config.enabled !== false);
});
}
ZoomScroll.$inject = [
'config.zoomScroll',
'eventBus',
'canvas'
];
ZoomScroll.prototype.scroll = function scroll(delta) {
this._canvas.scroll(delta);
};
ZoomScroll.prototype.reset = function reset() {
this._canvas.zoom('fit-viewport');
};
/**
* Zoom depending on delta.
*
* @param {number} delta
* @param {Object} position
*/
ZoomScroll.prototype.zoom = function zoom(delta, position) {
// zoom with half the step size of stepZoom
var stepSize = getStepSize(RANGE, NUM_STEPS * 2);
// add until threshold reached
this._totalDelta += delta;
if (Math.abs(this._totalDelta) > DELTA_THRESHOLD) {
this._zoom(delta, position, stepSize);
// reset
this._totalDelta = 0;
}
};
ZoomScroll.prototype._handleWheel = function handleWheel(event) {
// event is already handled by '.djs-scrollable'
if (closest(event.target, '.djs-scrollable', true)) {
return;
}
var element = this._container;
event.preventDefault();
// pinch to zoom is mapped to wheel + ctrlKey = true
// in modern browsers (!)
var isZoom = event.ctrlKey;
var isHorizontalScroll = event.shiftKey;
var factor = -1 * this._scale,
delta;
if (isZoom) {
factor *= event.deltaMode === 0 ? 0.020 : 0.32;
} else {
factor *= event.deltaMode === 0 ? 1.0 : 16.0;
}
if (isZoom) {
var elementRect = element.getBoundingClientRect();
var offset = {
x: event.clientX - elementRect.left,
y: event.clientY - elementRect.top
};
delta = (
Math.sqrt(
Math.pow(event.deltaY, 2) +
Math.pow(event.deltaX, 2)
) * sign(event.deltaY) * factor
);
// zoom in relative to diagram {x,y} coordinates
this.zoom(delta, offset);
} else {
if (isHorizontalScroll) {
delta = {
dx: factor * event.deltaY,
dy: 0
};
} else {
delta = {
dx: factor * event.deltaX,
dy: factor * event.deltaY
};
}
this.scroll(delta);
}
};
/**
* Zoom with fixed step size.
*
* @param {number} delta - Zoom delta (1 for zooming in, -1 for out).
* @param {Object} position
*/
ZoomScroll.prototype.stepZoom = function stepZoom(delta, position) {
var stepSize = getStepSize(RANGE, NUM_STEPS);
this._zoom(delta, position, stepSize);
};
/**
* Zoom in/out given a step size.
*
* @param {number} delta
* @param {Object} position
* @param {number} stepSize
*/
ZoomScroll.prototype._zoom = function(delta, position, stepSize) {
var canvas = this._canvas;
var direction = delta > 0 ? 1 : -1;
var currentLinearZoomLevel = log10(canvas.zoom());
// snap to a proximate zoom step
var newLinearZoomLevel = Math.round(currentLinearZoomLevel / stepSize) * stepSize;
// increase or decrease one zoom step in the given direction
newLinearZoomLevel += stepSize * direction;
// calculate the absolute logarithmic zoom level based on the linear zoom level
// (e.g. 2 for an absolute x2 zoom)
var newLogZoomLevel = Math.pow(10, newLinearZoomLevel);
canvas.zoom(cap(RANGE, newLogZoomLevel), position);
};
/**
* Toggle the zoom scroll ability via mouse wheel.
*
* @param {boolean} [newEnabled] new enabled state
*/
ZoomScroll.prototype.toggle = function toggle(newEnabled) {
var element = this._container;
var handleWheel = this._handleWheel;
var oldEnabled = this._enabled;
if (typeof newEnabled === 'undefined') {
newEnabled = !oldEnabled;
}
// only react on actual changes
if (oldEnabled !== newEnabled) {
// add or remove wheel listener based on
// changed enabled state
componentEvent[newEnabled ? 'bind' : 'unbind'](element, 'wheel', handleWheel, false);
}
this._enabled = newEnabled;
return newEnabled;
};
ZoomScroll.prototype._init = function(newEnabled) {
this.toggle(newEnabled);
};
var ZoomScrollModule = {
__init__: [ 'zoomScroll' ],
zoomScroll: [ 'type', ZoomScroll ]
};
/**
* A viewer that includes mouse navigation facilities
*
* @param {Object} options
*/
function NavigatedViewer(options) {
Viewer.call(this, options);
}
e(NavigatedViewer, Viewer);
NavigatedViewer.prototype._navigationModules = [
KeyboardMoveModule,
MoveCanvasModule,
ZoomScrollModule
];
NavigatedViewer.prototype._modules = [].concat(
Viewer.prototype._modules,
NavigatedViewer.prototype._navigationModules
);
var hammer = {exports: {}};
/*! Hammer.JS - v2.0.7 - 2016-04-22
* http://hammerjs.github.io/
*
* Copyright (c) 2016 Jorik Tangelder;
* Licensed under the MIT license */
(function (module) {
(function(window, document, exportName, undefined$1) {
var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
var TEST_ELEMENT = document.createElement('div');
var TYPE_FUNCTION = 'function';
var round = Math.round;
var abs = Math.abs;
var now = Date.now;
/**
* set a timeout with a given scope
* @param {Function} fn
* @param {Number} timeout
* @param {Object} context
* @returns {number}
*/
function setTimeoutContext(fn, timeout, context) {
return setTimeout(bindFn(fn, context), timeout);
}
/**
* if the argument is an array, we want to execute the fn on each entry
* if it aint an array we don't want to do a thing.
* this is used by all the methods that accept a single and array argument.
* @param {*|Array} arg
* @param {String} fn
* @param {Object} [context]
* @returns {Boolean}
*/
function invokeArrayArg(arg, fn, context) {
if (Array.isArray(arg)) {
each(arg, context[fn], context);
return true;
}
return false;
}
/**
* walk objects and arrays
* @param {Object} obj
* @param {Function} iterator
* @param {Object} context
*/
function each(obj, iterator, context) {
var i;
if (!obj) {
return;
}
if (obj.forEach) {
obj.forEach(iterator, context);
} else if (obj.length !== undefined$1) {
i = 0;
while (i < obj.length) {
iterator.call(context, obj[i], i, obj);
i++;
}
} else {
for (i in obj) {
obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
}
}
}
/**
* wrap a method with a deprecation warning and stack trace
* @param {Function} method
* @param {String} name
* @param {String} message
* @returns {Function} A new function wrapping the supplied method.
*/
function deprecate(method, name, message) {
var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
return function() {
var e = new Error('get-stack-trace');
var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
.replace(/^\s+at\s+/gm, '')
.replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
var log = window.console && (window.console.warn || window.console.log);
if (log) {
log.call(window.console, deprecationMessage, stack);
}
return method.apply(this, arguments);
};
}
/**
* extend object.
* means that properties in dest will be overwritten by the ones in src.
* @param {Object} target
* @param {...Object} objects_to_assign
* @returns {Object} target
*/
var assign;
if (typeof Object.assign !== 'function') {
assign = function assign(target) {
if (target === undefined$1 || target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var output = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source !== undefined$1 && source !== null) {
for (var nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}
}
}
}
return output;
};
} else {
assign = Object.assign;
}
/**
* extend object.
* means that properties in dest will be overwritten by the ones in src.
* @param {Object} dest
* @param {Object} src
* @param {Boolean} [merge=false]
* @returns {Object} dest
*/
var extend = deprecate(function extend(dest, src, merge) {
var keys = Object.keys(src);
var i = 0;
while (i < keys.length) {
if (!merge || (merge && dest[keys[i]] === undefined$1)) {
dest[keys[i]] = src[keys[i]];
}
i++;
}
return dest;
}, 'extend', 'Use `assign`.');
/**
* merge the values from src in the dest.
* means that properties that exist in dest will not be overwritten by src
* @param {Object} dest
* @param {Object} src
* @returns {Object} dest
*/
var merge = deprecate(function merge(dest, src) {
return extend(dest, src, true);
}, 'merge', 'Use `assign`.');
/**
* simple class inheritance
* @param {Function} child
* @param {Function} base
* @param {Object} [properties]
*/
function inherit(child, base, properties) {
var baseP = base.prototype,
childP;
childP = child.prototype = Object.create(baseP);
childP.constructor = child;
childP._super = baseP;
if (properties) {
assign(childP, properties);
}
}
/**
* simple function bind
* @param {Function} fn
* @param {Object} context
* @returns {Function}
*/
function bindFn(fn, context) {
return function boundFn() {
return fn.apply(context, arguments);
};
}
/**
* let a boolean value also be a function that must return a boolean
* this first item in args will be used as the context
* @param {Boolean|Function} val
* @param {Array} [args]
* @returns {Boolean}
*/
function boolOrFn(val, args) {
if (typeof val == TYPE_FUNCTION) {
return val.apply(args ? args[0] || undefined$1 : undefined$1, args);
}
return val;
}
/**
* use the val2 when val1 is undefined
* @param {*} val1
* @param {*} val2
* @returns {*}
*/
function ifUndefined(val1, val2) {
return (val1 === undefined$1) ? val2 : val1;
}
/**
* addEventListener with multiple events at once
* @param {EventTarget} target
* @param {String} types
* @param {Function} handler
*/
function addEventListeners(target, types, handler) {
each(splitStr(types), function(type) {
target.addEventListener(type, handler, false);
});
}
/**
* removeEventListener with multiple events at once
* @param {EventTarget} target
* @param {String} types
* @param {Function} handler
*/
function removeEventListeners(target, types, handler) {
each(splitStr(types), function(type) {
target.removeEventListener(type, handler, false);
});
}
/**
* find if a node is in the given parent
* @method hasParent
* @param {HTMLElement} node
* @param {HTMLElement} parent
* @return {Boolean} found
*/
function hasParent(node, parent) {
while (node) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
/**
* small indexOf wrapper
* @param {String} str
* @param {String} find
* @returns {Boolean} found
*/
function inStr(str, find) {
return str.indexOf(find) > -1;
}
/**
* split string on whitespace
* @param {String} str
* @returns {Array} words
*/
function splitStr(str) {
return str.trim().split(/\s+/g);
}
/**
* find if a array contains the object using indexOf or a simple polyFill
* @param {Array} src
* @param {String} find
* @param {String} [findByKey]
* @return {Boolean|Number} false when not found, or the index
*/
function inArray(src, find, findByKey) {
if (src.indexOf && !findByKey) {
return src.indexOf(find);
} else {
var i = 0;
while (i < src.length) {
if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
return i;
}
i++;
}
return -1;
}
}
/**
* convert array-like objects to real arrays
* @param {Object} obj
* @returns {Array}
*/
function toArray(obj) {
return Array.prototype.slice.call(obj, 0);
}
/**
* unique array with objects based on a key (like 'id') or just by the array's value
* @param {Array} src [{id:1},{id:2},{id:1}]
* @param {String} [key]
* @param {Boolean} [sort=False]
* @returns {Array} [{id:1},{id:2}]
*/
function uniqueArray(src, key, sort) {
var results = [];
var values = [];
var i = 0;
while (i < src.length) {
var val = key ? src[i][key] : src[i];
if (inArray(values, val) < 0) {
results.push(src[i]);
}
values[i] = val;
i++;
}
if (sort) {
if (!key) {
results = results.sort();
} else {
results = results.sort(function sortUniqueArray(a, b) {
return a[key] > b[key];
});
}
}
return results;
}
/**
* get the prefixed property
* @param {Object} obj
* @param {String} property
* @returns {String|Undefined} prefixed
*/
function prefixed(obj, property) {
var prefix, prop;
var camelProp = property[0].toUpperCase() + property.slice(1);
var i = 0;
while (i < VENDOR_PREFIXES.length) {
prefix = VENDOR_PREFIXES[i];
prop = (prefix) ? prefix + camelProp : property;
if (prop in obj) {
return prop;
}
i++;
}
return undefined$1;
}
/**
* get a unique id
* @returns {number} uniqueId
*/
var _uniqueId = 1;
function uniqueId() {
return _uniqueId++;
}
/**
* get the window object of an element
* @param {HTMLElement} element
* @returns {DocumentView|Window}
*/
function getWindowForElement(element) {
var doc = element.ownerDocument || element;
return (doc.defaultView || doc.parentWindow || window);
}
var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
var SUPPORT_TOUCH = ('ontouchstart' in window);
var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined$1;
var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
var INPUT_TYPE_TOUCH = 'touch';
var INPUT_TYPE_PEN = 'pen';
var INPUT_TYPE_MOUSE = 'mouse';
var INPUT_TYPE_KINECT = 'kinect';
var COMPUTE_INTERVAL = 25;
var INPUT_START = 1;
var INPUT_MOVE = 2;
var INPUT_END = 4;
var INPUT_CANCEL = 8;
var DIRECTION_NONE = 1;
var DIRECTION_LEFT = 2;
var DIRECTION_RIGHT = 4;
var DIRECTION_UP = 8;
var DIRECTION_DOWN = 16;
var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
var PROPS_XY = ['x', 'y'];
var PROPS_CLIENT_XY = ['clientX', 'clientY'];
/**
* create new input type manager
* @param {Manager} manager
* @param {Function} callback
* @returns {Input}
* @constructor
*/
function Input(manager, callback) {
var self = this;
this.manager = manager;
this.callback = callback;
this.element = manager.element;
this.target = manager.options.inputTarget;
// smaller wrapper around the handler, for the scope and the enabled state of the manager,
// so when disabled the input events are completely bypassed.
this.domHandler = function(ev) {
if (boolOrFn(manager.options.enable, [manager])) {
self.handler(ev);
}
};
this.init();
}
Input.prototype = {
/**
* should handle the inputEvent data and trigger the callback
* @virtual
*/
handler: function() { },
/**
* bind the events
*/
init: function() {
this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
},
/**
* unbind the events
*/
destroy: function() {
this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
}
};
/**
* create new input type manager
* called by the Manager constructor
* @param {Hammer} manager
* @returns {Input}
*/
function createInputInstance(manager) {
var Type;
var inputClass = manager.options.inputClass;
if (inputClass) {
Type = inputClass;
} else if (SUPPORT_POINTER_EVENTS) {
Type = PointerEventInput;
} else if (SUPPORT_ONLY_TOUCH) {
Type = TouchInput;
} else if (!SUPPORT_TOUCH) {
Type = MouseInput;
} else {
Type = TouchMouseInput;
}
return new (Type)(manager, inputHandler);
}
/**
* handle input events
* @param {Manager} manager
* @param {String} eventType
* @param {Object} input
*/
function inputHandler(manager, eventType, input) {
var pointersLen = input.pointers.length;
var changedPointersLen = input.changedPointers.length;
var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
input.isFirst = !!isFirst;
input.isFinal = !!isFinal;
if (isFirst) {
manager.session = {};
}
// source event is the normalized value of the domEvents
// like 'touchstart, mouseup, pointerdown'
input.eventType = eventType;
// compute scale, rotation etc
computeInputData(manager, input);
// emit secret event
manager.emit('hammer.input', input);
manager.recognize(input);
manager.session.prevInput = input;
}
/**
* extend the data with some usable properties like scale, rotate, velocity etc
* @param {Object} manager
* @param {Object} input
*/
function computeInputData(manager, input) {
var session = manager.session;
var pointers = input.pointers;
var pointersLength = pointers.length;
// store the first input to calculate the distance and direction
if (!session.firstInput) {
session.firstInput = simpleCloneInputData(input);
}
// to compute scale and rotation we need to store the multiple touches
if (pointersLength > 1 && !session.firstMultiple) {
session.firstMultiple = simpleCloneInputData(input);
} else if (pointersLength === 1) {
session.firstMultiple = false;
}
var firstInput = session.firstInput;
var firstMultiple = session.firstMultiple;
var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
var center = input.center = getCenter(pointers);
input.timeStamp = now();
input.deltaTime = input.timeStamp - firstInput.timeStamp;
input.angle = getAngle(offsetCenter, center);
input.distance = getDistance(offsetCenter, center);
computeDeltaXY(session, input);
input.offsetDirection = getDirection(input.deltaX, input.deltaY);
var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
input.overallVelocityX = overallVelocity.x;
input.overallVelocityY = overallVelocity.y;
input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
computeIntervalInputData(session, input);
// find the correct target
var target = manager.element;
if (hasParent(input.srcEvent.target, target)) {
target = input.srcEvent.target;
}
input.target = target;
}
function computeDeltaXY(session, input) {
var center = input.center;
var offset = session.offsetDelta || {};
var prevDelta = session.prevDelta || {};
var prevInput = session.prevInput || {};
if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
prevDelta = session.prevDelta = {
x: prevInput.deltaX || 0,
y: prevInput.deltaY || 0
};
offset = session.offsetDelta = {
x: center.x,
y: center.y
};
}
input.deltaX = prevDelta.x + (center.x - offset.x);
input.deltaY = prevDelta.y + (center.y - offset.y);
}
/**
* velocity is calculated every x ms
* @param {Object} session
* @param {Object} input
*/
function computeIntervalInputData(session, input) {
var last = session.lastInterval || input,
deltaTime = input.timeStamp - last.timeStamp,
velocity, velocityX, velocityY, direction;
if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined$1)) {
var deltaX = input.deltaX - last.deltaX;
var deltaY = input.deltaY - last.deltaY;
var v = getVelocity(deltaTime, deltaX, deltaY);
velocityX = v.x;
velocityY = v.y;
velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
direction = getDirection(deltaX, deltaY);
session.lastInterval = input;
} else {
// use latest velocity info if it doesn't overtake a minimum period
velocity = last.velocity;
velocityX = last.velocityX;
velocityY = last.velocityY;
direction = last.direction;
}
input.velocity = velocity;
input.velocityX = velocityX;
input.velocityY = velocityY;
input.direction = direction;
}
/**
* create a simple clone from the input used for storage of firstInput and firstMultiple
* @param {Object} input
* @returns {Object} clonedInputData
*/
function simpleCloneInputData(input) {
// make a simple copy of the pointers because we will get a reference if we don't
// we only need clientXY for the calculations
var pointers = [];
var i = 0;
while (i < input.pointers.length) {
pointers[i] = {
clientX: round(input.pointers[i].clientX),
clientY: round(input.pointers[i].clientY)
};
i++;
}
return {
timeStamp: now(),
pointers: pointers,
center: getCenter(pointers),
deltaX: input.deltaX,
deltaY: input.deltaY
};
}
/**
* get the center of all the pointers
* @param {Array} pointers
* @return {Object} center contains `x` and `y` properties
*/
function getCenter(pointers) {
var pointersLength = pointers.length;
// no need to loop when only one touch
if (pointersLength === 1) {
return {
x: round(pointers[0].clientX),
y: round(pointers[0].clientY)
};
}
var x = 0, y = 0, i = 0;
while (i < pointersLength) {
x += pointers[i].clientX;
y += pointers[i].clientY;
i++;
}
return {
x: round(x / pointersLength),
y: round(y / pointersLength)
};
}
/**
* calculate the velocity between two points. unit is in px per ms.
* @param {Number} deltaTime
* @param {Number} x
* @param {Number} y
* @return {Object} velocity `x` and `y`
*/
function getVelocity(deltaTime, x, y) {
return {
x: x / deltaTime || 0,
y: y / deltaTime || 0
};
}
/**
* get the direction between two points
* @param {Number} x
* @param {Number} y
* @return {Number} direction
*/
function getDirection(x, y) {
if (x === y) {
return DIRECTION_NONE;
}
if (abs(x) >= abs(y)) {
return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
}
return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
}
/**
* calculate the absolute distance between two points
* @param {Object} p1 {x, y}
* @param {Object} p2 {x, y}
* @param {Array} [props] containing x and y keys
* @return {Number} distance
*/
function getDistance(p1, p2, props) {
if (!props) {
props = PROPS_XY;
}
var x = p2[props[0]] - p1[props[0]],
y = p2[props[1]] - p1[props[1]];
return Math.sqrt((x * x) + (y * y));
}
/**
* calculate the angle between two coordinates
* @param {Object} p1
* @param {Object} p2
* @param {Array} [props] containing x and y keys
* @return {Number} angle
*/
function getAngle(p1, p2, props) {
if (!props) {
props = PROPS_XY;
}
var x = p2[props[0]] - p1[props[0]],
y = p2[props[1]] - p1[props[1]];
return Math.atan2(y, x) * 180 / Math.PI;
}
/**
* calculate the rotation degrees between two pointersets
* @param {Array} start array of pointers
* @param {Array} end array of pointers
* @return {Number} rotation
*/
function getRotation(start, end) {
return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
}
/**
* calculate the scale factor between two pointersets
* no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
* @param {Array} start array of pointers
* @param {Array} end array of pointers
* @return {Number} scale
*/
function getScale(start, end) {
return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
}
var MOUSE_INPUT_MAP = {
mousedown: INPUT_START,
mousemove: INPUT_MOVE,
mouseup: INPUT_END
};
var MOUSE_ELEMENT_EVENTS = 'mousedown';
var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
/**
* Mouse events input
* @constructor
* @extends Input
*/
function MouseInput() {
this.evEl = MOUSE_ELEMENT_EVENTS;
this.evWin = MOUSE_WINDOW_EVENTS;
this.pressed = false; // mousedown state
Input.apply(this, arguments);
}
inherit(MouseInput, Input, {
/**
* handle mouse events
* @param {Object} ev
*/
handler: function MEhandler(ev) {
var eventType = MOUSE_INPUT_MAP[ev.type];
// on start we want to have the left mouse button down
if (eventType & INPUT_START && ev.button === 0) {
this.pressed = true;
}
if (eventType & INPUT_MOVE && ev.which !== 1) {
eventType = INPUT_END;
}
// mouse must be down
if (!this.pressed) {
return;
}
if (eventType & INPUT_END) {
this.pressed = false;
}
this.callback(this.manager, eventType, {
pointers: [ev],
changedPointers: [ev],
pointerType: INPUT_TYPE_MOUSE,
srcEvent: ev
});
}
});
var POINTER_INPUT_MAP = {
pointerdown: INPUT_START,
pointermove: INPUT_MOVE,
pointerup: INPUT_END,
pointercancel: INPUT_CANCEL,
pointerout: INPUT_CANCEL
};
// in IE10 the pointer types is defined as an enum
var IE10_POINTER_TYPE_ENUM = {
2: INPUT_TYPE_TOUCH,
3: INPUT_TYPE_PEN,
4: INPUT_TYPE_MOUSE,
5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
};
var POINTER_ELEMENT_EVENTS = 'pointerdown';
var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
// IE10 has prefixed support, and case-sensitive
if (window.MSPointerEvent && !window.PointerEvent) {
POINTER_ELEMENT_EVENTS = 'MSPointerDown';
POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
}
/**
* Pointer events input
* @constructor
* @extends Input
*/
function PointerEventInput() {
this.evEl = POINTER_ELEMENT_EVENTS;
this.evWin = POINTER_WINDOW_EVENTS;
Input.apply(this, arguments);
this.store = (this.manager.session.pointerEvents = []);
}
inherit(PointerEventInput, Input, {
/**
* handle mouse events
* @param {Object} ev
*/
handler: function PEhandler(ev) {
var store = this.store;
var removePointer = false;
var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
var isTouch = (pointerType == INPUT_TYPE_TOUCH);
// get index of the event in the store
var storeIndex = inArray(store, ev.pointerId, 'pointerId');
// start and mouse must be down
if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
if (storeIndex < 0) {
store.push(ev);
storeIndex = store.length - 1;
}
} else if (eventType & (INPUT_END | INPUT_CANCEL)) {
removePointer = true;
}
// it not found, so the pointer hasn't been down (so it's probably a hover)
if (storeIndex < 0) {
return;
}
// update the event in the store
store[storeIndex] = ev;
this.callback(this.manager, eventType, {
pointers: store,
changedPointers: [ev],
pointerType: pointerType,
srcEvent: ev
});
if (removePointer) {
// remove from the store
store.splice(storeIndex, 1);
}
}
});
var SINGLE_TOUCH_INPUT_MAP = {
touchstart: INPUT_START,
touchmove: INPUT_MOVE,
touchend: INPUT_END,
touchcancel: INPUT_CANCEL
};
var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
/**
* Touch events input
* @constructor
* @extends Input
*/
function SingleTouchInput() {
this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
this.started = false;
Input.apply(this, arguments);
}
inherit(SingleTouchInput, Input, {
handler: function TEhandler(ev) {
var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
// should we handle the touch events?
if (type === INPUT_START) {
this.started = true;
}
if (!this.started) {
return;
}
var touches = normalizeSingleTouches.call(this, ev, type);
// when done, reset the started state
if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
this.started = false;
}
this.callback(this.manager, type, {
pointers: touches[0],
changedPointers: touches[1],
pointerType: INPUT_TYPE_TOUCH,
srcEvent: ev
});
}
});
/**
* @this {TouchInput}
* @param {Object} ev
* @param {Number} type flag
* @returns {undefined|Array} [all, changed]
*/
function normalizeSingleTouches(ev, type) {
var all = toArray(ev.touches);
var changed = toArray(ev.changedTouches);
if (type & (INPUT_END | INPUT_CANCEL)) {
all = uniqueArray(all.concat(changed), 'identifier', true);
}
return [all, changed];
}
var TOUCH_INPUT_MAP = {
touchstart: INPUT_START,
touchmove: INPUT_MOVE,
touchend: INPUT_END,
touchcancel: INPUT_CANCEL
};
var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
/**
* Multi-user touch events input
* @constructor
* @extends Input
*/
function TouchInput() {
this.evTarget = TOUCH_TARGET_EVENTS;
this.targetIds = {};
Input.apply(this, arguments);
}
inherit(TouchInput, Input, {
handler: function MTEhandler(ev) {
var type = TOUCH_INPUT_MAP[ev.type];
var touches = getTouches.call(this, ev, type);
if (!touches) {
return;
}
this.callback(this.manager, type, {
pointers: touches[0],
changedPointers: touches[1],
pointerType: INPUT_TYPE_TOUCH,
srcEvent: ev
});
}
});
/**
* @this {TouchInput}
* @param {Object} ev
* @param {Number} type flag
* @returns {undefined|Array} [all, changed]
*/
function getTouches(ev, type) {
var allTouches = toArray(ev.touches);
var targetIds = this.targetIds;
// when there is only one touch, the process can be simplified
if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
targetIds[allTouches[0].identifier] = true;
return [allTouches, allTouches];
}
var i,
targetTouches,
changedTouches = toArray(ev.changedTouches),
changedTargetTouches = [],
target = this.target;
// get target touches from touches
targetTouches = allTouches.filter(function(touch) {
return hasParent(touch.target, target);
});
// collect touches
if (type === INPUT_START) {
i = 0;
while (i < targetTouches.length) {
targetIds[targetTouches[i].identifier] = true;
i++;
}
}
// filter changed touches to only contain touches that exist in the collected target ids
i = 0;
while (i < changedTouches.length) {
if (targetIds[changedTouches[i].identifier]) {
changedTargetTouches.push(changedTouches[i]);
}
// cleanup removed touches
if (type & (INPUT_END | INPUT_CANCEL)) {
delete targetIds[changedTouches[i].identifier];
}
i++;
}
if (!changedTargetTouches.length) {
return;
}
return [
// merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
changedTargetTouches
];
}
/**
* Combined touch and mouse input
*
* Touch has a higher priority then mouse, and while touching no mouse events are allowed.
* This because touch devices also emit mouse events while doing a touch.
*
* @constructor
* @extends Input
*/
var DEDUP_TIMEOUT = 2500;
var DEDUP_DISTANCE = 25;
function TouchMouseInput() {
Input.apply(this, arguments);
var handler = bindFn(this.handler, this);
this.touch = new TouchInput(this.manager, handler);
this.mouse = new MouseInput(this.manager, handler);
this.primaryTouch = null;
this.lastTouches = [];
}
inherit(TouchMouseInput, Input, {
/**
* handle mouse and touch events
* @param {Hammer} manager
* @param {String} inputEvent
* @param {Object} inputData
*/
handler: function TMEhandler(manager, inputEvent, inputData) {
var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
return;
}
// when we're in a touch event, record touches to de-dupe synthetic mouse event
if (isTouch) {
recordTouches.call(this, inputEvent, inputData);
} else if (isMouse && isSyntheticEvent.call(this, inputData)) {
return;
}
this.callback(manager, inputEvent, inputData);
},
/**
* remove the event listeners
*/
destroy: function destroy() {
this.touch.destroy();
this.mouse.destroy();
}
});
function recordTouches(eventType, eventData) {
if (eventType & INPUT_START) {
this.primaryTouch = eventData.changedPointers[0].identifier;
setLastTouch.call(this, eventData);
} else if (eventType & (INPUT_END | INPUT_CANCEL)) {
setLastTouch.call(this, eventData);
}
}
function setLastTouch(eventData) {
var touch = eventData.changedPointers[0];
if (touch.identifier === this.primaryTouch) {
var lastTouch = {x: touch.clientX, y: touch.clientY};
this.lastTouches.push(lastTouch);
var lts = this.lastTouches;
var removeLastTouch = function() {
var i = lts.indexOf(lastTouch);
if (i > -1) {
lts.splice(i, 1);
}
};
setTimeout(removeLastTouch, DEDUP_TIMEOUT);
}
}
function isSyntheticEvent(eventData) {
var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
for (var i = 0; i < this.lastTouches.length; i++) {
var t = this.lastTouches[i];
var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
return true;
}
}
return false;
}
var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined$1;
// magical touchAction value
var TOUCH_ACTION_COMPUTE = 'compute';
var TOUCH_ACTION_AUTO = 'auto';
var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
var TOUCH_ACTION_NONE = 'none';
var TOUCH_ACTION_PAN_X = 'pan-x';
var TOUCH_ACTION_PAN_Y = 'pan-y';
var TOUCH_ACTION_MAP = getTouchActionProps();
/**
* Touch Action
* sets the touchAction property or uses the js alternative
* @param {Manager} manager
* @param {String} value
* @constructor
*/
function TouchAction(manager, value) {
this.manager = manager;
this.set(value);
}
TouchAction.prototype = {
/**
* set the touchAction value on the element or enable the polyfill
* @param {String} value
*/
set: function(value) {
// find out the touch-action by the event handlers
if (value == TOUCH_ACTION_COMPUTE) {
value = this.compute();
}
if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
}
this.actions = value.toLowerCase().trim();
},
/**
* just re-set the touchAction value
*/
update: function() {
this.set(this.manager.options.touchAction);
},
/**
* compute the value for the touchAction property based on the recognizer's settings
* @returns {String} value
*/
compute: function() {
var actions = [];
each(this.manager.recognizers, function(recognizer) {
if (boolOrFn(recognizer.options.enable, [recognizer])) {
actions = actions.concat(recognizer.getTouchAction());
}
});
return cleanTouchActions(actions.join(' '));
},
/**
* this method is called on each input cycle and provides the preventing of the browser behavior
* @param {Object} input
*/
preventDefaults: function(input) {
var srcEvent = input.srcEvent;
var direction = input.offsetDirection;
// if the touch action did prevented once this session
if (this.manager.session.prevented) {
srcEvent.preventDefault();
return;
}
var actions = this.actions;
var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
if (hasNone) {
//do not prevent defaults if this is a tap gesture
var isTapPointer = input.pointers.length === 1;
var isTapMovement = input.distance < 2;
var isTapTouchTime = input.deltaTime < 250;
if (isTapPointer && isTapMovement && isTapTouchTime) {
return;
}
}
if (hasPanX && hasPanY) {
// `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
return;
}
if (hasNone ||
(hasPanY && direction & DIRECTION_HORIZONTAL) ||
(hasPanX && direction & DIRECTION_VERTICAL)) {
return this.preventSrc(srcEvent);
}
},
/**
* call preventDefault to prevent the browser's default behavior (scrolling in most cases)
* @param {Object} srcEvent
*/
preventSrc: function(srcEvent) {
this.manager.session.prevented = true;
srcEvent.preventDefault();
}
};
/**
* when the touchActions are collected they are not a valid value, so we need to clean things up. *
* @param {String} actions
* @returns {*}
*/
function cleanTouchActions(actions) {
// none
if (inStr(actions, TOUCH_ACTION_NONE)) {
return TOUCH_ACTION_NONE;
}
var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
// if both pan-x and pan-y are set (different recognizers
// for different directions, e.g. horizontal pan but vertical swipe?)
// we need none (as otherwise with pan-x pan-y combined none of these
// recognizers will work, since the browser would handle all panning
if (hasPanX && hasPanY) {
return TOUCH_ACTION_NONE;
}
// pan-x OR pan-y
if (hasPanX || hasPanY) {
return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
}
// manipulation
if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
return TOUCH_ACTION_MANIPULATION;
}
return TOUCH_ACTION_AUTO;
}
function getTouchActionProps() {
if (!NATIVE_TOUCH_ACTION) {
return false;
}
var touchMap = {};
var cssSupports = window.CSS && window.CSS.supports;
['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
// If css.supports is not supported but there is native touch-action assume it supports
// all values. This is the case for IE 10 and 11.
touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
});
return touchMap;
}
/**
* Recognizer flow explained; *
* All recognizers have the initial state of POSSIBLE when a input session starts.
* The definition of a input session is from the first input until the last input, with all it's movement in it. *
* Example session for mouse-input: mousedown -> mousemove -> mouseup
*
* On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
* which determines with state it should be.
*
* If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
* POSSIBLE to give it another change on the next cycle.
*
* Possible
* |
* +-----+---------------+
* | |
* +-----+-----+ |
* | | |
* Failed Cancelled |
* +-------+------+
* | |
* Recognized Began
* |
* Changed
* |
* Ended/Recognized
*/
var STATE_POSSIBLE = 1;
var STATE_BEGAN = 2;
var STATE_CHANGED = 4;
var STATE_ENDED = 8;
var STATE_RECOGNIZED = STATE_ENDED;
var STATE_CANCELLED = 16;
var STATE_FAILED = 32;
/**
* Recognizer
* Every recognizer needs to extend from this class.
* @constructor
* @param {Object} options
*/
function Recognizer(options) {
this.options = assign({}, this.defaults, options || {});
this.id = uniqueId();
this.manager = null;
// default is enable true
this.options.enable = ifUndefined(this.options.enable, true);
this.state = STATE_POSSIBLE;
this.simultaneous = {};
this.requireFail = [];
}
Recognizer.prototype = {
/**
* @virtual
* @type {Object}
*/
defaults: {},
/**
* set options
* @param {Object} options
* @return {Recognizer}
*/
set: function(options) {
assign(this.options, options);
// also update the touchAction, in case something changed about the directions/enabled state
this.manager && this.manager.touchAction.update();
return this;
},
/**
* recognize simultaneous with an other recognizer.
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
recognizeWith: function(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
return this;
}
var simultaneous = this.simultaneous;
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
if (!simultaneous[otherRecognizer.id]) {
simultaneous[otherRecognizer.id] = otherRecognizer;
otherRecognizer.recognizeWith(this);
}
return this;
},
/**
* drop the simultaneous link. it doesnt remove the link on the other recognizer.
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
dropRecognizeWith: function(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
return this;
}
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
delete this.simultaneous[otherRecognizer.id];
return this;
},
/**
* recognizer can only run when an other is failing
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
requireFailure: function(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
return this;
}
var requireFail = this.requireFail;
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
if (inArray(requireFail, otherRecognizer) === -1) {
requireFail.push(otherRecognizer);
otherRecognizer.requireFailure(this);
}
return this;
},
/**
* drop the requireFailure link. it does not remove the link on the other recognizer.
* @param {Recognizer} otherRecognizer
* @returns {Recognizer} this
*/
dropRequireFailure: function(otherRecognizer) {
if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
return this;
}
otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
var index = inArray(this.requireFail, otherRecognizer);
if (index > -1) {
this.requireFail.splice(index, 1);
}
return this;
},
/**
* has require failures boolean
* @returns {boolean}
*/
hasRequireFailures: function() {
return this.requireFail.length > 0;
},
/**
* if the recognizer can recognize simultaneous with an other recognizer
* @param {Recognizer} otherRecognizer
* @returns {Boolean}
*/
canRecognizeWith: function(otherRecognizer) {
return !!this.simultaneous[otherRecognizer.id];
},
/**
* You should use `tryEmit` instead of `emit` directly to check
* that all the needed recognizers has failed before emitting.
* @param {Object} input
*/
emit: function(input) {
var self = this;
var state = this.state;
function emit(event) {
self.manager.emit(event, input);
}
// 'panstart' and 'panmove'
if (state < STATE_ENDED) {
emit(self.options.event + stateStr(state));
}
emit(self.options.event); // simple 'eventName' events
if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
emit(input.additionalEvent);
}
// panend and pancancel
if (state >= STATE_ENDED) {
emit(self.options.event + stateStr(state));
}
},
/**
* Check that all the require failure recognizers has failed,
* if true, it emits a gesture event,
* otherwise, setup the state to FAILED.
* @param {Object} input
*/
tryEmit: function(input) {
if (this.canEmit()) {
return this.emit(input);
}
// it's failing anyway
this.state = STATE_FAILED;
},
/**
* can we emit?
* @returns {boolean}
*/
canEmit: function() {
var i = 0;
while (i < this.requireFail.length) {
if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
return false;
}
i++;
}
return true;
},
/**
* update the recognizer
* @param {Object} inputData
*/
recognize: function(inputData) {
// make a new copy of the inputData
// so we can change the inputData without messing up the other recognizers
var inputDataClone = assign({}, inputData);
// is is enabled and allow recognizing?
if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
this.reset();
this.state = STATE_FAILED;
return;
}
// reset when we've reached the end
if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
this.state = STATE_POSSIBLE;
}
this.state = this.process(inputDataClone);
// the recognizer has recognized a gesture
// so trigger an event
if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
this.tryEmit(inputDataClone);
}
},
/**
* return the state of the recognizer
* the actual recognizing happens in this method
* @virtual
* @param {Object} inputData
* @returns {Const} STATE
*/
process: function(inputData) { }, // jshint ignore:line
/**
* return the preferred touch-action
* @virtual
* @returns {Array}
*/
getTouchAction: function() { },
/**
* called when the gesture isn't allowed to recognize
* like when another is being recognized or it is disabled
* @virtual
*/
reset: function() { }
};
/**
* get a usable string, used as event postfix
* @param {Const} state
* @returns {String} state
*/
function stateStr(state) {
if (state & STATE_CANCELLED) {
return 'cancel';
} else if (state & STATE_ENDED) {
return 'end';
} else if (state & STATE_CHANGED) {
return 'move';
} else if (state & STATE_BEGAN) {
return 'start';
}
return '';
}
/**
* direction cons to string
* @param {Const} direction
* @returns {String}
*/
function directionStr(direction) {
if (direction == DIRECTION_DOWN) {
return 'down';
} else if (direction == DIRECTION_UP) {
return 'up';
} else if (direction == DIRECTION_LEFT) {
return 'left';
} else if (direction == DIRECTION_RIGHT) {
return 'right';
}
return '';
}
/**
* get a recognizer by name if it is bound to a manager
* @param {Recognizer|String} otherRecognizer
* @param {Recognizer} recognizer
* @returns {Recognizer}
*/
function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
var manager = recognizer.manager;
if (manager) {
return manager.get(otherRecognizer);
}
return otherRecognizer;
}
/**
* This recognizer is just used as a base for the simple attribute recognizers.
* @constructor
* @extends Recognizer
*/
function AttrRecognizer() {
Recognizer.apply(this, arguments);
}
inherit(AttrRecognizer, Recognizer, {
/**
* @namespace
* @memberof AttrRecognizer
*/
defaults: {
/**
* @type {Number}
* @default 1
*/
pointers: 1
},
/**
* Used to check if it the recognizer receives valid input, like input.distance > 10.
* @memberof AttrRecognizer
* @param {Object} input
* @returns {Boolean} recognized
*/
attrTest: function(input) {
var optionPointers = this.options.pointers;
return optionPointers === 0 || input.pointers.length === optionPointers;
},
/**
* Process the input and return the state for the recognizer
* @memberof AttrRecognizer
* @param {Object} input
* @returns {*} State
*/
process: function(input) {
var state = this.state;
var eventType = input.eventType;
var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
var isValid = this.attrTest(input);
// on cancel input and we've recognized before, return STATE_CANCELLED
if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
return state | STATE_CANCELLED;
} else if (isRecognized || isValid) {
if (eventType & INPUT_END) {
return state | STATE_ENDED;
} else if (!(state & STATE_BEGAN)) {
return STATE_BEGAN;
}
return state | STATE_CHANGED;
}
return STATE_FAILED;
}
});
/**
* Pan
* Recognized when the pointer is down and moved in the allowed direction.
* @constructor
* @extends AttrRecognizer
*/
function PanRecognizer() {
AttrRecognizer.apply(this, arguments);
this.pX = null;
this.pY = null;
}
inherit(PanRecognizer, AttrRecognizer, {
/**
* @namespace
* @memberof PanRecognizer
*/
defaults: {
event: 'pan',
threshold: 10,
pointers: 1,
direction: DIRECTION_ALL
},
getTouchAction: function() {
var direction = this.options.direction;
var actions = [];
if (direction & DIRECTION_HORIZONTAL) {
actions.push(TOUCH_ACTION_PAN_Y);
}
if (direction & DIRECTION_VERTICAL) {
actions.push(TOUCH_ACTION_PAN_X);
}
return actions;
},
directionTest: function(input) {
var options = this.options;
var hasMoved = true;
var distance = input.distance;
var direction = input.direction;
var x = input.deltaX;
var y = input.deltaY;
// lock to axis?
if (!(direction & options.direction)) {
if (options.direction & DIRECTION_HORIZONTAL) {
direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
hasMoved = x != this.pX;
distance = Math.abs(input.deltaX);
} else {
direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
hasMoved = y != this.pY;
distance = Math.abs(input.deltaY);
}
}
input.direction = direction;
return hasMoved && distance > options.threshold && direction & options.direction;
},
attrTest: function(input) {
return AttrRecognizer.prototype.attrTest.call(this, input) &&
(this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
},
emit: function(input) {
this.pX = input.deltaX;
this.pY = input.deltaY;
var direction = directionStr(input.direction);
if (direction) {
input.additionalEvent = this.options.event + direction;
}
this._super.emit.call(this, input);
}
});
/**
* Pinch
* Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
* @constructor
* @extends AttrRecognizer
*/
function PinchRecognizer() {
AttrRecognizer.apply(this, arguments);
}
inherit(PinchRecognizer, AttrRecognizer, {
/**
* @namespace
* @memberof PinchRecognizer
*/
defaults: {
event: 'pinch',
threshold: 0,
pointers: 2
},
getTouchAction: function() {
return [TOUCH_ACTION_NONE];
},
attrTest: function(input) {
return this._super.attrTest.call(this, input) &&
(Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
},
emit: function(input) {
if (input.scale !== 1) {
var inOut = input.scale < 1 ? 'in' : 'out';
input.additionalEvent = this.options.event + inOut;
}
this._super.emit.call(this, input);
}
});
/**
* Press
* Recognized when the pointer is down for x ms without any movement.
* @constructor
* @extends Recognizer
*/
function PressRecognizer() {
Recognizer.apply(this, arguments);
this._timer = null;
this._input = null;
}
inherit(PressRecognizer, Recognizer, {
/**
* @namespace
* @memberof PressRecognizer
*/
defaults: {
event: 'press',
pointers: 1,
time: 251, // minimal time of the pointer to be pressed
threshold: 9 // a minimal movement is ok, but keep it low
},
getTouchAction: function() {
return [TOUCH_ACTION_AUTO];
},
process: function(input) {
var options = this.options;
var validPointers = input.pointers.length === options.pointers;
var validMovement = input.distance < options.threshold;
var validTime = input.deltaTime > options.time;
this._input = input;
// we only allow little movement
// and we've reached an end event, so a tap is possible
if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
this.reset();
} else if (input.eventType & INPUT_START) {
this.reset();
this._timer = setTimeoutContext(function() {
this.state = STATE_RECOGNIZED;
this.tryEmit();
}, options.time, this);
} else if (input.eventType & INPUT_END) {
return STATE_RECOGNIZED;
}
return STATE_FAILED;
},
reset: function() {
clearTimeout(this._timer);
},
emit: function(input) {
if (this.state !== STATE_RECOGNIZED) {
return;
}
if (input && (input.eventType & INPUT_END)) {
this.manager.emit(this.options.event + 'up', input);
} else {
this._input.timeStamp = now();
this.manager.emit(this.options.event, this._input);
}
}
});
/**
* Rotate
* Recognized when two or more pointer are moving in a circular motion.
* @constructor
* @extends AttrRecognizer
*/
function RotateRecognizer() {
AttrRecognizer.apply(this, arguments);
}
inherit(RotateRecognizer, AttrRecognizer, {
/**
* @namespace
* @memberof RotateRecognizer
*/
defaults: {
event: 'rotate',
threshold: 0,
pointers: 2
},
getTouchAction: function() {
return [TOUCH_ACTION_NONE];
},
attrTest: function(input) {
return this._super.attrTest.call(this, input) &&
(Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
}
});
/**
* Swipe
* Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
* @constructor
* @extends AttrRecognizer
*/
function SwipeRecognizer() {
AttrRecognizer.apply(this, arguments);
}
inherit(SwipeRecognizer, AttrRecognizer, {
/**
* @namespace
* @memberof SwipeRecognizer
*/
defaults: {
event: 'swipe',
threshold: 10,
velocity: 0.3,
direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
pointers: 1
},
getTouchAction: function() {
return PanRecognizer.prototype.getTouchAction.call(this);
},
attrTest: function(input) {
var direction = this.options.direction;
var velocity;
if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
velocity = input.overallVelocity;
} else if (direction & DIRECTION_HORIZONTAL) {
velocity = input.overallVelocityX;
} else if (direction & DIRECTION_VERTICAL) {
velocity = input.overallVelocityY;
}
return this._super.attrTest.call(this, input) &&
direction & input.offsetDirection &&
input.distance > this.options.threshold &&
input.maxPointers == this.options.pointers &&
abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
},
emit: function(input) {
var direction = directionStr(input.offsetDirection);
if (direction) {
this.manager.emit(this.options.event + direction, input);
}
this.manager.emit(this.options.event, input);
}
});
/**
* A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
* between the given interval and position. The delay option can be used to recognize multi-taps without firing
* a single tap.
*
* The eventData from the emitted event contains the property `tapCount`, which contains the amount of
* multi-taps being recognized.
* @constructor
* @extends Recognizer
*/
function TapRecognizer() {
Recognizer.apply(this, arguments);
// previous time and center,
// used for tap counting
this.pTime = false;
this.pCenter = false;
this._timer = null;
this._input = null;
this.count = 0;
}
inherit(TapRecognizer, Recognizer, {
/**
* @namespace
* @memberof PinchRecognizer
*/
defaults: {
event: 'tap',
pointers: 1,
taps: 1,
interval: 300, // max time between the multi-tap taps
time: 250, // max time of the pointer to be down (like finger on the screen)
threshold: 9, // a minimal movement is ok, but keep it low
posThreshold: 10 // a multi-tap can be a bit off the initial position
},
getTouchAction: function() {
return [TOUCH_ACTION_MANIPULATION];
},
process: function(input) {
var options = this.options;
var validPointers = input.pointers.length === options.pointers;
var validMovement = input.distance < options.threshold;
var validTouchTime = input.deltaTime < options.time;
this.reset();
if ((input.eventType & INPUT_START) && (this.count === 0)) {
return this.failTimeout();
}
// we only allow little movement
// and we've reached an end event, so a tap is possible
if (validMovement && validTouchTime && validPointers) {
if (input.eventType != INPUT_END) {
return this.failTimeout();
}
var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
this.pTime = input.timeStamp;
this.pCenter = input.center;
if (!validMultiTap || !validInterval) {
this.count = 1;
} else {
this.count += 1;
}
this._input = input;
// if tap count matches we have recognized it,
// else it has began recognizing...
var tapCount = this.count % options.taps;
if (tapCount === 0) {
// no failing requirements, immediately trigger the tap event
// or wait as long as the multitap interval to trigger
if (!this.hasRequireFailures()) {
return STATE_RECOGNIZED;
} else {
this._timer = setTimeoutContext(function() {
this.state = STATE_RECOGNIZED;
this.tryEmit();
}, options.interval, this);
return STATE_BEGAN;
}
}
}
return STATE_FAILED;
},
failTimeout: function() {
this._timer = setTimeoutContext(function() {
this.state = STATE_FAILED;
}, this.options.interval, this);
return STATE_FAILED;
},
reset: function() {
clearTimeout(this._timer);
},
emit: function() {
if (this.state == STATE_RECOGNIZED) {
this._input.tapCount = this.count;
this.manager.emit(this.options.event, this._input);
}
}
});
/**
* Simple way to create a manager with a default set of recognizers.
* @param {HTMLElement} element
* @param {Object} [options]
* @constructor
*/
function Hammer(element, options) {
options = options || {};
options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
return new Manager(element, options);
}
/**
* @const {string}
*/
Hammer.VERSION = '2.0.7';
/**
* default settings
* @namespace
*/
Hammer.defaults = {
/**
* set if DOM events are being triggered.
* But this is slower and unused by simple implementations, so disabled by default.
* @type {Boolean}
* @default false
*/
domEvents: false,
/**
* The value for the touchAction property/fallback.
* When set to `compute` it will magically set the correct value based on the added recognizers.
* @type {String}
* @default compute
*/
touchAction: TOUCH_ACTION_COMPUTE,
/**
* @type {Boolean}
* @default true
*/
enable: true,
/**
* EXPERIMENTAL FEATURE -- can be removed/changed
* Change the parent input target element.
* If Null, then it is being set the to main element.
* @type {Null|EventTarget}
* @default null
*/
inputTarget: null,
/**
* force an input class
* @type {Null|Function}
* @default null
*/
inputClass: null,
/**
* Default recognizer setup when calling `Hammer()`
* When creating a new Manager these will be skipped.
* @type {Array}
*/
preset: [
// RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
[RotateRecognizer, {enable: false}],
[PinchRecognizer, {enable: false}, ['rotate']],
[SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
[PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
[TapRecognizer],
[TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
[PressRecognizer]
],
/**
* Some CSS properties can be used to improve the working of Hammer.
* Add them to this method and they will be set when creating a new Manager.
* @namespace
*/
cssProps: {
/**
* Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
* @type {String}
* @default 'none'
*/
userSelect: 'none',
/**
* Disable the Windows Phone grippers when pressing an element.
* @type {String}
* @default 'none'
*/
touchSelect: 'none',
/**
* Disables the default callout shown when you touch and hold a touch target.
* On iOS, when you touch and hold a touch target such as a link, Safari displays
* a callout containing information about the link. This property allows you to disable that callout.
* @type {String}
* @default 'none'
*/
touchCallout: 'none',
/**
* Specifies whether zooming is enabled. Used by IE10>
* @type {String}
* @default 'none'
*/
contentZooming: 'none',
/**
* Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
* @type {String}
* @default 'none'
*/
userDrag: 'none',
/**
* Overrides the highlight color shown when the user taps a link or a JavaScript
* clickable element in iOS. This property obeys the alpha value, if specified.
* @type {String}
* @default 'rgba(0,0,0,0)'
*/
tapHighlightColor: 'rgba(0,0,0,0)'
}
};
var STOP = 1;
var FORCED_STOP = 2;
/**
* Manager
* @param {HTMLElement} element
* @param {Object} [options]
* @constructor
*/
function Manager(element, options) {
this.options = assign({}, Hammer.defaults, options || {});
this.options.inputTarget = this.options.inputTarget || element;
this.handlers = {};
this.session = {};
this.recognizers = [];
this.oldCssProps = {};
this.element = element;
this.input = createInputInstance(this);
this.touchAction = new TouchAction(this, this.options.touchAction);
toggleCssProps(this, true);
each(this.options.recognizers, function(item) {
var recognizer = this.add(new (item[0])(item[1]));
item[2] && recognizer.recognizeWith(item[2]);
item[3] && recognizer.requireFailure(item[3]);
}, this);
}
Manager.prototype = {
/**
* set options
* @param {Object} options
* @returns {Manager}
*/
set: function(options) {
assign(this.options, options);
// Options that need a little more setup
if (options.touchAction) {
this.touchAction.update();
}
if (options.inputTarget) {
// Clean up existing event listeners and reinitialize
this.input.destroy();
this.input.target = options.inputTarget;
this.input.init();
}
return this;
},
/**
* stop recognizing for this session.
* This session will be discarded, when a new [input]start event is fired.
* When forced, the recognizer cycle is stopped immediately.
* @param {Boolean} [force]
*/
stop: function(force) {
this.session.stopped = force ? FORCED_STOP : STOP;
},
/**
* run the recognizers!
* called by the inputHandler function on every movement of the pointers (touches)
* it walks through all the recognizers and tries to detect the gesture that is being made
* @param {Object} inputData
*/
recognize: function(inputData) {
var session = this.session;
if (session.stopped) {
return;
}
// run the touch-action polyfill
this.touchAction.preventDefaults(inputData);
var recognizer;
var recognizers = this.recognizers;
// this holds the recognizer that is being recognized.
// so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
// if no recognizer is detecting a thing, it is set to `null`
var curRecognizer = session.curRecognizer;
// reset when the last recognizer is recognized
// or when we're in a new session
if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
curRecognizer = session.curRecognizer = null;
}
var i = 0;
while (i < recognizers.length) {
recognizer = recognizers[i];
// find out if we are allowed try to recognize the input for this one.
// 1. allow if the session is NOT forced stopped (see the .stop() method)
// 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
// that is being recognized.
// 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
// this can be setup with the `recognizeWith()` method on the recognizer.
if (session.stopped !== FORCED_STOP && ( // 1
!curRecognizer || recognizer == curRecognizer || // 2
recognizer.canRecognizeWith(curRecognizer))) { // 3
recognizer.recognize(inputData);
} else {
recognizer.reset();
}
// if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
// current active recognizer. but only if we don't already have an active recognizer
if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
curRecognizer = session.curRecognizer = recognizer;
}
i++;
}
},
/**
* get a recognizer by its event name.
* @param {Recognizer|String} recognizer
* @returns {Recognizer|Null}
*/
get: function(recognizer) {
if (recognizer instanceof Recognizer) {
return recognizer;
}
var recognizers = this.recognizers;
for (var i = 0; i < recognizers.length; i++) {
if (recognizers[i].options.event == recognizer) {
return recognizers[i];
}
}
return null;
},
/**
* add a recognizer to the manager
* existing recognizers with the same event name will be removed
* @param {Recognizer} recognizer
* @returns {Recognizer|Manager}
*/
add: function(recognizer) {
if (invokeArrayArg(recognizer, 'add', this)) {
return this;
}
// remove existing
var existing = this.get(recognizer.options.event);
if (existing) {
this.remove(existing);
}
this.recognizers.push(recognizer);
recognizer.manager = this;
this.touchAction.update();
return recognizer;
},
/**
* remove a recognizer by name or instance
* @param {Recognizer|String} recognizer
* @returns {Manager}
*/
remove: function(recognizer) {
if (invokeArrayArg(recognizer, 'remove', this)) {
return this;
}
recognizer = this.get(recognizer);
// let's make sure this recognizer exists
if (recognizer) {
var recognizers = this.recognizers;
var index = inArray(recognizers, recognizer);
if (index !== -1) {
recognizers.splice(index, 1);
this.touchAction.update();
}
}
return this;
},
/**
* bind event
* @param {String} events
* @param {Function} handler
* @returns {EventEmitter} this
*/
on: function(events, handler) {
if (events === undefined$1) {
return;
}
if (handler === undefined$1) {
return;
}
var handlers = this.handlers;
each(splitStr(events), function(event) {
handlers[event] = handlers[event] || [];
handlers[event].push(handler);
});
return this;
},
/**
* unbind event, leave emit blank to remove all handlers
* @param {String} events
* @param {Function} [handler]
* @returns {EventEmitter} this
*/
off: function(events, handler) {
if (events === undefined$1) {
return;
}
var handlers = this.handlers;
each(splitStr(events), function(event) {
if (!handler) {
delete handlers[event];
} else {
handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
}
});
return this;
},
/**
* emit event to the listeners
* @param {String} event
* @param {Object} data
*/
emit: function(event, data) {
// we also want to trigger dom events
if (this.options.domEvents) {
triggerDomEvent(event, data);
}
// no handlers, so skip it all
var handlers = this.handlers[event] && this.handlers[event].slice();
if (!handlers || !handlers.length) {
return;
}
data.type = event;
data.preventDefault = function() {
data.srcEvent.preventDefault();
};
var i = 0;
while (i < handlers.length) {
handlers[i](data);
i++;
}
},
/**
* destroy the manager and unbinds all events
* it doesn't unbind dom events, that is the user own responsibility
*/
destroy: function() {
this.element && toggleCssProps(this, false);
this.handlers = {};
this.session = {};
this.input.destroy();
this.element = null;
}
};
/**
* add/remove the css properties as defined in manager.options.cssProps
* @param {Manager} manager
* @param {Boolean} add
*/
function toggleCssProps(manager, add) {
var element = manager.element;
if (!element.style) {
return;
}
var prop;
each(manager.options.cssProps, function(value, name) {
prop = prefixed(element.style, name);
if (add) {
manager.oldCssProps[prop] = element.style[prop];
element.style[prop] = value;
} else {
element.style[prop] = manager.oldCssProps[prop] || '';
}
});
if (!add) {
manager.oldCssProps = {};
}
}
/**
* trigger dom event
* @param {String} event
* @param {Object} data
*/
function triggerDomEvent(event, data) {
var gestureEvent = document.createEvent('Event');
gestureEvent.initEvent(event, true, true);
gestureEvent.gesture = data;
data.target.dispatchEvent(gestureEvent);
}
assign(Hammer, {
INPUT_START: INPUT_START,
INPUT_MOVE: INPUT_MOVE,
INPUT_END: INPUT_END,
INPUT_CANCEL: INPUT_CANCEL,
STATE_POSSIBLE: STATE_POSSIBLE,
STATE_BEGAN: STATE_BEGAN,
STATE_CHANGED: STATE_CHANGED,
STATE_ENDED: STATE_ENDED,
STATE_RECOGNIZED: STATE_RECOGNIZED,
STATE_CANCELLED: STATE_CANCELLED,
STATE_FAILED: STATE_FAILED,
DIRECTION_NONE: DIRECTION_NONE,
DIRECTION_LEFT: DIRECTION_LEFT,
DIRECTION_RIGHT: DIRECTION_RIGHT,
DIRECTION_UP: DIRECTION_UP,
DIRECTION_DOWN: DIRECTION_DOWN,
DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
DIRECTION_VERTICAL: DIRECTION_VERTICAL,
DIRECTION_ALL: DIRECTION_ALL,
Manager: Manager,
Input: Input,
TouchAction: TouchAction,
TouchInput: TouchInput,
MouseInput: MouseInput,
PointerEventInput: PointerEventInput,
TouchMouseInput: TouchMouseInput,
SingleTouchInput: SingleTouchInput,
Recognizer: Recognizer,
AttrRecognizer: AttrRecognizer,
Tap: TapRecognizer,
Pan: PanRecognizer,
Swipe: SwipeRecognizer,
Pinch: PinchRecognizer,
Rotate: RotateRecognizer,
Press: PressRecognizer,
on: addEventListeners,
off: removeEventListeners,
each: each,
merge: merge,
extend: extend,
assign: assign,
inherit: inherit,
bindFn: bindFn,
prefixed: prefixed
});
// this prevents errors when Hammer is loaded in the presence of an AMD
// style loader but by script tag, not by the loader.
var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
freeGlobal.Hammer = Hammer;
if (typeof undefined$1 === 'function' && undefined$1.amd) {
undefined$1(function() {
return Hammer;
});
} else if (module.exports) {
module.exports = Hammer;
} else {
window[exportName] = Hammer;
}
})(window, document, 'Hammer');
} (hammer));
var Hammer = hammer.exports;
var MIN_ZOOM = 0.2,
MAX_ZOOM = 4;
var mouseEvents = [
'mousedown',
'mouseup',
'mouseover',
'mouseout',
'click',
'dblclick'
];
function get(service, injector) {
return injector.get(service, false);
}
function stopEvent(event) {
event.preventDefault();
if (typeof event.stopPropagation === 'function') {
event.stopPropagation();
} else if (event.srcEvent && typeof event.srcEvent.stopPropagation === 'function') {
// iPhone & iPad
event.srcEvent.stopPropagation();
}
if (typeof event.stopImmediatePropagation === 'function') {
event.stopImmediatePropagation();
}
}
function createTouchRecognizer(node) {
function stopMouse(event) {
forEach$1(mouseEvents, function(e) {
componentEvent.bind(node, e, stopEvent, true);
});
}
function allowMouse(event) {
setTimeout(function() {
forEach$1(mouseEvents, function(e) {
componentEvent.unbind(node, e, stopEvent, true);
});
}, 500);
}
componentEvent.bind(node, 'touchstart', stopMouse, true);
componentEvent.bind(node, 'touchend', allowMouse, true);
componentEvent.bind(node, 'touchcancel', allowMouse, true);
// A touch event recognizer that handles
// touch events only (we know, we can already handle
// mouse events out of the box)
var recognizer = new Hammer.Manager(node, {
inputClass: Hammer.TouchInput,
recognizers: [],
domEvents: true
});
var tap = new Hammer.Tap();
var pan = new Hammer.Pan({ threshold: 10 });
var press = new Hammer.Press();
var pinch = new Hammer.Pinch();
var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 });
pinch.requireFailure(pan);
pinch.requireFailure(press);
recognizer.add([ pan, press, pinch, doubleTap, tap ]);
recognizer.reset = function(force) {
var recognizers = this.recognizers,
session = this.session;
if (session.stopped) {
return;
}
recognizer.stop(force);
setTimeout(function() {
var i, r;
for (i = 0; (r = recognizers[i]); i++) {
r.reset();
r.state = 8; // FAILED STATE
}
session.curRecognizer = null;
}, 0);
};
recognizer.on('hammer.input', function(event) {
if (event.srcEvent.defaultPrevented) {
recognizer.reset(true);
}
});
return recognizer;
}
/**
* A plugin that provides touch events for elements.
*
* @param {EventBus} eventBus
* @param {InteractionEvents} interactionEvents
*/
function TouchInteractionEvents(
injector, canvas, eventBus,
elementRegistry, interactionEvents) {
// optional integrations
var dragging = get('dragging', injector),
move = get('move', injector),
contextPad = get('contextPad', injector),
palette = get('palette', injector);
// the touch recognizer
var recognizer;
function handler(type, buttonType) {
return function(event) {
var gfx = getGfx(event.target),
element = gfx && elementRegistry.get(gfx);
// translate into an actual mouse click event
if (buttonType) {
event.srcEvent.button = buttonType;
}
return interactionEvents.fire(type, event, element);
};
}
function getGfx(target) {
var node = closest(target, 'svg, .djs-element', true);
return node;
}
function initEvents(svg) {
// touch recognizer
recognizer = createTouchRecognizer(svg);
function startGrabCanvas(event) {
var lx = 0, ly = 0;
function update(e) {
var dx = e.deltaX - lx,
dy = e.deltaY - ly;
canvas.scroll({ dx: dx, dy: dy });
lx = e.deltaX;
ly = e.deltaY;
}
function end(e) {
recognizer.off('panmove', update);
recognizer.off('panend', end);
recognizer.off('pancancel', end);
}
recognizer.on('panmove', update);
recognizer.on('panend', end);
recognizer.on('pancancel', end);
}
function startGrab(event) {
var gfx = getGfx(event.target),
element = gfx && elementRegistry.get(gfx);
// recognizer
if (move && canvas.getRootElement() !== element) {
return move.start(event, element, true);
} else {
startGrabCanvas();
}
}
function startZoom(e) {
var zoom = canvas.zoom(),
mid = e.center;
function update(e) {
var ratio = 1 - (1 - e.scale) / 1.50,
newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, ratio * zoom));
canvas.zoom(newZoom, mid);
stopEvent(e);
}
function end(e) {
recognizer.off('pinchmove', update);
recognizer.off('pinchend', end);
recognizer.off('pinchcancel', end);
recognizer.reset(true);
}
recognizer.on('pinchmove', update);
recognizer.on('pinchend', end);
recognizer.on('pinchcancel', end);
}
recognizer.on('tap', handler('element.click'));
recognizer.on('doubletap', handler('element.dblclick', 1));
recognizer.on('panstart', startGrab);
recognizer.on('press', startGrab);
recognizer.on('pinchstart', startZoom);
}
if (dragging) {
// simulate hover during dragging
eventBus.on('drag.move', function(event) {
var originalEvent = event.originalEvent;
if (!originalEvent || originalEvent instanceof MouseEvent) {
return;
}
var position = toPoint(originalEvent);
// this gets really expensive ...
var node = document.elementFromPoint(position.x, position.y),
gfx = getGfx(node),
element = gfx && elementRegistry.get(gfx);
if (element !== event.hover) {
if (event.hover) {
dragging.out(event);
}
if (element) {
dragging.hover({ element: element, gfx: gfx });
event.hover = element;
event.hoverGfx = gfx;
}
}
});
}
if (contextPad) {
eventBus.on('contextPad.create', function(event) {
var node = event.pad.html;
// touch recognizer
var padRecognizer = createTouchRecognizer(node);
padRecognizer.on('panstart', function(event) {
contextPad.trigger('dragstart', event, true);
});
padRecognizer.on('press', function(event) {
contextPad.trigger('dragstart', event, true);
});
padRecognizer.on('tap', function(event) {
contextPad.trigger('click', event);
});
});
}
if (palette) {
eventBus.on('palette.create', function(event) {
var node = event.container;
// touch recognizer
var padRecognizer = createTouchRecognizer(node);
padRecognizer.on('panstart', function(event) {
palette.trigger('dragstart', event, true);
});
padRecognizer.on('press', function(event) {
palette.trigger('dragstart', event, true);
});
padRecognizer.on('tap', function(event) {
palette.trigger('click', event);
});
});
}
eventBus.on('canvas.init', function(event) {
initEvents(event.svg);
});
}
TouchInteractionEvents.$inject = [
'injector',
'canvas',
'eventBus',
'elementRegistry',
'interactionEvents',
'touchFix'
];
function TouchFix(canvas, eventBus) {
var self = this;
eventBus.on('canvas.init', function(e) {
self.addBBoxMarker(e.svg);
});
}
TouchFix.$inject = [ 'canvas', 'eventBus' ];
/**
* Safari mobile (iOS 7) does not fire touchstart event in <SVG> element
* if there is no shape between 0,0 and viewport elements origin.
*
* So touchstart event is only fired when the <g class="viewport"> element was hit.
* Putting an element over and below the 'viewport' fixes that behavior.
*/
TouchFix.prototype.addBBoxMarker = function(svg) {
var markerStyle = {
fill: 'none',
class: 'outer-bound-marker'
};
var rect1 = create$1('rect');
attr(rect1, {
x: -10000,
y: 10000,
width: 10,
height: 10
});
attr(rect1, markerStyle);
append(svg, rect1);
var rect2 = create$1('rect');
attr(rect2, {
x: 10000,
y: 10000,
width: 10,
height: 10
});
attr(rect2, markerStyle);
append(svg, rect2);
};
var TouchModule$1 = {
__depends__: [ InteractionEventsModule$1 ],
__init__: [ 'touchInteractionEvents' ],
touchInteractionEvents: [ 'type', TouchInteractionEvents ],
touchFix: [ 'type', TouchFix ]
};
var TouchModule = {
__depends__: [
TouchModule$1
]
};
function last(arr) {
return arr && arr[arr.length - 1];
}
function sortTopOrMiddle(element) {
return element.y;
}
function sortLeftOrCenter(element) {
return element.x;
}
/**
* Sorting functions for different types of alignment
*
* @type {Object}
*
* @return {Function}
*/
var ALIGNMENT_SORTING = {
left: sortLeftOrCenter,
center: sortLeftOrCenter,
right: function(element) {
return element.x + element.width;
},
top: sortTopOrMiddle,
middle: sortTopOrMiddle,
bottom: function(element) {
return element.y + element.height;
}
};
function AlignElements$1(modeling, rules) {
this._modeling = modeling;
this._rules = rules;
}
AlignElements$1.$inject = [ 'modeling', 'rules' ];
/**
* Get the relevant "axis" and "dimension" related to the current type of alignment
*
* @param {string} type left|right|center|top|bottom|middle
*
* @return {Object} { axis, dimension }
*/
AlignElements$1.prototype._getOrientationDetails = function(type) {
var vertical = [ 'top', 'bottom', 'middle' ],
axis = 'x',
dimension = 'width';
if (vertical.indexOf(type) !== -1) {
axis = 'y';
dimension = 'height';
}
return {
axis: axis,
dimension: dimension
};
};
AlignElements$1.prototype._isType = function(type, types) {
return types.indexOf(type) !== -1;
};
/**
* Get a point on the relevant axis where elements should align to
*
* @param {string} type left|right|center|top|bottom|middle
* @param {Array} sortedElements
*
* @return {Object}
*/
AlignElements$1.prototype._alignmentPosition = function(type, sortedElements) {
var orientation = this._getOrientationDetails(type),
axis = orientation.axis,
dimension = orientation.dimension,
alignment = {},
centers = {},
hasSharedCenters = false,
centeredElements,
firstElement,
lastElement;
function getMiddleOrTop(first, last) {
return Math.round((first[axis] + last[axis] + last[dimension]) / 2);
}
if (this._isType(type, [ 'left', 'top' ])) {
alignment[type] = sortedElements[0][axis];
} else if (this._isType(type, [ 'right', 'bottom' ])) {
lastElement = last(sortedElements);
alignment[type] = lastElement[axis] + lastElement[dimension];
} else if (this._isType(type, [ 'center', 'middle' ])) {
// check if there is a center shared by more than one shape
// if not, just take the middle of the range
forEach$1(sortedElements, function(element) {
var center = element[axis] + Math.round(element[dimension] / 2);
if (centers[center]) {
centers[center].elements.push(element);
} else {
centers[center] = {
elements: [ element ],
center: center
};
}
});
centeredElements = sortBy(centers, function(center) {
if (center.elements.length > 1) {
hasSharedCenters = true;
}
return center.elements.length;
});
if (hasSharedCenters) {
alignment[type] = last(centeredElements).center;
return alignment;
}
firstElement = sortedElements[0];
sortedElements = sortBy(sortedElements, function(element) {
return element[axis] + element[dimension];
});
lastElement = last(sortedElements);
alignment[type] = getMiddleOrTop(firstElement, lastElement);
}
return alignment;
};
/**
* Executes the alignment of a selection of elements
*
* @param {Array} elements
* @param {string} type left|right|center|top|bottom|middle
*/
AlignElements$1.prototype.trigger = function(elements, type) {
var modeling = this._modeling,
allowed;
// filter out elements which cannot be aligned
var filteredElements = filter(elements, function(element) {
return !(element.waypoints || element.host || element.labelTarget);
});
// filter out elements via rules
allowed = this._rules.allowed('elements.align', { elements: filteredElements });
if (isArray$3(allowed)) {
filteredElements = allowed;
}
if (filteredElements.length < 2 || !allowed) {
return;
}
var sortFn = ALIGNMENT_SORTING[type];
var sortedElements = sortBy(filteredElements, sortFn);
var alignment = this._alignmentPosition(type, sortedElements);
modeling.alignElements(sortedElements, alignment);
};
var AlignElementsModule$1 = {
__init__: [ 'alignElements' ],
alignElements: [ 'type', AlignElements$1 ]
};
var entrySelector = '.entry';
var DEFAULT_PRIORITY$2 = 1000;
var CONTEXT_PAD_PADDING = 12;
/**
* @typedef {djs.model.Base|djs.model.Base[]} ContextPadTarget
*/
/**
* A context pad that displays element specific, contextual actions next
* to a diagram element.
*
* @param {Canvas} canvas
* @param {Object} config
* @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }]
* @param {number} [config.scale.min]
* @param {number} [config.scale.max]
* @param {EventBus} eventBus
* @param {Overlays} overlays
*/
function ContextPad(canvas, config, eventBus, overlays) {
this._canvas = canvas;
this._eventBus = eventBus;
this._overlays = overlays;
var scale = isDefined(config && config.scale) ? config.scale : {
min: 1,
max: 1.5
};
this._overlaysConfig = {
scale: scale
};
this._current = null;
this._init();
}
ContextPad.$inject = [
'canvas',
'config.contextPad',
'eventBus',
'overlays'
];
/**
* Registers events needed for interaction with other components.
*/
ContextPad.prototype._init = function() {
var self = this;
this._eventBus.on('selection.changed', function(event) {
var selection = event.newSelection;
var target = selection.length
? selection.length === 1
? selection[0]
: selection
: null;
if (target) {
self.open(target, true);
} else {
self.close();
}
});
this._eventBus.on('elements.changed', function(event) {
var elements = event.elements,
current = self._current;
if (!current) {
return;
}
var currentTarget = current.target;
var currentChanged = some(
isArray$3(currentTarget) ? currentTarget : [ currentTarget ],
function(element) {
return includes$8(elements, element);
}
);
// re-open if elements in current selection changed
if (currentChanged) {
self.open(currentTarget, true);
}
});
};
/**
* Register context pad provider.
*
* @param {number} [priority=1000]
* @param {ContextPadProvider} provider
*
* @example
* const contextPadProvider = {
* getContextPadEntries: function(element) {
* return function(entries) {
* return {
* ...entries,
* 'entry-1': {
* label: 'My Entry',
* action: function() { alert("I have been clicked!"); }
* }
* };
* }
* },
*
* getMultiElementContextPadEntries: function(elements) {
* // ...
* }
* };
*
* contextPad.registerProvider(800, contextPadProvider);
*/
ContextPad.prototype.registerProvider = function(priority, provider) {
if (!provider) {
provider = priority;
priority = DEFAULT_PRIORITY$2;
}
this._eventBus.on('contextPad.getProviders', priority, function(event) {
event.providers.push(provider);
});
};
/**
* Get context pad entries for given elements.
*
* @param {ContextPadTarget} target
*
* @return {ContextPadEntryDescriptor[]} list of entries
*/
ContextPad.prototype.getEntries = function(target) {
var providers = this._getProviders();
var provideFn = isArray$3(target)
? 'getMultiElementContextPadEntries'
: 'getContextPadEntries';
var entries = {};
// loop through all providers and their entries.
// group entries by id so that overriding an entry is possible
forEach$1(providers, function(provider) {
if (!isFunction(provider[provideFn])) {
return;
}
var entriesOrUpdater = provider[provideFn](target);
if (isFunction(entriesOrUpdater)) {
entries = entriesOrUpdater(entries);
} else {
forEach$1(entriesOrUpdater, function(entry, id) {
entries[id] = entry;
});
}
});
return entries;
};
/**
* Trigger context pad action.
*
* @param {string} action
* @param {Event} event
* @param {boolean} [autoActivate=false]
*/
ContextPad.prototype.trigger = function(action, event, autoActivate) {
var target = this._current.target,
entries = this._current.entries,
entry,
handler,
originalEvent,
button = event.delegateTarget || event.target;
if (!button) {
return event.preventDefault();
}
entry = entries[attr$1(button, 'data-action')];
handler = entry.action;
originalEvent = event.originalEvent || event;
// simple action (via callback function)
if (isFunction(handler)) {
if (action === 'click') {
return handler(originalEvent, target, autoActivate);
}
} else {
if (handler[action]) {
return handler[action](originalEvent, target, autoActivate);
}
}
// silence other actions
event.preventDefault();
};
/**
* Open the context pad for given elements.
*
* @param {ContextPadTarget} target
* @param {boolean} [force=false] - Force re-opening context pad.
*/
ContextPad.prototype.open = function(target, force) {
if (!force && this.isOpen(target)) {
return;
}
this.close();
this._updateAndOpen(target);
};
ContextPad.prototype._getProviders = function() {
var event = this._eventBus.createEvent({
type: 'contextPad.getProviders',
providers: []
});
this._eventBus.fire(event);
return event.providers;
};
/**
* @param {ContextPadTarget} target
*/
ContextPad.prototype._updateAndOpen = function(target) {
var entries = this.getEntries(target),
pad = this.getPad(target),
html = pad.html,
image;
forEach$1(entries, function(entry, id) {
var grouping = entry.group || 'default',
control = domify(entry.html || '<div class="entry" draggable="true"></div>'),
container;
attr$1(control, 'data-action', id);
container = query('[data-group=' + cssEscape(grouping) + ']', html);
if (!container) {
container = domify('<div class="group"></div>');
attr$1(container, 'data-group', grouping);
html.appendChild(container);
}
container.appendChild(control);
if (entry.className) {
addClasses$1(control, entry.className);
}
if (entry.title) {
attr$1(control, 'title', entry.title);
}
if (entry.imageUrl) {
image = domify('<img>');
attr$1(image, 'src', entry.imageUrl);
image.style.width = '100%';
image.style.height = '100%';
control.appendChild(image);
}
});
classes$1(html).add('open');
this._current = {
target: target,
entries: entries,
pad: pad
};
this._eventBus.fire('contextPad.open', { current: this._current });
};
/**
* @param {ContextPadTarget} target
*
* @return {Overlay}
*/
ContextPad.prototype.getPad = function(target) {
if (this.isOpen()) {
return this._current.pad;
}
var self = this;
var overlays = this._overlays;
var html = domify('<div class="djs-context-pad"></div>');
var position = this._getPosition(target);
var overlaysConfig = assign({
html: html
}, this._overlaysConfig, position);
delegate.bind(html, entrySelector, 'click', function(event) {
self.trigger('click', event);
});
delegate.bind(html, entrySelector, 'dragstart', function(event) {
self.trigger('dragstart', event);
});
// stop propagation of mouse events
componentEvent.bind(html, 'mousedown', function(event) {
event.stopPropagation();
});
var activeRootElement = this._canvas.getRootElement();
this._overlayId = overlays.add(activeRootElement, 'context-pad', overlaysConfig);
var pad = overlays.get(this._overlayId);
this._eventBus.fire('contextPad.create', {
target: target,
pad: pad
});
return pad;
};
/**
* Close the context pad
*/
ContextPad.prototype.close = function() {
if (!this.isOpen()) {
return;
}
this._overlays.remove(this._overlayId);
this._overlayId = null;
this._eventBus.fire('contextPad.close', { current: this._current });
this._current = null;
};
/**
* Check if pad is open.
*
* If target is provided, check if it is opened
* for the given target (single or multiple elements).
*
* @param {ContextPadTarget} [target]
* @return {boolean}
*/
ContextPad.prototype.isOpen = function(target) {
var current = this._current;
if (!current) {
return false;
}
// basic no-args is open check
if (!target) {
return true;
}
var currentTarget = current.target;
// strict handling of single vs. multi-selection
if (isArray$3(target) !== isArray$3(currentTarget)) {
return false;
}
if (isArray$3(target)) {
return (
target.length === currentTarget.length &&
every(target, function(element) {
return includes$8(currentTarget, element);
})
);
} else {
return currentTarget === target;
}
};
/**
* Get contex pad position.
*
* @param {ContextPadTarget} target
* @return {Bounds}
*/
ContextPad.prototype._getPosition = function(target) {
var elements = isArray$3(target) ? target : [ target ];
var bBox = getBBox(elements);
return {
position: {
left: bBox.x + bBox.width + CONTEXT_PAD_PADDING,
top: bBox.y - CONTEXT_PAD_PADDING / 2
}
};
};
// helpers //////////
function addClasses$1(element, classNames) {
var classes = classes$1(element);
classNames = isArray$3(classNames) ? classNames : classNames.split(/\s+/g);
classNames.forEach(function(cls) {
classes.add(cls);
});
}
/**
* @param {any[]} array
* @param {any} item
*
* @return {boolean}
*/
function includes$8(array, item) {
return array.indexOf(item) !== -1;
}
var ContextPadModule$1 = {
__depends__: [
InteractionEventsModule$1,
OverlaysModule
],
contextPad: [ 'type', ContextPad ]
};
var DATA_REF = 'data-id';
var CLOSE_EVENTS = [
'contextPad.close',
'canvas.viewbox.changing',
'commandStack.changed'
];
var DEFAULT_PRIORITY$1 = 1000;
/**
* A popup menu that can be used to display a list of actions anywhere in the canvas.
*
* @param {Object} config
* @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }]
* @param {number} [config.scale.min]
* @param {number} [config.scale.max]
* @param {EventBus} eventBus
* @param {Canvas} canvas
*
* @class
* @constructor
*/
function PopupMenu(config, eventBus, canvas) {
var scale = isDefined(config && config.scale) ? config.scale : {
min: 1,
max: 1.5
};
this._config = {
scale: scale
};
this._eventBus = eventBus;
this._canvas = canvas;
this._providers = {};
this._current = {};
}
PopupMenu.$inject = [
'config.popupMenu',
'eventBus',
'canvas'
];
/**
* Registers a popup menu provider
*
* @param {string} id
* @param {number} [priority=1000]
* @param {Object} provider
*
* @example
* const popupMenuProvider = {
* getPopupMenuEntries: function(element) {
* return {
* 'entry-1': {
* label: 'My Entry',
* action: function() { alert("I have been clicked!"); }
* }
* }
* }
* };
*
* popupMenu.registerProvider('myMenuID', popupMenuProvider);
*/
PopupMenu.prototype.registerProvider = function(id, priority, provider) {
if (!provider) {
provider = priority;
priority = DEFAULT_PRIORITY$1;
}
this._eventBus.on('popupMenu.getProviders.' + id, priority, function(event) {
event.providers.push(provider);
});
};
/**
* Determine if the popup menu has entries.
*
* @return {boolean} true if empty
*/
PopupMenu.prototype.isEmpty = function(element, providerId) {
if (!element) {
throw new Error('element parameter is missing');
}
if (!providerId) {
throw new Error('providerId parameter is missing');
}
var providers = this._getProviders(providerId);
if (!providers) {
return true;
}
var entries = this._getEntries(element, providers),
headerEntries = this._getHeaderEntries(element, providers);
var hasEntries = size(entries) > 0,
hasHeaderEntries = headerEntries && size(headerEntries) > 0;
return !hasEntries && !hasHeaderEntries;
};
/**
* Create entries and open popup menu at given position
*
* @param {Object} element
* @param {string} id provider id
* @param {Object} position
*
* @return {Object} popup menu instance
*/
PopupMenu.prototype.open = function(element, id, position) {
var providers = this._getProviders(id);
if (!element) {
throw new Error('Element is missing');
}
if (!providers || !providers.length) {
throw new Error('No registered providers for: ' + id);
}
if (!position) {
throw new Error('the position argument is missing');
}
if (this.isOpen()) {
this.close();
}
this._emit('open');
var current = this._current = {
className: id,
element: element,
position: position
};
var entries = this._getEntries(element, providers),
headerEntries = this._getHeaderEntries(element, providers);
current.entries = assign({}, entries, headerEntries);
current.container = this._createContainer(id);
if (size(headerEntries)) {
current.container.appendChild(
this._createEntries(headerEntries, 'djs-popup-header')
);
}
if (size(entries)) {
current.container.appendChild(
this._createEntries(entries, 'djs-popup-body')
);
}
var canvas = this._canvas,
parent = canvas.getContainer();
this._attachContainer(current.container, parent, position.cursor);
this._bindAutoClose();
};
/**
* Removes the popup menu and unbinds the event handlers.
*/
PopupMenu.prototype.close = function() {
if (!this.isOpen()) {
return;
}
this._emit('close');
this._unbindAutoClose();
remove$2(this._current.container);
this._current.container = null;
};
/**
* Determine if an open popup menu exist.
*
* @return {boolean} true if open
*/
PopupMenu.prototype.isOpen = function() {
return !!this._current.container;
};
/**
* Trigger an action associated with an entry.
*
* @param {Object} event
*
* @return the result of the action callback, if any
*/
PopupMenu.prototype.trigger = function(event) {
// silence other actions
event.preventDefault();
var element = event.delegateTarget || event.target,
entryId = attr$1(element, DATA_REF);
var entry = this._getEntry(entryId);
if (entry.action) {
return entry.action.call(null, event, entry);
}
};
PopupMenu.prototype._getProviders = function(id) {
var event = this._eventBus.createEvent({
type: 'popupMenu.getProviders.' + id,
providers: []
});
this._eventBus.fire(event);
return event.providers;
};
PopupMenu.prototype._getEntries = function(element, providers) {
var entries = {};
forEach$1(providers, function(provider) {
// handle legacy method
if (!provider.getPopupMenuEntries) {
forEach$1(provider.getEntries(element), function(entry) {
var id = entry.id;
if (!id) {
throw new Error('every entry must have the id property set');
}
entries[id] = omit(entry, [ 'id' ]);
});
return;
}
var entriesOrUpdater = provider.getPopupMenuEntries(element);
if (isFunction(entriesOrUpdater)) {
entries = entriesOrUpdater(entries);
} else {
forEach$1(entriesOrUpdater, function(entry, id) {
entries[id] = entry;
});
}
});
return entries;
};
PopupMenu.prototype._getHeaderEntries = function(element, providers) {
var entries = {};
forEach$1(providers, function(provider) {
// handle legacy method
if (!provider.getPopupMenuHeaderEntries) {
if (!provider.getHeaderEntries) {
return;
}
forEach$1(provider.getHeaderEntries(element), function(entry) {
var id = entry.id;
if (!id) {
throw new Error('every entry must have the id property set');
}
entries[id] = omit(entry, [ 'id' ]);
});
return;
}
var entriesOrUpdater = provider.getPopupMenuHeaderEntries(element);
if (isFunction(entriesOrUpdater)) {
entries = entriesOrUpdater(entries);
} else {
forEach$1(entriesOrUpdater, function(entry, id) {
entries[id] = entry;
});
}
});
return entries;
};
/**
* Gets an entry instance (either entry or headerEntry) by id.
*
* @param {string} entryId
*
* @return {Object} entry instance
*/
PopupMenu.prototype._getEntry = function(entryId) {
var entry = this._current.entries[entryId];
if (!entry) {
throw new Error('entry not found');
}
return entry;
};
PopupMenu.prototype._emit = function(eventName) {
this._eventBus.fire('popupMenu.' + eventName);
};
/**
* Creates the popup menu container.
*
* @return {Object} a DOM container
*/
PopupMenu.prototype._createContainer = function(id) {
var container = domify('<div class="djs-popup">'),
position = this._current.position,
className = this._current.className;
assign$1(container, {
position: 'absolute',
left: position.x + 'px',
top: position.y + 'px',
visibility: 'hidden'
});
classes$1(container).add(className);
attr$1(container, 'data-popup', id);
return container;
};
/**
* Attaches the container to the DOM.
*
* @param {Object} container
* @param {Object} parent
*/
PopupMenu.prototype._attachContainer = function(container, parent, cursor) {
var self = this;
// Event handler
delegate.bind(container, '.entry' ,'click', function(event) {
self.trigger(event);
});
this._updateScale(container);
// Attach to DOM
parent.appendChild(container);
if (cursor) {
this._assureIsInbounds(container, cursor);
}
// display after position adjustment to avoid flickering
assign$1(container, { visibility: 'visible' });
};
/**
* Updates popup style.transform with respect to the config and zoom level.
*
* @method _updateScale
*
* @param {Object} container
*/
PopupMenu.prototype._updateScale = function(container) {
var zoom = this._canvas.zoom();
var scaleConfig = this._config.scale,
minScale,
maxScale,
scale = zoom;
if (scaleConfig !== true) {
if (scaleConfig === false) {
minScale = 1;
maxScale = 1;
} else {
minScale = scaleConfig.min;
maxScale = scaleConfig.max;
}
if (isDefined(minScale) && zoom < minScale) {
scale = minScale;
}
if (isDefined(maxScale) && zoom > maxScale) {
scale = maxScale;
}
}
setTransform(container, 'scale(' + scale + ')');
};
/**
* Make sure that the menu is always fully shown
*
* @method function
*
* @param {Object} container
* @param {Position} cursor {x, y}
*/
PopupMenu.prototype._assureIsInbounds = function(container, cursor) {
var canvas = this._canvas,
clientRect = canvas._container.getBoundingClientRect();
var containerX = container.offsetLeft,
containerY = container.offsetTop,
containerWidth = container.scrollWidth,
containerHeight = container.scrollHeight,
overAxis = {},
left, top;
var cursorPosition = {
x: cursor.x - clientRect.left,
y: cursor.y - clientRect.top
};
if (containerX + containerWidth > clientRect.width) {
overAxis.x = true;
}
if (containerY + containerHeight > clientRect.height) {
overAxis.y = true;
}
if (overAxis.x && overAxis.y) {
left = cursorPosition.x - containerWidth + 'px';
top = cursorPosition.y - containerHeight + 'px';
} else if (overAxis.x) {
left = cursorPosition.x - containerWidth + 'px';
top = cursorPosition.y + 'px';
} else if (overAxis.y && cursorPosition.y < containerHeight) {
left = cursorPosition.x + 'px';
top = 10 + 'px';
} else if (overAxis.y) {
left = cursorPosition.x + 'px';
top = cursorPosition.y - containerHeight + 'px';
}
assign$1(container, { left: left, top: top }, { 'zIndex': 1000 });
};
/**
* Creates a list of entries and returns them as a DOM container.
*
* @param {Array<Object>} entries an array of entry objects
* @param {string} className the class name of the entry container
*
* @return {Object} a DOM container
*/
PopupMenu.prototype._createEntries = function(entries, className) {
var entriesContainer = domify('<div>'),
self = this;
classes$1(entriesContainer).add(className);
forEach$1(entries, function(entry, id) {
var entryContainer = self._createEntry(entry, id),
grouping = entry.group || 'default',
groupContainer = query('[data-group=' + cssEscape(grouping) + ']', entriesContainer);
if (!groupContainer) {
groupContainer = domify('<div class="group"></div>');
attr$1(groupContainer, 'data-group', grouping);
entriesContainer.appendChild(groupContainer);
}
groupContainer.appendChild(entryContainer);
});
return entriesContainer;
};
/**
* Creates a single entry and returns it as a DOM container.
*
* @param {Object} entry
*
* @return {Object} a DOM container
*/
PopupMenu.prototype._createEntry = function(entry, id) {
var entryContainer = domify('<div>'),
entryClasses = classes$1(entryContainer);
entryClasses.add('entry');
if (entry.className) {
entry.className.split(' ').forEach(function(className) {
entryClasses.add(className);
});
}
attr$1(entryContainer, DATA_REF, id);
if (entry.label) {
var label = domify('<span>');
label.textContent = entry.label;
entryContainer.appendChild(label);
}
if (entry.imageUrl) {
var image = domify('<img>');
attr$1(image, 'src', entry.imageUrl);
entryContainer.appendChild(image);
}
if (entry.active === true) {
entryClasses.add('active');
}
if (entry.disabled === true) {
entryClasses.add('disabled');
}
if (entry.title) {
entryContainer.title = entry.title;
}
return entryContainer;
};
/**
* Set up listener to close popup automatically on certain events.
*/
PopupMenu.prototype._bindAutoClose = function() {
this._eventBus.once(CLOSE_EVENTS, this.close, this);
};
/**
* Remove the auto-closing listener.
*/
PopupMenu.prototype._unbindAutoClose = function() {
this._eventBus.off(CLOSE_EVENTS, this.close, this);
};
// helpers /////////////////////////////
function setTransform(element, transform) {
element.style['transform-origin'] = 'top left';
[ '', '-ms-', '-webkit-' ].forEach(function(prefix) {
element.style[prefix + 'transform'] = transform;
});
}
var PopupMenuModule$1 = {
__init__: [ 'popupMenu' ],
popupMenu: [ 'type', PopupMenu ]
};
/**
* To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`,
* and then replace respective icons with the optimized data URIs in `./dist`.
*/
var icons$1 = {
align: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%202000%202000%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M200%20150v1700%22%2F%3E%3Crect%20x%3D%22500%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22700%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22500%22%20y%3D%221150%22%20width%3D%22700%22%20height%3D%22700%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
bottom: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M150%201650h1500%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22350%22%20width%3D%22600%22%20height%3D%221300%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22850%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
center: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M900%20150v1500%22%2F%3E%3Crect%20x%3D%22250%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22500%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
left: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M100%20150v1500%22%2F%3E%3Crect%20x%3D%22100%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22100%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
right: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M1650%20150v1500%22%2F%3E%3Crect%20x%3D%22350%22%20y%3D%22150%22%20width%3D%221300%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22850%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
top: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M150%20150h1500%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22150%22%20width%3D%22600%22%20height%3D%221300%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22150%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
middle: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22stroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linecap%3Around%22%20d%3D%22M150%20900h1500%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22250%22%20width%3D%22600%22%20height%3D%221300%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22500%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E'
};
var LOW_PRIORITY$l = 900;
/**
* A provider for align elements context pad button
*/
function AlignElementsContextPadProvider(contextPad, popupMenu, translate, canvas) {
contextPad.registerProvider(LOW_PRIORITY$l, this);
this._contextPad = contextPad;
this._popupMenu = popupMenu;
this._translate = translate;
this._canvas = canvas;
}
AlignElementsContextPadProvider.$inject = [
'contextPad',
'popupMenu',
'translate',
'canvas'
];
AlignElementsContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
var actions = {};
if (this._isAllowed(elements)) {
assign(actions, this._getEntries(elements));
}
return actions;
};
AlignElementsContextPadProvider.prototype._isAllowed = function(elements) {
return !this._popupMenu.isEmpty(elements, 'align-elements');
};
AlignElementsContextPadProvider.prototype._getEntries = function(elements) {
var self = this;
return {
'align-elements': {
group: 'align-elements',
title: self._translate('Align elements'),
imageUrl: icons$1['align'],
action: {
click: function(event, elements) {
var position = self._getMenuPosition(elements);
assign(position, {
cursor: {
x: event.x,
y: event.y
}
});
self._popupMenu.open(elements, 'align-elements', position);
}
}
}
};
};
AlignElementsContextPadProvider.prototype._getMenuPosition = function(elements) {
var Y_OFFSET = 5;
var diagramContainer = this._canvas.getContainer(),
pad = this._contextPad.getPad(elements).html;
var diagramRect = diagramContainer.getBoundingClientRect(),
padRect = pad.getBoundingClientRect();
var top = padRect.top - diagramRect.top;
var left = padRect.left - diagramRect.left;
var pos = {
x: left,
y: top + padRect.height + Y_OFFSET
};
return pos;
};
var ALIGNMENT_OPTIONS = [
'left',
'center',
'right',
'top',
'middle',
'bottom'
];
/**
* A provider for align elements popup menu.
*/
function AlignElementsMenuProvider(popupMenu, alignElements, translate, rules) {
this._alignElements = alignElements;
this._translate = translate;
this._popupMenu = popupMenu;
this._rules = rules;
popupMenu.registerProvider('align-elements', this);
}
AlignElementsMenuProvider.$inject = [
'popupMenu',
'alignElements',
'translate',
'rules'
];
AlignElementsMenuProvider.prototype.getPopupMenuEntries = function(elements) {
var entries = {};
if (this._isAllowed(elements)) {
assign(entries, this._getEntries(elements));
}
return entries;
};
AlignElementsMenuProvider.prototype._isAllowed = function(elements) {
return this._rules.allowed('elements.align', { elements: elements });
};
AlignElementsMenuProvider.prototype._getEntries = function(elements) {
var alignElements = this._alignElements,
translate = this._translate,
popupMenu = this._popupMenu;
var entries = {};
forEach$1(ALIGNMENT_OPTIONS, function(alignment) {
entries[ 'align-elements-' + alignment ] = {
group: 'align',
title: translate('Align elements ' + alignment),
className: 'bjs-align-elements-menu-entry',
imageUrl: icons$1[alignment],
action: function(event, entry) {
alignElements.trigger(elements, alignment);
popupMenu.close();
}
};
});
return entries;
};
/**
* A basic provider that may be extended to implement modeling rules.
*
* Extensions should implement the init method to actually add their custom
* modeling checks. Checks may be added via the #addRule(action, fn) method.
*
* @param {EventBus} eventBus
*/
function RuleProvider(eventBus) {
CommandInterceptor.call(this, eventBus);
this.init();
}
RuleProvider.$inject = [ 'eventBus' ];
e(RuleProvider, CommandInterceptor);
/**
* Adds a modeling rule for the given action, implemented through
* a callback function.
*
* The function will receive the modeling specific action context
* to perform its check. It must return `false` to disallow the
* action from happening or `true` to allow the action.
*
* A rule provider may pass over the evaluation to lower priority
* rules by returning return nothing (or <code>undefined</code>).
*
* @example
*
* ResizableRules.prototype.init = function() {
*
* \/**
* * Return `true`, `false` or nothing to denote
* * _allowed_, _not allowed_ and _continue evaluating_.
* *\/
* this.addRule('shape.resize', function(context) {
*
* var shape = context.shape;
*
* if (!context.newBounds) {
* // check general resizability
* if (!shape.resizable) {
* return false;
* }
*
* // not returning anything (read: undefined)
* // will continue the evaluation of other rules
* // (with lower priority)
* return;
* } else {
* // element must have minimum size of 10*10 points
* return context.newBounds.width > 10 && context.newBounds.height > 10;
* }
* });
* };
*
* @param {string|Array<string>} actions the identifier for the modeling action to check
* @param {number} [priority] the priority at which this rule is being applied
* @param {Function} fn the callback function that performs the actual check
*/
RuleProvider.prototype.addRule = function(actions, priority, fn) {
var self = this;
if (typeof actions === 'string') {
actions = [ actions ];
}
actions.forEach(function(action) {
self.canExecute(action, priority, function(context, action, event) {
return fn(context);
}, true);
});
};
/**
* Implement this method to add new rules during provider initialization.
*/
RuleProvider.prototype.init = function() {};
/**
* Rule provider for alignment of BPMN elements.
*/
function BpmnAlignElements(eventBus) {
RuleProvider.call(this, eventBus);
}
BpmnAlignElements.$inject = [ 'eventBus' ];
e(BpmnAlignElements, RuleProvider);
BpmnAlignElements.prototype.init = function() {
this.addRule('elements.align', function(context) {
var elements = context.elements;
// filter out elements which cannot be aligned
var filteredElements = filter(elements, function(element) {
return !(element.waypoints || element.host || element.labelTarget);
});
// filter out elements which are children of any of the selected elements
filteredElements = getParents$1(filteredElements);
if (filteredElements.length < 2) {
return false;
}
return filteredElements;
});
};
var AlignElementsModule = {
__depends__: [
AlignElementsModule$1,
ContextPadModule$1,
PopupMenuModule$1
],
__init__: [
'alignElementsContextPadProvider',
'alignElementsMenuProvider',
'bpmnAlignElements'
],
alignElementsContextPadProvider: [ 'type', AlignElementsContextPadProvider ],
alignElementsMenuProvider: [ 'type', AlignElementsMenuProvider ],
bpmnAlignElements: [ 'type', BpmnAlignElements ]
};
// padding to detect element placement
var PLACEMENT_DETECTION_PAD = 10;
var DEFAULT_DISTANCE = 50;
var DEFAULT_MAX_DISTANCE = 250;
/**
* Get free position starting from given position.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} element
* @param {Point} position
* @param {Function} getNextPosition
*
* @return {Point}
*/
function findFreePosition(source, element, position, getNextPosition) {
var connectedAtPosition;
while ((connectedAtPosition = getConnectedAtPosition(source, position, element))) {
position = getNextPosition(element, position, connectedAtPosition);
}
return position;
}
/**
* Returns function that returns next position.
*
* @param {Object} nextPositionDirection
* @param {Object} [nextPositionDirection.x]
* @param {Object} [nextPositionDirection.y]
*
* @returns {Function}
*/
function generateGetNextPosition(nextPositionDirection) {
return function(element, previousPosition, connectedAtPosition) {
var nextPosition = {
x: previousPosition.x,
y: previousPosition.y
};
[ 'x', 'y' ].forEach(function(axis) {
var nextPositionDirectionForAxis = nextPositionDirection[ axis ];
if (!nextPositionDirectionForAxis) {
return;
}
var dimension = axis === 'x' ? 'width' : 'height';
var margin = nextPositionDirectionForAxis.margin,
minDistance = nextPositionDirectionForAxis.minDistance;
if (margin < 0) {
nextPosition[ axis ] = Math.min(
connectedAtPosition[ axis ] + margin - element[ dimension ] / 2,
previousPosition[ axis ] - minDistance + margin
);
} else {
nextPosition[ axis ] = Math.max(
connectedAtPosition[ axis ] + connectedAtPosition[ dimension ] + margin + element[ dimension ] / 2,
previousPosition[ axis ] + minDistance + margin
);
}
});
return nextPosition;
};
}
/**
* Return target at given position, if defined.
*
* This takes connected elements from host and attachers
* into account, too.
*/
function getConnectedAtPosition(source, position, element) {
var bounds = {
x: position.x - (element.width / 2),
y: position.y - (element.height / 2),
width: element.width,
height: element.height
};
var closure = getAutoPlaceClosure(source);
return find(closure, function(target) {
if (target === element) {
return false;
}
var orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD);
return orientation === 'intersect';
});
}
/**
* Compute optimal distance between source and target based on existing connections to and from source.
* Assumes left-to-right and top-to-down modeling.
*
* @param {djs.model.Shape} source
* @param {Object} [hints]
* @param {number} [hints.defaultDistance]
* @param {string} [hints.direction]
* @param {Function} [hints.filter]
* @param {Function} [hints.getWeight]
* @param {number} [hints.maxDistance]
* @param {string} [hints.reference]
*
* @return {number}
*/
function getConnectedDistance(source, hints) {
if (!hints) {
hints = {};
}
// targets > sources by default
function getDefaultWeight(connection) {
return connection.source === source ? 1 : -1;
}
var defaultDistance = hints.defaultDistance || DEFAULT_DISTANCE,
direction = hints.direction || 'e',
filter = hints.filter,
getWeight = hints.getWeight || getDefaultWeight,
maxDistance = hints.maxDistance || DEFAULT_MAX_DISTANCE,
reference = hints.reference || 'start';
if (!filter) {
filter = noneFilter;
}
function getDistance(a, b) {
if (direction === 'n') {
if (reference === 'start') {
return asTRBL(a).top - asTRBL(b).bottom;
} else if (reference === 'center') {
return asTRBL(a).top - getMid(b).y;
} else {
return asTRBL(a).top - asTRBL(b).top;
}
} else if (direction === 'w') {
if (reference === 'start') {
return asTRBL(a).left - asTRBL(b).right;
} else if (reference === 'center') {
return asTRBL(a).left - getMid(b).x;
} else {
return asTRBL(a).left - asTRBL(b).left;
}
} else if (direction === 's') {
if (reference === 'start') {
return asTRBL(b).top - asTRBL(a).bottom;
} else if (reference === 'center') {
return getMid(b).y - asTRBL(a).bottom;
} else {
return asTRBL(b).bottom - asTRBL(a).bottom;
}
} else {
if (reference === 'start') {
return asTRBL(b).left - asTRBL(a).right;
} else if (reference === 'center') {
return getMid(b).x - asTRBL(a).right;
} else {
return asTRBL(b).right - asTRBL(a).right;
}
}
}
var sourcesDistances = source.incoming
.filter(filter)
.map(function(connection) {
var weight = getWeight(connection);
var distance = weight < 0
? getDistance(connection.source, source)
: getDistance(source, connection.source);
return {
id: connection.source.id,
distance: distance,
weight: weight
};
});
var targetsDistances = source.outgoing
.filter(filter)
.map(function(connection) {
var weight = getWeight(connection);
var distance = weight > 0
? getDistance(source, connection.target)
: getDistance(connection.target, source);
return {
id: connection.target.id,
distance: distance,
weight: weight
};
});
var distances = sourcesDistances.concat(targetsDistances).reduce(function(accumulator, currentValue) {
accumulator[ currentValue.id + '__weight_' + currentValue.weight ] = currentValue;
return accumulator;
}, {});
var distancesGrouped = reduce(distances, function(accumulator, currentValue) {
var distance = currentValue.distance,
weight = currentValue.weight;
if (distance < 0 || distance > maxDistance) {
return accumulator;
}
if (!accumulator[ String(distance) ]) {
accumulator[ String(distance) ] = 0;
}
accumulator[ String(distance) ] += 1 * weight;
if (!accumulator.distance || accumulator[ accumulator.distance ] < accumulator[ String(distance) ]) {
accumulator.distance = distance;
}
return accumulator;
}, {});
return distancesGrouped.distance || defaultDistance;
}
/**
* Returns all connected elements around the given source.
*
* This includes:
*
* - connected elements
* - host connected elements
* - attachers connected elements
*
* @param {djs.model.Shape} source
*
* @return {Array<djs.model.Shape>}
*/
function getAutoPlaceClosure(source) {
var allConnected = getConnected(source);
if (source.host) {
allConnected = allConnected.concat(getConnected(source.host));
}
if (source.attachers) {
allConnected = allConnected.concat(source.attachers.reduce(function(shapes, attacher) {
return shapes.concat(getConnected(attacher));
}, []));
}
return allConnected;
}
function getConnected(element) {
return getTargets(element).concat(getSources(element));
}
function getSources(shape) {
return shape.incoming.map(function(connection) {
return connection.source;
});
}
function getTargets(shape) {
return shape.outgoing.map(function(connection) {
return connection.target;
});
}
function noneFilter() {
return true;
}
var LOW_PRIORITY$k = 100;
/**
* A service that places elements connected to existing ones
* to an appropriate position in an _automated_ fashion.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
function AutoPlace$1(eventBus, modeling, canvas) {
eventBus.on('autoPlace', LOW_PRIORITY$k, function(context) {
var shape = context.shape,
source = context.source;
return getNewShapePosition$1(source, shape);
});
eventBus.on('autoPlace.end', function(event) {
canvas.scrollToElement(event.shape);
});
/**
* Append shape to source at appropriate position.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} shape
*
* @return {djs.model.Shape} appended shape
*/
this.append = function(source, shape, hints) {
eventBus.fire('autoPlace.start', {
source: source,
shape: shape
});
// allow others to provide the position
var position = eventBus.fire('autoPlace', {
source: source,
shape: shape
});
var newShape = modeling.appendShape(source, shape, position, source.parent, hints);
eventBus.fire('autoPlace.end', {
source: source,
shape: newShape
});
return newShape;
};
}
AutoPlace$1.$inject = [
'eventBus',
'modeling',
'canvas'
];
// helpers //////////
/**
* Find the new position for the target element to
* connect to source.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} element
* @param {Object} [hints]
* @param {Object} [hints.defaultDistance]
*
* @returns {Point}
*/
function getNewShapePosition$1(source, element, hints) {
if (!hints) {
hints = {};
}
var distance = hints.defaultDistance || DEFAULT_DISTANCE;
var sourceMid = getMid(source),
sourceTrbl = asTRBL(source);
// simply put element right next to source
return {
x: sourceTrbl.right + distance + element.width / 2,
y: sourceMid.y
};
}
/**
* Select element after auto placement.
*
* @param {EventBus} eventBus
* @param {Selection} selection
*/
function AutoPlaceSelectionBehavior(eventBus, selection) {
eventBus.on('autoPlace.end', 500, function(e) {
selection.select(e.shape);
});
}
AutoPlaceSelectionBehavior.$inject = [
'eventBus',
'selection'
];
var AutoPlaceModule$1 = {
__init__: [ 'autoPlaceSelectionBehavior' ],
autoPlace: [ 'type', AutoPlace$1 ],
autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ]
};
/**
* Return the parent of the element with any of the given types.
*
* @param {djs.model.Base} element
* @param {string|Array<string>} anyType
*
* @return {djs.model.Base}
*/
function getParent(element, anyType) {
if (typeof anyType === 'string') {
anyType = [ anyType ];
}
while ((element = element.parent)) {
if (isAny(element, anyType)) {
return element;
}
}
return null;
}
/**
* Find the new position for the target element to
* connect to source.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} element
*
* @return {Point}
*/
function getNewShapePosition(source, element) {
if (is$1(element, 'bpmn:TextAnnotation')) {
return getTextAnnotationPosition(source, element);
}
if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
return getDataElementPosition(source, element);
}
if (is$1(element, 'bpmn:FlowNode')) {
return getFlowNodePosition(source, element);
}
}
/**
* Always try to place element right of source;
* compute actual distance from previous nodes in flow.
*/
function getFlowNodePosition(source, element) {
var sourceTrbl = asTRBL(source);
var sourceMid = getMid(source);
var horizontalDistance = getConnectedDistance(source, {
filter: function(connection) {
return is$1(connection, 'bpmn:SequenceFlow');
}
});
var margin = 30,
minDistance = 80,
orientation = 'left';
if (is$1(source, 'bpmn:BoundaryEvent')) {
orientation = getOrientation(source, source.host, -25);
if (orientation.indexOf('top') !== -1) {
margin *= -1;
}
}
var position = {
x: sourceTrbl.right + horizontalDistance + element.width / 2,
y: sourceMid.y + getVerticalDistance(orientation, minDistance)
};
var nextPositionDirection = {
y: {
margin: margin,
minDistance: minDistance
}
};
return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
}
function getVerticalDistance(orientation, minDistance) {
if (orientation.indexOf('top') != -1) {
return -1 * minDistance;
} else if (orientation.indexOf('bottom') != -1) {
return minDistance;
} else {
return 0;
}
}
/**
* Always try to place text annotations top right of source.
*/
function getTextAnnotationPosition(source, element) {
var sourceTrbl = asTRBL(source);
var position = {
x: sourceTrbl.right + element.width / 2,
y: sourceTrbl.top - 50 - element.height / 2
};
if (isConnection$e(source)) {
position = getMid(source);
position.x += 100;
position.y -= 50;
}
var nextPositionDirection = {
y: {
margin: -30,
minDistance: 20
}
};
return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
}
/**
* Always put element bottom right of source.
*/
function getDataElementPosition(source, element) {
var sourceTrbl = asTRBL(source);
var position = {
x: sourceTrbl.right - 10 + element.width / 2,
y: sourceTrbl.bottom + 40 + element.width / 2
};
var nextPositionDirection = {
x: {
margin: 30,
minDistance: 30
}
};
return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
}
function isConnection$e(element) {
return !!element.waypoints;
}
/**
* BPMN auto-place behavior.
*
* @param {EventBus} eventBus
*/
function AutoPlace(eventBus) {
eventBus.on('autoPlace', function(context) {
var shape = context.shape,
source = context.source;
return getNewShapePosition(source, shape);
});
}
AutoPlace.$inject = [ 'eventBus' ];
var AutoPlaceModule = {
__depends__: [ AutoPlaceModule$1 ],
__init__: [ 'bpmnAutoPlace' ],
bpmnAutoPlace: [ 'type', AutoPlace ]
};
/**
* An auto resize component that takes care of expanding a parent element
* if child elements are created or moved close the parents edge.
*
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
* @param {Modeling} modeling
* @param {Rules} rules
*/
function AutoResize(eventBus, elementRegistry, modeling, rules) {
CommandInterceptor.call(this, eventBus);
this._elementRegistry = elementRegistry;
this._modeling = modeling;
this._rules = rules;
var self = this;
this.postExecuted([ 'shape.create' ], function(event) {
var context = event.context,
hints = context.hints || {},
shape = context.shape,
parent = context.parent || context.newParent;
if (hints.autoResize === false) {
return;
}
self._expand([ shape ], parent);
});
this.postExecuted([ 'elements.move' ], function(event) {
var context = event.context,
elements = flatten(values(context.closure.topLevel)),
hints = context.hints;
var autoResize = hints ? hints.autoResize : true;
if (autoResize === false) {
return;
}
var expandings = groupBy(elements, function(element) {
return element.parent.id;
});
forEach$1(expandings, function(elements, parentId) {
// optionally filter elements to be considered when resizing
if (isArray$3(autoResize)) {
elements = elements.filter(function(element) {
return find(autoResize, matchPattern({ id: element.id }));
});
}
self._expand(elements, parentId);
});
});
this.postExecuted([ 'shape.toggleCollapse' ], function(event) {
var context = event.context,
hints = context.hints,
shape = context.shape;
if (hints && hints.autoResize === false) {
return;
}
if (shape.collapsed) {
return;
}
self._expand(shape.children || [], shape);
});
this.postExecuted([ 'shape.resize' ], function(event) {
var context = event.context,
hints = context.hints,
shape = context.shape,
parent = shape.parent;
if (hints && hints.autoResize === false) {
return;
}
if (parent) {
self._expand([ shape ], parent);
}
});
}
AutoResize.$inject = [
'eventBus',
'elementRegistry',
'modeling',
'rules'
];
e(AutoResize, CommandInterceptor);
/**
* Calculate the new bounds of the target shape, given
* a number of elements have been moved or added into the parent.
*
* This method considers the current size, the added elements as well as
* the provided padding for the new bounds.
*
* @param {Array<djs.model.Shape>} elements
* @param {djs.model.Shape} target
*/
AutoResize.prototype._getOptimalBounds = function(elements, target) {
var offset = this.getOffset(target),
padding = this.getPadding(target);
var elementsTrbl = asTRBL(getBBox(elements)),
targetTrbl = asTRBL(target);
var newTrbl = {};
if (elementsTrbl.top - targetTrbl.top < padding.top) {
newTrbl.top = elementsTrbl.top - offset.top;
}
if (elementsTrbl.left - targetTrbl.left < padding.left) {
newTrbl.left = elementsTrbl.left - offset.left;
}
if (targetTrbl.right - elementsTrbl.right < padding.right) {
newTrbl.right = elementsTrbl.right + offset.right;
}
if (targetTrbl.bottom - elementsTrbl.bottom < padding.bottom) {
newTrbl.bottom = elementsTrbl.bottom + offset.bottom;
}
return asBounds(assign({}, targetTrbl, newTrbl));
};
/**
* Expand the target shape respecting rules, offset and padding
*
* @param {Array<djs.model.Shape>} elements
* @param {djs.model.Shape|string} target|targetId
*/
AutoResize.prototype._expand = function(elements, target) {
if (typeof target === 'string') {
target = this._elementRegistry.get(target);
}
var allowed = this._rules.allowed('element.autoResize', {
elements: elements,
target: target
});
if (!allowed) {
return;
}
// calculate the new bounds
var newBounds = this._getOptimalBounds(elements, target);
if (!boundsChanged$1(newBounds, target)) {
return;
}
var resizeDirections = getResizeDirections(pick(target, [ 'x', 'y', 'width', 'height' ]), newBounds);
// resize the parent shape
this.resize(target, newBounds, {
autoResize: resizeDirections
});
var parent = target.parent;
// recursively expand parent elements
if (parent) {
this._expand([ target ], parent);
}
};
/**
* Get the amount to expand the given shape in each direction.
*
* @param {djs.model.Shape} shape
*
* @return {TRBL}
*/
AutoResize.prototype.getOffset = function(shape) {
return { top: 60, bottom: 60, left: 100, right: 100 };
};
/**
* Get the activation threshold for each side for which
* resize triggers.
*
* @param {djs.model.Shape} shape
*
* @return {TRBL}
*/
AutoResize.prototype.getPadding = function(shape) {
return { top: 2, bottom: 2, left: 15, right: 15 };
};
/**
* Perform the actual resize operation.
*
* @param {djs.model.Shape} shape
* @param {Bounds} newBounds
* @param {Object} [hints]
* @param {string} [hints.autoResize]
*/
AutoResize.prototype.resize = function(shape, newBounds, hints) {
this._modeling.resizeShape(shape, newBounds, null, hints);
};
function boundsChanged$1(newBounds, oldBounds) {
return (
newBounds.x !== oldBounds.x ||
newBounds.y !== oldBounds.y ||
newBounds.width !== oldBounds.width ||
newBounds.height !== oldBounds.height
);
}
/**
* Get directions of resize as {n|w|s|e} e.g. "nw".
*
* @param {Bounds} oldBounds
* @param {Bounds} newBounds
*
* @returns {string} Resize directions as {n|w|s|e}.
*/
function getResizeDirections(oldBounds, newBounds) {
var directions = '';
oldBounds = asTRBL(oldBounds);
newBounds = asTRBL(newBounds);
if (oldBounds.top > newBounds.top) {
directions = directions.concat('n');
}
if (oldBounds.right < newBounds.right) {
directions = directions.concat('w');
}
if (oldBounds.bottom < newBounds.bottom) {
directions = directions.concat('s');
}
if (oldBounds.left > newBounds.left) {
directions = directions.concat('e');
}
return directions;
}
/**
* Sub class of the AutoResize module which implements a BPMN
* specific resize function.
*/
function BpmnAutoResize(injector) {
injector.invoke(AutoResize, this);
}
BpmnAutoResize.$inject = [
'injector'
];
e(BpmnAutoResize, AutoResize);
/**
* Resize shapes and lanes.
*
* @param {djs.model.Shape} target
* @param {Bounds} newBounds
* @param {Object} hints
*/
BpmnAutoResize.prototype.resize = function(target, newBounds, hints) {
if (is$1(target, 'bpmn:Participant')) {
this._modeling.resizeLane(target, newBounds, null, hints);
} else {
this._modeling.resizeShape(target, newBounds, null, hints);
}
};
/**
* This is a base rule provider for the element.autoResize rule.
*/
function AutoResizeProvider(eventBus) {
RuleProvider.call(this, eventBus);
var self = this;
this.addRule('element.autoResize', function(context) {
return self.canResize(context.elements, context.target);
});
}
AutoResizeProvider.$inject = [ 'eventBus' ];
e(AutoResizeProvider, RuleProvider);
/**
* Needs to be implemented by sub classes to allow actual auto resize
*
* @param {Array<djs.model.Shape>} elements
* @param {djs.model.Shape} target
*
* @return {boolean}
*/
AutoResizeProvider.prototype.canResize = function(elements, target) {
return false;
};
/**
* This module is a provider for automatically resizing parent BPMN elements
*/
function BpmnAutoResizeProvider(eventBus, modeling) {
AutoResizeProvider.call(this, eventBus);
this._modeling = modeling;
}
e(BpmnAutoResizeProvider, AutoResizeProvider);
BpmnAutoResizeProvider.$inject = [
'eventBus',
'modeling'
];
/**
* Check if the given target can be expanded
*
* @param {djs.model.Shape} target
*
* @return {boolean}
*/
BpmnAutoResizeProvider.prototype.canResize = function(elements, target) {
// do not resize plane elements:
// root elements, collapsed sub-processes
if (is$1(target.di, 'bpmndi:BPMNPlane')) {
return false;
}
if (!is$1(target, 'bpmn:Participant') && !is$1(target, 'bpmn:Lane') && !(is$1(target, 'bpmn:SubProcess'))) {
return false;
}
var canResize = true;
forEach$1(elements, function(element) {
if (is$1(element, 'bpmn:Lane') || element.labelTarget) {
canResize = false;
return;
}
});
return canResize;
};
var AutoResizeModule = {
__init__: [
'bpmnAutoResize',
'bpmnAutoResizeProvider'
],
bpmnAutoResize: [ 'type', BpmnAutoResize ],
bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ]
};
var HIGH_PRIORITY$j = 1500;
/**
* Browsers may swallow certain events (hover, out ...) if users are to
* fast with the mouse.
*
* @see http://stackoverflow.com/questions/7448468/why-cant-i-reliably-capture-a-mouseout-event
*
* The fix implemented in this component ensure that we
*
* 1) have a hover state after a successful drag.move event
* 2) have an out event when dragging leaves an element
*
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Injector} injector
*/
function HoverFix(elementRegistry, eventBus, injector) {
var self = this;
var dragging = injector.get('dragging', false);
/**
* Make sure we are god damn hovering!
*
* @param {Event} dragging event
*/
function ensureHover(event) {
if (event.hover) {
return;
}
var originalEvent = event.originalEvent;
var gfx = self._findTargetGfx(originalEvent);
var element = gfx && elementRegistry.get(gfx);
if (gfx && element) {
// 1) cancel current mousemove
event.stopPropagation();
// 2) emit fake hover for new target
dragging.hover({ element: element, gfx: gfx });
// 3) re-trigger move event
dragging.move(originalEvent);
}
}
if (dragging) {
/**
* We wait for a specific sequence of events before
* emitting a fake drag.hover event.
*
* Event Sequence:
*
* drag.start
* drag.move >> ensure we are hovering
*/
eventBus.on('drag.start', function(event) {
eventBus.once('drag.move', HIGH_PRIORITY$j, function(event) {
ensureHover(event);
});
});
}
/**
* We make sure that element.out is always fired, even if the
* browser swallows an element.out event.
*
* Event sequence:
*
* element.hover
* (element.out >> sometimes swallowed)
* element.hover >> ensure we fired element.out
*/
(function() {
var hoverGfx;
var hover;
eventBus.on('element.hover', function(event) {
// (1) remember current hover element
hoverGfx = event.gfx;
hover = event.element;
});
eventBus.on('element.hover', HIGH_PRIORITY$j, function(event) {
// (3) am I on an element still?
if (hover) {
// (4) that is a problem, gotta "simulate the out"
eventBus.fire('element.out', {
element: hover,
gfx: hoverGfx
});
}
});
eventBus.on('element.out', function() {
// (2) unset hover state if we correctly outed us *GG*
hoverGfx = null;
hover = null;
});
})();
this._findTargetGfx = function(event) {
var position,
target;
if (!(event instanceof MouseEvent)) {
return;
}
position = toPoint(event);
// damn expensive operation, ouch!
target = document.elementFromPoint(position.x, position.y);
return getGfx(target);
};
}
HoverFix.$inject = [
'elementRegistry',
'eventBus',
'injector'
];
// helpers /////////////////////
function getGfx(target) {
return closest(target, 'svg, .djs-element', true);
}
var HoverFixModule = {
__init__: [
'hoverFix'
],
hoverFix: [ 'type', HoverFix ],
};
var round$a = Math.round;
var DRAG_ACTIVE_CLS = 'djs-drag-active';
function preventDefault$1(event) {
event.preventDefault();
}
function isTouchEvent(event) {
// check for TouchEvent being available first
// (i.e. not available on desktop Firefox)
return typeof TouchEvent !== 'undefined' && event instanceof TouchEvent;
}
function getLength(point) {
return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
}
/**
* A helper that fires canvas localized drag events and realizes
* the general "drag-and-drop" look and feel.
*
* Calling {@link Dragging#activate} activates dragging on a canvas.
*
* It provides the following:
*
* * emits life cycle events, namespaced with a prefix assigned
* during dragging activation
* * sets and restores the cursor
* * sets and restores the selection if elements still exist
* * ensures there can be only one drag operation active at a time
*
* Dragging may be canceled manually by calling {@link Dragging#cancel}
* or by pressing ESC.
*
*
* ## Life-cycle events
*
* Dragging can be in three different states, off, initialized
* and active.
*
* (1) off: no dragging operation is in progress
* (2) initialized: a new drag operation got initialized but not yet
* started (i.e. because of no initial move)
* (3) started: dragging is in progress
*
* Eventually dragging will be off again after a drag operation has
* been ended or canceled via user click or ESC key press.
*
* To indicate transitions between these states dragging emits generic
* life-cycle events with the `drag.` prefix _and_ events namespaced
* to a prefix choosen by a user during drag initialization.
*
* The following events are emitted (appropriately prefixed) via
* the {@link EventBus}.
*
* * `init`
* * `start`
* * `move`
* * `end`
* * `ended` (dragging already in off state)
* * `cancel` (only if previously started)
* * `canceled` (dragging already in off state, only if previously started)
* * `cleanup`
*
*
* @example
*
* function MyDragComponent(eventBus, dragging) {
*
* eventBus.on('mydrag.start', function(event) {
* console.log('yes, we start dragging');
* });
*
* eventBus.on('mydrag.move', function(event) {
* console.log('canvas local coordinates', event.x, event.y, event.dx, event.dy);
*
* // local drag data is passed with the event
* event.context.foo; // "BAR"
*
* // the original mouse event, too
* event.originalEvent; // MouseEvent(...)
* });
*
* eventBus.on('element.click', function(event) {
* dragging.init(event, 'mydrag', {
* cursor: 'grabbing',
* data: {
* context: {
* foo: "BAR"
* }
* }
* });
* });
* }
*/
function Dragging(eventBus, canvas, selection, elementRegistry) {
var defaultOptions = {
threshold: 5,
trapClick: true
};
// the currently active drag operation
// dragging is active as soon as this context exists.
//
// it is visually _active_ only when a context.active flag is set to true.
var context;
/* convert a global event into local coordinates */
function toLocalPoint(globalPosition) {
var viewbox = canvas.viewbox();
var clientRect = canvas._container.getBoundingClientRect();
return {
x: viewbox.x + (globalPosition.x - clientRect.left) / viewbox.scale,
y: viewbox.y + (globalPosition.y - clientRect.top) / viewbox.scale
};
}
// helpers
function fire(type, dragContext) {
dragContext = dragContext || context;
var event = eventBus.createEvent(
assign(
{},
dragContext.payload,
dragContext.data,
{ isTouch: dragContext.isTouch }
)
);
// default integration
if (eventBus.fire('drag.' + type, event) === false) {
return false;
}
return eventBus.fire(dragContext.prefix + '.' + type, event);
}
function restoreSelection(previousSelection) {
var existingSelection = previousSelection.filter(function(element) {
return elementRegistry.get(element.id);
});
existingSelection.length && selection.select(existingSelection);
}
// event listeners
function move(event, activate) {
var payload = context.payload,
displacement = context.displacement;
var globalStart = context.globalStart,
globalCurrent = toPoint(event),
globalDelta = delta(globalCurrent, globalStart);
var localStart = context.localStart,
localCurrent = toLocalPoint(globalCurrent),
localDelta = delta(localCurrent, localStart);
// activate context explicitly or once threshold is reached
if (!context.active && (activate || getLength(globalDelta) > context.threshold)) {
// fire start event with original
// starting coordinates
assign(payload, {
x: round$a(localStart.x + displacement.x),
y: round$a(localStart.y + displacement.y),
dx: 0,
dy: 0
}, { originalEvent: event });
if (false === fire('start')) {
return cancel();
}
context.active = true;
// unset selection and remember old selection
// the previous (old) selection will always passed
// with the event via the event.previousSelection property
if (!context.keepSelection) {
payload.previousSelection = selection.get();
selection.select(null);
}
// allow custom cursor
if (context.cursor) {
set(context.cursor);
}
// indicate dragging via marker on root element
canvas.addMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS);
}
stopPropagation$1(event);
if (context.active) {
// update payload with actual coordinates
assign(payload, {
x: round$a(localCurrent.x + displacement.x),
y: round$a(localCurrent.y + displacement.y),
dx: round$a(localDelta.x),
dy: round$a(localDelta.y)
}, { originalEvent: event });
// emit move event
fire('move');
}
}
function end(event) {
var previousContext,
returnValue = true;
if (context.active) {
if (event) {
context.payload.originalEvent = event;
// suppress original event (click, ...)
// because we just ended a drag operation
stopPropagation$1(event);
}
// implementations may stop restoring the
// original state (selections, ...) by preventing the
// end events default action
returnValue = fire('end');
}
if (returnValue === false) {
fire('rejected');
}
previousContext = cleanup(returnValue !== true);
// last event to be fired when all drag operations are done
// at this point in time no drag operation is in progress anymore
fire('ended', previousContext);
}
// cancel active drag operation if the user presses
// the ESC key on the keyboard
function checkCancel(event) {
if (event.which === 27) {
preventDefault$1(event);
cancel();
}
}
// prevent ghost click that might occur after a finished
// drag and drop session
function trapClickAndEnd(event) {
var untrap;
// trap the click in case we are part of an active
// drag operation. This will effectively prevent
// the ghost click that cannot be canceled otherwise.
if (context.active) {
untrap = install(eventBus);
// remove trap after minimal delay
setTimeout(untrap, 400);
// prevent default action (click)
preventDefault$1(event);
}
end(event);
}
function trapTouch(event) {
move(event);
}
// update the drag events hover (djs.model.Base) and hoverGfx (Snap<SVGElement>)
// properties during hover and out and fire {prefix}.hover and {prefix}.out properties
// respectively
function hover(event) {
var payload = context.payload;
payload.hoverGfx = event.gfx;
payload.hover = event.element;
fire('hover');
}
function out(event) {
fire('out');
var payload = context.payload;
payload.hoverGfx = null;
payload.hover = null;
}
// life-cycle methods
function cancel(restore) {
var previousContext;
if (!context) {
return;
}
var wasActive = context.active;
if (wasActive) {
fire('cancel');
}
previousContext = cleanup(restore);
if (wasActive) {
// last event to be fired when all drag operations are done
// at this point in time no drag operation is in progress anymore
fire('canceled', previousContext);
}
}
function cleanup(restore) {
var previousContext,
endDrag;
fire('cleanup');
// reset cursor
unset();
if (context.trapClick) {
endDrag = trapClickAndEnd;
} else {
endDrag = end;
}
// reset dom listeners
componentEvent.unbind(document, 'mousemove', move);
componentEvent.unbind(document, 'dragstart', preventDefault$1);
componentEvent.unbind(document, 'selectstart', preventDefault$1);
componentEvent.unbind(document, 'mousedown', endDrag, true);
componentEvent.unbind(document, 'mouseup', endDrag, true);
componentEvent.unbind(document, 'keyup', checkCancel);
componentEvent.unbind(document, 'touchstart', trapTouch, true);
componentEvent.unbind(document, 'touchcancel', cancel, true);
componentEvent.unbind(document, 'touchmove', move, true);
componentEvent.unbind(document, 'touchend', end, true);
eventBus.off('element.hover', hover);
eventBus.off('element.out', out);
// remove drag marker on root element
canvas.removeMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS);
// restore selection, unless it has changed
var previousSelection = context.payload.previousSelection;
if (restore !== false && previousSelection && !selection.get().length) {
restoreSelection(previousSelection);
}
previousContext = context;
context = null;
return previousContext;
}
/**
* Initialize a drag operation.
*
* If `localPosition` is given, drag events will be emitted
* relative to it.
*
* @param {MouseEvent|TouchEvent} [event]
* @param {Point} [localPosition] actual diagram local position this drag operation should start at
* @param {string} prefix
* @param {Object} [options]
*/
function init(event, relativeTo, prefix, options) {
// only one drag operation may be active, at a time
if (context) {
cancel(false);
}
if (typeof relativeTo === 'string') {
options = prefix;
prefix = relativeTo;
relativeTo = null;
}
options = assign({}, defaultOptions, options || {});
var data = options.data || {},
originalEvent,
globalStart,
localStart,
endDrag,
isTouch;
if (options.trapClick) {
endDrag = trapClickAndEnd;
} else {
endDrag = end;
}
if (event) {
originalEvent = getOriginal$1(event) || event;
globalStart = toPoint(event);
stopPropagation$1(event);
// prevent default browser dragging behavior
if (originalEvent.type === 'dragstart') {
preventDefault$1(originalEvent);
}
} else {
originalEvent = null;
globalStart = { x: 0, y: 0 };
}
localStart = toLocalPoint(globalStart);
if (!relativeTo) {
relativeTo = localStart;
}
isTouch = isTouchEvent(originalEvent);
context = assign({
prefix: prefix,
data: data,
payload: {},
globalStart: globalStart,
displacement: delta(relativeTo, localStart),
localStart: localStart,
isTouch: isTouch
}, options);
// skip dom registration if trigger
// is set to manual (during testing)
if (!options.manual) {
// add dom listeners
if (isTouch) {
componentEvent.bind(document, 'touchstart', trapTouch, true);
componentEvent.bind(document, 'touchcancel', cancel, true);
componentEvent.bind(document, 'touchmove', move, true);
componentEvent.bind(document, 'touchend', end, true);
} else {
// assume we use the mouse to interact per default
componentEvent.bind(document, 'mousemove', move);
// prevent default browser drag and text selection behavior
componentEvent.bind(document, 'dragstart', preventDefault$1);
componentEvent.bind(document, 'selectstart', preventDefault$1);
componentEvent.bind(document, 'mousedown', endDrag, true);
componentEvent.bind(document, 'mouseup', endDrag, true);
}
componentEvent.bind(document, 'keyup', checkCancel);
eventBus.on('element.hover', hover);
eventBus.on('element.out', out);
}
fire('init');
if (options.autoActivate) {
move(event, true);
}
}
// cancel on diagram destruction
eventBus.on('diagram.destroy', cancel);
// API
this.init = init;
this.move = move;
this.hover = hover;
this.out = out;
this.end = end;
this.cancel = cancel;
// for introspection
this.context = function() {
return context;
};
this.setOptions = function(options) {
assign(defaultOptions, options);
};
}
Dragging.$inject = [
'eventBus',
'canvas',
'selection',
'elementRegistry'
];
var DraggingModule = {
__depends__: [
HoverFixModule,
SelectionModule,
],
dragging: [ 'type', Dragging ],
};
/**
* Initiates canvas scrolling if current cursor point is close to a border.
* Cancelled when current point moves back inside the scrolling borders
* or cancelled manually.
*
* Default options :
* scrollThresholdIn: [ 20, 20, 20, 20 ],
* scrollThresholdOut: [ 0, 0, 0, 0 ],
* scrollRepeatTimeout: 15,
* scrollStep: 10
*
* Threshold order:
* [ left, top, right, bottom ]
*/
function AutoScroll(config, eventBus, canvas) {
this._canvas = canvas;
this._opts = assign({
scrollThresholdIn: [ 20, 20, 20, 20 ],
scrollThresholdOut: [ 0, 0, 0, 0 ],
scrollRepeatTimeout: 15,
scrollStep: 10
}, config);
var self = this;
eventBus.on('drag.move', function(e) {
var point = self._toBorderPoint(e);
self.startScroll(point);
});
eventBus.on([ 'drag.cleanup' ], function() {
self.stopScroll();
});
}
AutoScroll.$inject = [
'config.autoScroll',
'eventBus',
'canvas'
];
/**
* Starts scrolling loop.
* Point is given in global scale in canvas container box plane.
*
* @param {Object} point { x: X, y: Y }
*/
AutoScroll.prototype.startScroll = function(point) {
var canvas = this._canvas;
var opts = this._opts;
var self = this;
var clientRect = canvas.getContainer().getBoundingClientRect();
var diff = [
point.x,
point.y,
clientRect.width - point.x,
clientRect.height - point.y
];
this.stopScroll();
var dx = 0,
dy = 0;
for (var i = 0; i < 4; i++) {
if (between(diff[i], opts.scrollThresholdOut[i], opts.scrollThresholdIn[i])) {
if (i === 0) {
dx = opts.scrollStep;
} else if (i == 1) {
dy = opts.scrollStep;
} else if (i == 2) {
dx = -opts.scrollStep;
} else if (i == 3) {
dy = -opts.scrollStep;
}
}
}
if (dx !== 0 || dy !== 0) {
canvas.scroll({ dx: dx, dy: dy });
this._scrolling = setTimeout(function() {
self.startScroll(point);
}, opts.scrollRepeatTimeout);
}
};
function between(val, start, end) {
if (start < val && val < end) {
return true;
}
return false;
}
/**
* Stops scrolling loop.
*/
AutoScroll.prototype.stopScroll = function() {
clearTimeout(this._scrolling);
};
/**
* Overrides defaults options.
*
* @param {Object} options
*/
AutoScroll.prototype.setOptions = function(options) {
this._opts = assign({}, this._opts, options);
};
/**
* Converts event to a point in canvas container plane in global scale.
*
* @param {Event} event
* @return {Point}
*/
AutoScroll.prototype._toBorderPoint = function(event) {
var clientRect = this._canvas._container.getBoundingClientRect();
var globalPosition = toPoint(event.originalEvent);
return {
x: globalPosition.x - clientRect.left,
y: globalPosition.y - clientRect.top
};
};
var AutoScrollModule = {
__depends__: [
DraggingModule,
],
__init__: [ 'autoScroll' ],
autoScroll: [ 'type', AutoScroll ]
};
/**
* A service that provides rules for certain diagram actions.
*
* The default implementation will hook into the {@link CommandStack}
* to perform the actual rule evaluation. Make sure to provide the
* `commandStack` service with this module if you plan to use it.
*
* Together with this implementation you may use the {@link RuleProvider}
* to implement your own rule checkers.
*
* This module is ment to be easily replaced, thus the tiny foot print.
*
* @param {Injector} injector
*/
function Rules(injector) {
this._commandStack = injector.get('commandStack', false);
}
Rules.$inject = [ 'injector' ];
/**
* Returns whether or not a given modeling action can be executed
* in the specified context.
*
* This implementation will respond with allow unless anyone
* objects.
*
* @param {string} action the action to be checked
* @param {Object} [context] the context to check the action in
*
* @return {boolean} returns true, false or null depending on whether the
* operation is allowed, not allowed or should be ignored.
*/
Rules.prototype.allowed = function(action, context) {
var allowed = true;
var commandStack = this._commandStack;
if (commandStack) {
allowed = commandStack.canExecute(action, context);
}
// map undefined to true, i.e. no rules
return allowed === undefined ? true : allowed;
};
var RulesModule$1 = {
__init__: [ 'rules' ],
rules: [ 'type', Rules ]
};
var round$9 = Math.round,
max$6 = Math.max;
function circlePath(center, r) {
var x = center.x,
y = center.y;
return [
[ 'M', x, y ],
[ 'm', 0, -r ],
[ 'a', r, r, 0, 1, 1, 0, 2 * r ],
[ 'a', r, r, 0, 1, 1, 0, -2 * r ],
[ 'z' ]
];
}
function linePath(points) {
var segments = [];
points.forEach(function(p, idx) {
segments.push([ idx === 0 ? 'M' : 'L', p.x, p.y ]);
});
return segments;
}
var INTERSECTION_THRESHOLD$1 = 10;
function getBendpointIntersection(waypoints, reference) {
var i, w;
for (i = 0; (w = waypoints[i]); i++) {
if (pointDistance(w, reference) <= INTERSECTION_THRESHOLD$1) {
return {
point: waypoints[i],
bendpoint: true,
index: i
};
}
}
return null;
}
function getPathIntersection(waypoints, reference) {
var intersections = intersect(circlePath(reference, INTERSECTION_THRESHOLD$1), linePath(waypoints));
var a = intersections[0],
b = intersections[intersections.length - 1],
idx;
if (!a) {
// no intersection
return null;
}
if (a !== b) {
if (a.segment2 !== b.segment2) {
// we use the bendpoint in between both segments
// as the intersection point
idx = max$6(a.segment2, b.segment2) - 1;
return {
point: waypoints[idx],
bendpoint: true,
index: idx
};
}
return {
point: {
x: (round$9(a.x + b.x) / 2),
y: (round$9(a.y + b.y) / 2)
},
index: a.segment2
};
}
return {
point: {
x: round$9(a.x),
y: round$9(a.y)
},
index: a.segment2
};
}
/**
* Returns the closest point on the connection towards a given reference point.
*
* @param {Array<Point>} waypoints
* @param {Point} reference
*
* @return {Object} intersection data (segment, point)
*/
function getApproxIntersection(waypoints, reference) {
return getBendpointIntersection(waypoints, reference) || getPathIntersection(waypoints, reference);
}
/**
* Returns the length of a vector
*
* @param {Vector}
* @return {Float}
*/
function vectorLength(v) {
return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2));
}
/**
* Calculates the angle between a line a the yAxis
*
* @param {Array}
* @return {Float}
*/
function getAngle(line) {
// return value is between 0, 180 and -180, -0
// @janstuemmel: maybe replace return a/b with b/a
return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x));
}
/**
* Rotates a vector by a given angle
*
* @param {Vector}
* @param {Float} Angle in radians
* @return {Vector}
*/
function rotateVector(vector, angle) {
return (!angle) ? vector : {
x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
};
}
/**
* Solves a 2D equation system
* a + r*b = c, where a,b,c are 2D vectors
*
* @param {Vector}
* @param {Vector}
* @param {Vector}
* @return {Float}
*/
function solveLambaSystem(a, b, c) {
// the 2d system
var system = [
{ n: a[0] - c[0], lambda: b[0] },
{ n: a[1] - c[1], lambda: b[1] }
];
// solve
var n = system[0].n * b[0] + system[1].n * b[1],
l = system[0].lambda * b[0] + system[1].lambda * b[1];
return -n / l;
}
/**
* Position of perpendicular foot
*
* @param {Point}
* @param [ {Point}, {Point} ] line defined through two points
* @return {Point} the perpendicular foot position
*/
function perpendicularFoot(point, line) {
var a = line[0], b = line[1];
// relative position of b from a
var bd = { x: b.x - a.x, y: b.y - a.y };
// solve equation system to the parametrized vectors param real value
var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]);
return { x: a.x + r * bd.x, y: a.y + r * bd.y };
}
/**
* Calculates the distance between a point and a line
*
* @param {Point}
* @param [ {Point}, {Point} ] line defined through two points
* @return {Float} distance
*/
function getDistancePointLine(point, line) {
var pfPoint = perpendicularFoot(point, line);
// distance vector
var connectionVector = {
x: pfPoint.x - point.x,
y: pfPoint.y - point.y
};
return vectorLength(connectionVector);
}
/**
* Calculates the distance between two points
*
* @param {Point}
* @param {Point}
* @return {Float} distance
*/
function getDistancePointPoint(point1, point2) {
return vectorLength({
x: point1.x - point2.x,
y: point1.y - point2.y
});
}
var BENDPOINT_CLS = 'djs-bendpoint';
var SEGMENT_DRAGGER_CLS = 'djs-segment-dragger';
function toCanvasCoordinates(canvas, event) {
var position = toPoint(event),
clientRect = canvas._container.getBoundingClientRect(),
offset;
// canvas relative position
offset = {
x: clientRect.left,
y: clientRect.top
};
// update actual event payload with canvas relative measures
var viewbox = canvas.viewbox();
return {
x: viewbox.x + (position.x - offset.x) / viewbox.scale,
y: viewbox.y + (position.y - offset.y) / viewbox.scale
};
}
function getConnectionIntersection(canvas, waypoints, event) {
var localPosition = toCanvasCoordinates(canvas, event),
intersection = getApproxIntersection(waypoints, localPosition);
return intersection;
}
function addBendpoint(parentGfx, cls) {
var groupGfx = create$1('g');
classes(groupGfx).add(BENDPOINT_CLS);
append(parentGfx, groupGfx);
var visual = create$1('circle');
attr(visual, {
cx: 0,
cy: 0,
r: 4
});
classes(visual).add('djs-visual');
append(groupGfx, visual);
var hit = create$1('circle');
attr(hit, {
cx: 0,
cy: 0,
r: 10
});
classes(hit).add('djs-hit');
append(groupGfx, hit);
if (cls) {
classes(groupGfx).add(cls);
}
return groupGfx;
}
function createParallelDragger(parentGfx, segmentStart, segmentEnd, alignment) {
var draggerGfx = create$1('g');
append(parentGfx, draggerGfx);
var width = 18,
height = 6,
padding = 11,
hitWidth = calculateHitWidth(segmentStart, segmentEnd, alignment),
hitHeight = height + padding;
var visual = create$1('rect');
attr(visual, {
x: -width / 2,
y: -height / 2,
width: width,
height: height
});
classes(visual).add('djs-visual');
append(draggerGfx, visual);
var hit = create$1('rect');
attr(hit, {
x: -hitWidth / 2,
y: -hitHeight / 2,
width: hitWidth,
height: hitHeight
});
classes(hit).add('djs-hit');
append(draggerGfx, hit);
rotate(draggerGfx, alignment === 'v' ? 90 : 0);
return draggerGfx;
}
function addSegmentDragger(parentGfx, segmentStart, segmentEnd) {
var groupGfx = create$1('g'),
mid = getMidPoint(segmentStart, segmentEnd),
alignment = pointsAligned(segmentStart, segmentEnd);
append(parentGfx, groupGfx);
createParallelDragger(groupGfx, segmentStart, segmentEnd, alignment);
classes(groupGfx).add(SEGMENT_DRAGGER_CLS);
classes(groupGfx).add(alignment === 'h' ? 'horizontal' : 'vertical');
translate$2(groupGfx, mid.x, mid.y);
return groupGfx;
}
/**
* Calculates region for segment move which is 2/3 of the full segment length
* @param {number} segmentLength
*
* @return {number}
*/
function calculateSegmentMoveRegion(segmentLength) {
return Math.abs(Math.round(segmentLength * 2 / 3));
}
/**
* Returns the point with the closest distance that is on the connection path.
*
* @param {Point} position
* @param {djs.Base.Connection} connection
* @returns {Point}
*/
function getClosestPointOnConnection(position, connection) {
var segment = getClosestSegment(position, connection);
return perpendicularFoot(position, segment);
}
// helper //////////
function calculateHitWidth(segmentStart, segmentEnd, alignment) {
var segmentLengthXAxis = segmentEnd.x - segmentStart.x,
segmentLengthYAxis = segmentEnd.y - segmentStart.y;
return alignment === 'h' ?
calculateSegmentMoveRegion(segmentLengthXAxis) :
calculateSegmentMoveRegion(segmentLengthYAxis);
}
function getClosestSegment(position, connection) {
var waypoints = connection.waypoints;
var minDistance = Infinity,
segmentIndex;
for (var i = 0; i < waypoints.length - 1; i++) {
var start = waypoints[i],
end = waypoints[i + 1],
distance = getDistancePointLine(position, [ start, end ]);
if (distance < minDistance) {
minDistance = distance;
segmentIndex = i;
}
}
return [ waypoints[segmentIndex], waypoints[segmentIndex + 1] ];
}
/**
* A service that adds editable bendpoints to connections.
*/
function Bendpoints(
eventBus, canvas, interactionEvents,
bendpointMove, connectionSegmentMove) {
/**
* Returns true if intersection point is inside middle region of segment, adjusted by
* optional threshold
*/
function isIntersectionMiddle(intersection, waypoints, treshold) {
var idx = intersection.index,
p = intersection.point,
p0, p1, mid, aligned, xDelta, yDelta;
if (idx <= 0 || intersection.bendpoint) {
return false;
}
p0 = waypoints[idx - 1];
p1 = waypoints[idx];
mid = getMidPoint(p0, p1),
aligned = pointsAligned(p0, p1);
xDelta = Math.abs(p.x - mid.x);
yDelta = Math.abs(p.y - mid.y);
return aligned && xDelta <= treshold && yDelta <= treshold;
}
/**
* Calculates the threshold from a connection's middle which fits the two-third-region
*/
function calculateIntersectionThreshold(connection, intersection) {
var waypoints = connection.waypoints,
relevantSegment, alignment, segmentLength, threshold;
if (intersection.index <= 0 || intersection.bendpoint) {
return null;
}
// segment relative to connection intersection
relevantSegment = {
start: waypoints[intersection.index - 1],
end: waypoints[intersection.index]
};
alignment = pointsAligned(relevantSegment.start, relevantSegment.end);
if (!alignment) {
return null;
}
if (alignment === 'h') {
segmentLength = relevantSegment.end.x - relevantSegment.start.x;
} else {
segmentLength = relevantSegment.end.y - relevantSegment.start.y;
}
// calculate threshold relative to 2/3 of segment length
threshold = calculateSegmentMoveRegion(segmentLength) / 2;
return threshold;
}
function activateBendpointMove(event, connection) {
var waypoints = connection.waypoints,
intersection = getConnectionIntersection(canvas, waypoints, event),
threshold;
if (!intersection) {
return;
}
threshold = calculateIntersectionThreshold(connection, intersection);
if (isIntersectionMiddle(intersection, waypoints, threshold)) {
connectionSegmentMove.start(event, connection, intersection.index);
} else {
bendpointMove.start(event, connection, intersection.index, !intersection.bendpoint);
}
// we've handled the event
return true;
}
function bindInteractionEvents(node, eventName, element) {
componentEvent.bind(node, eventName, function(event) {
interactionEvents.triggerMouseEvent(eventName, event, element);
event.stopPropagation();
});
}
function getBendpointsContainer(element, create) {
var layer = canvas.getLayer('overlays'),
gfx = query('.djs-bendpoints[data-element-id="' + cssEscape(element.id) + '"]', layer);
if (!gfx && create) {
gfx = create$1('g');
attr(gfx, { 'data-element-id': element.id });
classes(gfx).add('djs-bendpoints');
append(layer, gfx);
bindInteractionEvents(gfx, 'mousedown', element);
bindInteractionEvents(gfx, 'click', element);
bindInteractionEvents(gfx, 'dblclick', element);
}
return gfx;
}
function getSegmentDragger(idx, parentGfx) {
return query(
'.djs-segment-dragger[data-segment-idx="' + idx + '"]',
parentGfx
);
}
function createBendpoints(gfx, connection) {
connection.waypoints.forEach(function(p, idx) {
var bendpoint = addBendpoint(gfx);
append(gfx, bendpoint);
translate$2(bendpoint, p.x, p.y);
});
// add floating bendpoint
addBendpoint(gfx, 'floating');
}
function createSegmentDraggers(gfx, connection) {
var waypoints = connection.waypoints;
var segmentStart,
segmentEnd,
segmentDraggerGfx;
for (var i = 1; i < waypoints.length; i++) {
segmentStart = waypoints[i - 1];
segmentEnd = waypoints[i];
if (pointsAligned(segmentStart, segmentEnd)) {
segmentDraggerGfx = addSegmentDragger(gfx, segmentStart, segmentEnd);
attr(segmentDraggerGfx, { 'data-segment-idx': i });
bindInteractionEvents(segmentDraggerGfx, 'mousemove', connection);
}
}
}
function clearBendpoints(gfx) {
forEach$1(all('.' + BENDPOINT_CLS, gfx), function(node) {
remove$1(node);
});
}
function clearSegmentDraggers(gfx) {
forEach$1(all('.' + SEGMENT_DRAGGER_CLS, gfx), function(node) {
remove$1(node);
});
}
function addHandles(connection) {
var gfx = getBendpointsContainer(connection);
if (!gfx) {
gfx = getBendpointsContainer(connection, true);
createBendpoints(gfx, connection);
createSegmentDraggers(gfx, connection);
}
return gfx;
}
function updateHandles(connection) {
var gfx = getBendpointsContainer(connection);
if (gfx) {
clearSegmentDraggers(gfx);
clearBendpoints(gfx);
createSegmentDraggers(gfx, connection);
createBendpoints(gfx, connection);
}
}
function updateFloatingBendpointPosition(parentGfx, intersection) {
var floating = query('.floating', parentGfx),
point = intersection.point;
if (!floating) {
return;
}
translate$2(floating, point.x, point.y);
}
function updateSegmentDraggerPosition(parentGfx, intersection, waypoints) {
var draggerGfx = getSegmentDragger(intersection.index, parentGfx),
segmentStart = waypoints[intersection.index - 1],
segmentEnd = waypoints[intersection.index],
point = intersection.point,
mid = getMidPoint(segmentStart, segmentEnd),
alignment = pointsAligned(segmentStart, segmentEnd),
draggerVisual, relativePosition;
if (!draggerGfx) {
return;
}
draggerVisual = getDraggerVisual(draggerGfx);
relativePosition = {
x: point.x - mid.x,
y: point.y - mid.y
};
if (alignment === 'v') {
// rotate position
relativePosition = {
x: relativePosition.y,
y: relativePosition.x
};
}
translate$2(draggerVisual, relativePosition.x, relativePosition.y);
}
eventBus.on('connection.changed', function(event) {
updateHandles(event.element);
});
eventBus.on('connection.remove', function(event) {
var gfx = getBendpointsContainer(event.element);
if (gfx) {
remove$1(gfx);
}
});
eventBus.on('element.marker.update', function(event) {
var element = event.element,
bendpointsGfx;
if (!element.waypoints) {
return;
}
bendpointsGfx = addHandles(element);
if (event.add) {
classes(bendpointsGfx).add(event.marker);
} else {
classes(bendpointsGfx).remove(event.marker);
}
});
eventBus.on('element.mousemove', function(event) {
var element = event.element,
waypoints = element.waypoints,
bendpointsGfx,
intersection;
if (waypoints) {
bendpointsGfx = getBendpointsContainer(element, true);
intersection = getConnectionIntersection(canvas, waypoints, event.originalEvent);
if (!intersection) {
return;
}
updateFloatingBendpointPosition(bendpointsGfx, intersection);
if (!intersection.bendpoint) {
updateSegmentDraggerPosition(bendpointsGfx, intersection, waypoints);
}
}
});
eventBus.on('element.mousedown', function(event) {
if (!isPrimaryButton(event)) {
return;
}
var originalEvent = event.originalEvent,
element = event.element;
if (!element.waypoints) {
return;
}
return activateBendpointMove(originalEvent, element);
});
eventBus.on('selection.changed', function(event) {
var newSelection = event.newSelection,
primary = newSelection[0];
if (primary && primary.waypoints) {
addHandles(primary);
}
});
eventBus.on('element.hover', function(event) {
var element = event.element;
if (element.waypoints) {
addHandles(element);
interactionEvents.registerEvent(event.gfx, 'mousemove', 'element.mousemove');
}
});
eventBus.on('element.out', function(event) {
interactionEvents.unregisterEvent(event.gfx, 'mousemove', 'element.mousemove');
});
// update bendpoint container data attribute on element ID change
eventBus.on('element.updateId', function(context) {
var element = context.element,
newId = context.newId;
if (element.waypoints) {
var bendpointContainer = getBendpointsContainer(element);
if (bendpointContainer) {
attr(bendpointContainer, { 'data-element-id': newId });
}
}
});
// API
this.addHandles = addHandles;
this.updateHandles = updateHandles;
this.getBendpointsContainer = getBendpointsContainer;
this.getSegmentDragger = getSegmentDragger;
}
Bendpoints.$inject = [
'eventBus',
'canvas',
'interactionEvents',
'bendpointMove',
'connectionSegmentMove'
];
// helper /////////////
function getDraggerVisual(draggerGfx) {
return query('.djs-visual', draggerGfx);
}
var round$8 = Math.round;
var RECONNECT_START$1 = 'reconnectStart',
RECONNECT_END$1 = 'reconnectEnd',
UPDATE_WAYPOINTS$1 = 'updateWaypoints';
/**
* Move bendpoints through drag and drop to add/remove bendpoints or reconnect connection.
*/
function BendpointMove(injector, eventBus, canvas, dragging, rules, modeling) {
this._injector = injector;
this.start = function(event, connection, bendpointIndex, insert) {
var gfx = canvas.getGraphics(connection),
source = connection.source,
target = connection.target,
waypoints = connection.waypoints,
type;
if (!insert && bendpointIndex === 0) {
type = RECONNECT_START$1;
} else
if (!insert && bendpointIndex === waypoints.length - 1) {
type = RECONNECT_END$1;
} else {
type = UPDATE_WAYPOINTS$1;
}
var command = type === UPDATE_WAYPOINTS$1 ? 'connection.updateWaypoints' : 'connection.reconnect';
var allowed = rules.allowed(command, {
connection: connection,
source: source,
target: target
});
if (allowed === false) {
allowed = rules.allowed(command, {
connection: connection,
source: target,
target: source
});
}
if (allowed === false) {
return;
}
dragging.init(event, 'bendpoint.move', {
data: {
connection: connection,
connectionGfx: gfx,
context: {
allowed: allowed,
bendpointIndex: bendpointIndex,
connection: connection,
source: source,
target: target,
insert: insert,
type: type
}
}
});
};
eventBus.on('bendpoint.move.hover', function(event) {
var context = event.context,
connection = context.connection,
source = connection.source,
target = connection.target,
hover = event.hover,
type = context.type;
// cache hover state
context.hover = hover;
var allowed;
if (!hover) {
return;
}
var command = type === UPDATE_WAYPOINTS$1 ? 'connection.updateWaypoints' : 'connection.reconnect';
allowed = context.allowed = rules.allowed(command, {
connection: connection,
source: type === RECONNECT_START$1 ? hover : source,
target: type === RECONNECT_END$1 ? hover : target
});
if (allowed) {
context.source = type === RECONNECT_START$1 ? hover : source;
context.target = type === RECONNECT_END$1 ? hover : target;
return;
}
if (allowed === false) {
allowed = context.allowed = rules.allowed(command, {
connection: connection,
source: type === RECONNECT_END$1 ? hover : target,
target: type === RECONNECT_START$1 ? hover : source
});
}
if (allowed) {
context.source = type === RECONNECT_END$1 ? hover : target;
context.target = type === RECONNECT_START$1 ? hover : source;
}
});
eventBus.on([ 'bendpoint.move.out', 'bendpoint.move.cleanup' ], function(event) {
var context = event.context,
type = context.type;
context.hover = null;
context.source = null;
context.target = null;
if (type !== UPDATE_WAYPOINTS$1) {
context.allowed = false;
}
});
eventBus.on('bendpoint.move.end', function(event) {
var context = event.context,
allowed = context.allowed,
bendpointIndex = context.bendpointIndex,
connection = context.connection,
insert = context.insert,
newWaypoints = connection.waypoints.slice(),
source = context.source,
target = context.target,
type = context.type,
hints = context.hints || {};
// ensure integer values (important if zoom level was > 1 during move)
var docking = {
x: round$8(event.x),
y: round$8(event.y)
};
if (!allowed) {
return false;
}
if (type === UPDATE_WAYPOINTS$1) {
if (insert) {
// insert new bendpoint
newWaypoints.splice(bendpointIndex, 0, docking);
} else {
// swap previous waypoint with moved one
newWaypoints[bendpointIndex] = docking;
}
// pass hints about actual moved bendpoint
// useful for connection/label layout
hints.bendpointMove = {
insert: insert,
bendpointIndex: bendpointIndex
};
newWaypoints = this.cropWaypoints(connection, newWaypoints);
modeling.updateWaypoints(connection, filterRedundantWaypoints(newWaypoints), hints);
} else {
if (type === RECONNECT_START$1) {
hints.docking = 'source';
if (isReverse$2(context)) {
hints.docking = 'target';
hints.newWaypoints = newWaypoints.reverse();
}
} else if (type === RECONNECT_END$1) {
hints.docking = 'target';
if (isReverse$2(context)) {
hints.docking = 'source';
hints.newWaypoints = newWaypoints.reverse();
}
}
modeling.reconnect(connection, source, target, docking, hints);
}
}, this);
}
BendpointMove.$inject = [
'injector',
'eventBus',
'canvas',
'dragging',
'rules',
'modeling'
];
BendpointMove.prototype.cropWaypoints = function(connection, newWaypoints) {
var connectionDocking = this._injector.get('connectionDocking', false);
if (!connectionDocking) {
return newWaypoints;
}
var waypoints = connection.waypoints;
connection.waypoints = newWaypoints;
connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
newWaypoints = connection.waypoints;
connection.waypoints = waypoints;
return newWaypoints;
};
// helpers //////////
function isReverse$2(context) {
var hover = context.hover,
source = context.source,
target = context.target,
type = context.type;
if (type === RECONNECT_START$1) {
return hover && target && hover === target && source !== target;
}
if (type === RECONNECT_END$1) {
return hover && source && hover === source && source !== target;
}
}
var RECONNECT_START = 'reconnectStart',
RECONNECT_END = 'reconnectEnd',
UPDATE_WAYPOINTS = 'updateWaypoints';
var MARKER_OK$4 = 'connect-ok',
MARKER_NOT_OK$4 = 'connect-not-ok',
MARKER_CONNECT_HOVER$1 = 'connect-hover',
MARKER_CONNECT_UPDATING$1 = 'djs-updating',
MARKER_ELEMENT_HIDDEN = 'djs-element-hidden';
var HIGH_PRIORITY$i = 1100;
/**
* Preview connection while moving bendpoints.
*/
function BendpointMovePreview(bendpointMove, injector, eventBus, canvas) {
this._injector = injector;
var connectionPreview = injector.get('connectionPreview', false);
eventBus.on('bendpoint.move.start', function(event) {
var context = event.context,
bendpointIndex = context.bendpointIndex,
connection = context.connection,
insert = context.insert,
waypoints = connection.waypoints,
newWaypoints = waypoints.slice();
context.waypoints = waypoints;
if (insert) {
// insert placeholder for new bendpoint
newWaypoints.splice(bendpointIndex, 0, { x: event.x, y: event.y });
}
connection.waypoints = newWaypoints;
// add dragger gfx
var draggerGfx = context.draggerGfx = addBendpoint(canvas.getLayer('overlays'));
classes(draggerGfx).add('djs-dragging');
canvas.addMarker(connection, MARKER_ELEMENT_HIDDEN);
canvas.addMarker(connection, MARKER_CONNECT_UPDATING$1);
});
eventBus.on('bendpoint.move.hover', function(event) {
var context = event.context,
allowed = context.allowed,
hover = context.hover,
type = context.type;
if (hover) {
canvas.addMarker(hover, MARKER_CONNECT_HOVER$1);
if (type === UPDATE_WAYPOINTS) {
return;
}
if (allowed) {
canvas.removeMarker(hover, MARKER_NOT_OK$4);
canvas.addMarker(hover, MARKER_OK$4);
} else if (allowed === false) {
canvas.removeMarker(hover, MARKER_OK$4);
canvas.addMarker(hover, MARKER_NOT_OK$4);
}
}
});
eventBus.on([
'bendpoint.move.out',
'bendpoint.move.cleanup'
], HIGH_PRIORITY$i, function(event) {
var context = event.context,
hover = context.hover,
target = context.target;
if (hover) {
canvas.removeMarker(hover, MARKER_CONNECT_HOVER$1);
canvas.removeMarker(hover, target ? MARKER_OK$4 : MARKER_NOT_OK$4);
}
});
eventBus.on('bendpoint.move.move', function(event) {
var context = event.context,
allowed = context.allowed,
bendpointIndex = context.bendpointIndex,
draggerGfx = context.draggerGfx,
hover = context.hover,
type = context.type,
connection = context.connection,
source = connection.source,
target = connection.target,
newWaypoints = connection.waypoints.slice(),
bendpoint = { x: event.x, y: event.y },
hints = context.hints || {},
drawPreviewHints = {};
if (connectionPreview) {
if (hints.connectionStart) {
drawPreviewHints.connectionStart = hints.connectionStart;
}
if (hints.connectionEnd) {
drawPreviewHints.connectionEnd = hints.connectionEnd;
}
if (type === RECONNECT_START) {
if (isReverse$2(context)) {
drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint;
drawPreviewHints.source = target;
drawPreviewHints.target = hover || source;
newWaypoints = newWaypoints.reverse();
} else {
drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint;
drawPreviewHints.source = hover || source;
drawPreviewHints.target = target;
}
} else if (type === RECONNECT_END) {
if (isReverse$2(context)) {
drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint;
drawPreviewHints.source = hover || target;
drawPreviewHints.target = source;
newWaypoints = newWaypoints.reverse();
} else {
drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint;
drawPreviewHints.source = source;
drawPreviewHints.target = hover || target;
}
} else {
drawPreviewHints.noCropping = true;
drawPreviewHints.noLayout = true;
newWaypoints[ bendpointIndex ] = bendpoint;
}
if (type === UPDATE_WAYPOINTS) {
newWaypoints = bendpointMove.cropWaypoints(connection, newWaypoints);
}
drawPreviewHints.waypoints = newWaypoints;
connectionPreview.drawPreview(context, allowed, drawPreviewHints);
}
translate$2(draggerGfx, event.x, event.y);
}, this);
eventBus.on([
'bendpoint.move.end',
'bendpoint.move.cancel'
], HIGH_PRIORITY$i, function(event) {
var context = event.context,
connection = context.connection,
draggerGfx = context.draggerGfx,
hover = context.hover,
target = context.target,
waypoints = context.waypoints;
connection.waypoints = waypoints;
// remove dragger gfx
remove$1(draggerGfx);
canvas.removeMarker(connection, MARKER_CONNECT_UPDATING$1);
canvas.removeMarker(connection, MARKER_ELEMENT_HIDDEN);
if (hover) {
canvas.removeMarker(hover, MARKER_OK$4);
canvas.removeMarker(hover, target ? MARKER_OK$4 : MARKER_NOT_OK$4);
}
if (connectionPreview) {
connectionPreview.cleanUp(context);
}
});
}
BendpointMovePreview.$inject = [
'bendpointMove',
'injector',
'eventBus',
'canvas'
];
var MARKER_CONNECT_HOVER = 'connect-hover',
MARKER_CONNECT_UPDATING = 'djs-updating';
function axisAdd(point, axis, delta) {
return axisSet(point, axis, point[axis] + delta);
}
function axisSet(point, axis, value) {
return {
x: (axis === 'x' ? value : point.x),
y: (axis === 'y' ? value : point.y)
};
}
function axisFenced(position, segmentStart, segmentEnd, axis) {
var maxValue = Math.max(segmentStart[axis], segmentEnd[axis]),
minValue = Math.min(segmentStart[axis], segmentEnd[axis]);
var padding = 20;
var fencedValue = Math.min(Math.max(minValue + padding, position[axis]), maxValue - padding);
return axisSet(segmentStart, axis, fencedValue);
}
function flipAxis(axis) {
return axis === 'x' ? 'y' : 'x';
}
/**
* Get the docking point on the given element.
*
* Compute a reasonable docking, if non exists.
*
* @param {Point} point
* @param {djs.model.Shape} referenceElement
* @param {string} moveAxis (x|y)
*
* @return {Point}
*/
function getDocking$2(point, referenceElement, moveAxis) {
var referenceMid,
inverseAxis;
if (point.original) {
return point.original;
} else {
referenceMid = getMid(referenceElement);
inverseAxis = flipAxis(moveAxis);
return axisSet(point, inverseAxis, referenceMid[inverseAxis]);
}
}
/**
* A component that implements moving of bendpoints
*/
function ConnectionSegmentMove(
injector, eventBus, canvas,
dragging, graphicsFactory, modeling) {
// optional connection docking integration
var connectionDocking = injector.get('connectionDocking', false);
// API
this.start = function(event, connection, idx) {
var context,
gfx = canvas.getGraphics(connection),
segmentStartIndex = idx - 1,
segmentEndIndex = idx,
waypoints = connection.waypoints,
segmentStart = waypoints[segmentStartIndex],
segmentEnd = waypoints[segmentEndIndex],
intersection = getConnectionIntersection(canvas, waypoints, event),
direction, axis, dragPosition;
direction = pointsAligned(segmentStart, segmentEnd);
// do not move diagonal connection
if (!direction) {
return;
}
// the axis where we are going to move things
axis = direction === 'v' ? 'x' : 'y';
if (segmentStartIndex === 0) {
segmentStart = getDocking$2(segmentStart, connection.source, axis);
}
if (segmentEndIndex === waypoints.length - 1) {
segmentEnd = getDocking$2(segmentEnd, connection.target, axis);
}
if (intersection) {
dragPosition = intersection.point;
} else {
// set to segment center as default
dragPosition = {
x: (segmentStart.x + segmentEnd.x) / 2,
y: (segmentStart.y + segmentEnd.y) / 2
};
}
context = {
connection: connection,
segmentStartIndex: segmentStartIndex,
segmentEndIndex: segmentEndIndex,
segmentStart: segmentStart,
segmentEnd: segmentEnd,
axis: axis,
dragPosition: dragPosition
};
dragging.init(event, dragPosition, 'connectionSegment.move', {
cursor: axis === 'x' ? 'resize-ew' : 'resize-ns',
data: {
connection: connection,
connectionGfx: gfx,
context: context
}
});
};
/**
* Crop connection if connection cropping is provided.
*
* @param {Connection} connection
* @param {Array<Point>} newWaypoints
*
* @return {Array<Point>} cropped connection waypoints
*/
function cropConnection(connection, newWaypoints) {
// crop connection, if docking service is provided only
if (!connectionDocking) {
return newWaypoints;
}
var oldWaypoints = connection.waypoints,
croppedWaypoints;
// temporary set new waypoints
connection.waypoints = newWaypoints;
croppedWaypoints = connectionDocking.getCroppedWaypoints(connection);
// restore old waypoints
connection.waypoints = oldWaypoints;
return croppedWaypoints;
}
// DRAGGING IMPLEMENTATION
function redrawConnection(data) {
graphicsFactory.update('connection', data.connection, data.connectionGfx);
}
function updateDragger(context, segmentOffset, event) {
var newWaypoints = context.newWaypoints,
segmentStartIndex = context.segmentStartIndex + segmentOffset,
segmentStart = newWaypoints[segmentStartIndex],
segmentEndIndex = context.segmentEndIndex + segmentOffset,
segmentEnd = newWaypoints[segmentEndIndex],
axis = flipAxis(context.axis);
// make sure the dragger does not move
// outside the connection
var draggerPosition = axisFenced(event, segmentStart, segmentEnd, axis);
// update dragger
translate$2(context.draggerGfx, draggerPosition.x, draggerPosition.y);
}
/**
* Filter waypoints for redundant ones (i.e. on the same axis).
* Returns the filtered waypoints and the offset related to the segment move.
*
* @param {Array<Point>} waypoints
* @param {Integer} segmentStartIndex of moved segment start
*
* @return {Object} { filteredWaypoints, segmentOffset }
*/
function filterRedundantWaypoints(waypoints, segmentStartIndex) {
var segmentOffset = 0;
var filteredWaypoints = waypoints.filter(function(r, idx) {
if (pointsOnLine(waypoints[idx - 1], waypoints[idx + 1], r)) {
// remove point and increment offset
segmentOffset = idx <= segmentStartIndex ? segmentOffset - 1 : segmentOffset;
return false;
}
// dont remove point
return true;
});
return {
waypoints: filteredWaypoints,
segmentOffset: segmentOffset
};
}
eventBus.on('connectionSegment.move.start', function(event) {
var context = event.context,
connection = event.connection,
layer = canvas.getLayer('overlays');
context.originalWaypoints = connection.waypoints.slice();
// add dragger gfx
context.draggerGfx = addSegmentDragger(layer, context.segmentStart, context.segmentEnd);
classes(context.draggerGfx).add('djs-dragging');
canvas.addMarker(connection, MARKER_CONNECT_UPDATING);
});
eventBus.on('connectionSegment.move.move', function(event) {
var context = event.context,
connection = context.connection,
segmentStartIndex = context.segmentStartIndex,
segmentEndIndex = context.segmentEndIndex,
segmentStart = context.segmentStart,
segmentEnd = context.segmentEnd,
axis = context.axis;
var newWaypoints = context.originalWaypoints.slice(),
newSegmentStart = axisAdd(segmentStart, axis, event['d' + axis]),
newSegmentEnd = axisAdd(segmentEnd, axis, event['d' + axis]);
// original waypoint count and added / removed
// from start waypoint delta. We use the later
// to retrieve the updated segmentStartIndex / segmentEndIndex
var waypointCount = newWaypoints.length,
segmentOffset = 0;
// move segment start / end by axis delta
newWaypoints[segmentStartIndex] = newSegmentStart;
newWaypoints[segmentEndIndex] = newSegmentEnd;
var sourceToSegmentOrientation,
targetToSegmentOrientation;
// handle first segment
if (segmentStartIndex < 2) {
sourceToSegmentOrientation = getOrientation(connection.source, newSegmentStart);
// first bendpoint, remove first segment if intersecting
if (segmentStartIndex === 1) {
if (sourceToSegmentOrientation === 'intersect') {
newWaypoints.shift();
newWaypoints[0] = newSegmentStart;
segmentOffset--;
}
}
// docking point, add segment if not intersecting anymore
else {
if (sourceToSegmentOrientation !== 'intersect') {
newWaypoints.unshift(segmentStart);
segmentOffset++;
}
}
}
// handle last segment
if (segmentEndIndex > waypointCount - 3) {
targetToSegmentOrientation = getOrientation(connection.target, newSegmentEnd);
// last bendpoint, remove last segment if intersecting
if (segmentEndIndex === waypointCount - 2) {
if (targetToSegmentOrientation === 'intersect') {
newWaypoints.pop();
newWaypoints[newWaypoints.length - 1] = newSegmentEnd;
}
}
// last bendpoint, remove last segment if intersecting
else {
if (targetToSegmentOrientation !== 'intersect') {
newWaypoints.push(segmentEnd);
}
}
}
// update connection waypoints
context.newWaypoints = connection.waypoints = cropConnection(connection, newWaypoints);
// update dragger position
updateDragger(context, segmentOffset, event);
// save segmentOffset in context
context.newSegmentStartIndex = segmentStartIndex + segmentOffset;
// redraw connection
redrawConnection(event);
});
eventBus.on('connectionSegment.move.hover', function(event) {
event.context.hover = event.hover;
canvas.addMarker(event.hover, MARKER_CONNECT_HOVER);
});
eventBus.on([
'connectionSegment.move.out',
'connectionSegment.move.cleanup'
], function(event) {
// remove connect marker
// if it was added
var hover = event.context.hover;
if (hover) {
canvas.removeMarker(hover, MARKER_CONNECT_HOVER);
}
});
eventBus.on('connectionSegment.move.cleanup', function(event) {
var context = event.context,
connection = context.connection;
// remove dragger gfx
if (context.draggerGfx) {
remove$1(context.draggerGfx);
}
canvas.removeMarker(connection, MARKER_CONNECT_UPDATING);
});
eventBus.on([
'connectionSegment.move.cancel',
'connectionSegment.move.end'
], function(event) {
var context = event.context,
connection = context.connection;
connection.waypoints = context.originalWaypoints;
redrawConnection(event);
});
eventBus.on('connectionSegment.move.end', function(event) {
var context = event.context,
connection = context.connection,
newWaypoints = context.newWaypoints,
newSegmentStartIndex = context.newSegmentStartIndex;
// ensure we have actual pixel values bendpoint
// coordinates (important when zoom level was > 1 during move)
newWaypoints = newWaypoints.map(function(p) {
return {
original: p.original,
x: Math.round(p.x),
y: Math.round(p.y)
};
});
// apply filter redunant waypoints
var filtered = filterRedundantWaypoints(newWaypoints, newSegmentStartIndex);
// get filtered waypoints
var filteredWaypoints = filtered.waypoints,
croppedWaypoints = cropConnection(connection, filteredWaypoints),
segmentOffset = filtered.segmentOffset;
var hints = {
segmentMove: {
segmentStartIndex: context.segmentStartIndex,
newSegmentStartIndex: newSegmentStartIndex + segmentOffset
}
};
modeling.updateWaypoints(connection, croppedWaypoints, hints);
});
}
ConnectionSegmentMove.$inject = [
'injector',
'eventBus',
'canvas',
'dragging',
'graphicsFactory',
'modeling'
];
var abs$6 = Math.abs,
round$7 = Math.round;
/**
* Snap value to a collection of reference values.
*
* @param {number} value
* @param {Array<number>} values
* @param {number} [tolerance=10]
*
* @return {number} the value we snapped to or null, if none snapped
*/
function snapTo(value, values, tolerance) {
tolerance = tolerance === undefined ? 10 : tolerance;
var idx, snapValue;
for (idx = 0; idx < values.length; idx++) {
snapValue = values[idx];
if (abs$6(snapValue - value) <= tolerance) {
return snapValue;
}
}
}
function topLeft(bounds) {
return {
x: bounds.x,
y: bounds.y
};
}
function bottomRight(bounds) {
return {
x: bounds.x + bounds.width,
y: bounds.y + bounds.height
};
}
function mid$2(bounds, defaultValue) {
if (!bounds || isNaN(bounds.x) || isNaN(bounds.y)) {
return defaultValue;
}
return {
x: round$7(bounds.x + bounds.width / 2),
y: round$7(bounds.y + bounds.height / 2)
};
}
/**
* Retrieve the snap state of the given event.
*
* @param {Event} event
* @param {string} axis
*
* @return {boolean} the snapped state
*
*/
function isSnapped(event, axis) {
var snapped = event.snapped;
if (!snapped) {
return false;
}
if (typeof axis === 'string') {
return snapped[axis];
}
return snapped.x && snapped.y;
}
/**
* Set the given event as snapped.
*
* This method may change the x and/or y position of the shape
* from the given event!
*
* @param {Event} event
* @param {string} axis
* @param {number|boolean} value
*
* @return {number} old value
*/
function setSnapped(event, axis, value) {
if (typeof axis !== 'string') {
throw new Error('axis must be in [x, y]');
}
if (typeof value !== 'number' && value !== false) {
throw new Error('value must be Number or false');
}
var delta,
previousValue = event[axis];
var snapped = event.snapped = (event.snapped || {});
if (value === false) {
snapped[axis] = false;
} else {
snapped[axis] = true;
delta = value - previousValue;
event[axis] += delta;
event['d' + axis] += delta;
}
return previousValue;
}
/**
* Get children of a shape.
*
* @param {djs.model.Shape} parent
*
* @returns {Array<djs.model.Shape|djs.model.Connection>}
*/
function getChildren(parent) {
return parent.children || [];
}
var abs$5 = Math.abs,
round$6 = Math.round;
var TOLERANCE = 10;
function BendpointSnapping(eventBus) {
function snapTo(values, value) {
if (isArray$3(values)) {
var i = values.length;
while (i--) if (abs$5(values[i] - value) <= TOLERANCE) {
return values[i];
}
} else {
values = +values;
var rem = value % values;
if (rem < TOLERANCE) {
return value - rem;
}
if (rem > values - TOLERANCE) {
return value - rem + values;
}
}
return value;
}
function getSnapPoint(element, event) {
if (element.waypoints) {
return getClosestPointOnConnection(event, element);
}
if (element.width) {
return {
x: round$6(element.width / 2 + element.x),
y: round$6(element.height / 2 + element.y)
};
}
}
// connection segment snapping //////////////////////
function getConnectionSegmentSnaps(event) {
var context = event.context,
snapPoints = context.snapPoints,
connection = context.connection,
waypoints = connection.waypoints,
segmentStart = context.segmentStart,
segmentStartIndex = context.segmentStartIndex,
segmentEnd = context.segmentEnd,
segmentEndIndex = context.segmentEndIndex,
axis = context.axis;
if (snapPoints) {
return snapPoints;
}
var referenceWaypoints = [
waypoints[segmentStartIndex - 1],
segmentStart,
segmentEnd,
waypoints[segmentEndIndex + 1]
];
if (segmentStartIndex < 2) {
referenceWaypoints.unshift(getSnapPoint(connection.source, event));
}
if (segmentEndIndex > waypoints.length - 3) {
referenceWaypoints.unshift(getSnapPoint(connection.target, event));
}
context.snapPoints = snapPoints = { horizontal: [] , vertical: [] };
forEach$1(referenceWaypoints, function(p) {
// we snap on existing bendpoints only,
// not placeholders that are inserted during add
if (p) {
p = p.original || p;
if (axis === 'y') {
snapPoints.horizontal.push(p.y);
}
if (axis === 'x') {
snapPoints.vertical.push(p.x);
}
}
});
return snapPoints;
}
eventBus.on('connectionSegment.move.move', 1500, function(event) {
var snapPoints = getConnectionSegmentSnaps(event),
x = event.x,
y = event.y,
sx, sy;
if (!snapPoints) {
return;
}
// snap
sx = snapTo(snapPoints.vertical, x);
sy = snapTo(snapPoints.horizontal, y);
// correction x/y
var cx = (x - sx),
cy = (y - sy);
// update delta
assign(event, {
dx: event.dx - cx,
dy: event.dy - cy,
x: sx,
y: sy
});
// only set snapped if actually snapped
if (cx || snapPoints.vertical.indexOf(x) !== -1) {
setSnapped(event, 'x', sx);
}
if (cy || snapPoints.horizontal.indexOf(y) !== -1) {
setSnapped(event, 'y', sy);
}
});
// bendpoint snapping //////////////////////
function getBendpointSnaps(context) {
var snapPoints = context.snapPoints,
waypoints = context.connection.waypoints,
bendpointIndex = context.bendpointIndex;
if (snapPoints) {
return snapPoints;
}
var referenceWaypoints = [ waypoints[bendpointIndex - 1], waypoints[bendpointIndex + 1] ];
context.snapPoints = snapPoints = { horizontal: [] , vertical: [] };
forEach$1(referenceWaypoints, function(p) {
// we snap on existing bendpoints only,
// not placeholders that are inserted during add
if (p) {
p = p.original || p;
snapPoints.horizontal.push(p.y);
snapPoints.vertical.push(p.x);
}
});
return snapPoints;
}
// Snap Endpoint of new connection
eventBus.on([
'connect.hover',
'connect.move',
'connect.end'
], 1500, function(event) {
var context = event.context,
hover = context.hover,
hoverMid = hover && getSnapPoint(hover, event);
// only snap on connections, elements can have multiple connect endpoints
if (!isConnection$d(hover) || !hoverMid || !hoverMid.x || !hoverMid.y) {
return;
}
setSnapped(event, 'x', hoverMid.x);
setSnapped(event, 'y', hoverMid.y);
});
eventBus.on([ 'bendpoint.move.move', 'bendpoint.move.end' ], 1500, function(event) {
var context = event.context,
snapPoints = getBendpointSnaps(context),
hover = context.hover,
hoverMid = hover && getSnapPoint(hover, event),
x = event.x,
y = event.y,
sx, sy;
if (!snapPoints) {
return;
}
// snap to hover mid
sx = snapTo(hoverMid ? snapPoints.vertical.concat([ hoverMid.x ]) : snapPoints.vertical, x);
sy = snapTo(hoverMid ? snapPoints.horizontal.concat([ hoverMid.y ]) : snapPoints.horizontal, y);
// correction x/y
var cx = (x - sx),
cy = (y - sy);
// update delta
assign(event, {
dx: event.dx - cx,
dy: event.dy - cy,
x: event.x - cx,
y: event.y - cy
});
// only set snapped if actually snapped
if (cx || snapPoints.vertical.indexOf(x) !== -1) {
setSnapped(event, 'x', sx);
}
if (cy || snapPoints.horizontal.indexOf(y) !== -1) {
setSnapped(event, 'y', sy);
}
});
}
BendpointSnapping.$inject = [ 'eventBus' ];
// helpers //////////////////////
function isConnection$d(element) {
return element && !!element.waypoints;
}
var BendpointsModule = {
__depends__: [
DraggingModule,
RulesModule$1
],
__init__: [ 'bendpoints', 'bendpointSnapping', 'bendpointMovePreview' ],
bendpoints: [ 'type', Bendpoints ],
bendpointMove: [ 'type', BendpointMove ],
bendpointMovePreview: [ 'type', BendpointMovePreview ],
connectionSegmentMove: [ 'type', ConnectionSegmentMove ],
bendpointSnapping: [ 'type', BendpointSnapping ]
};
function Connect(eventBus, dragging, modeling, rules) {
// rules
function canConnect(source, target) {
return rules.allowed('connection.create', {
source: source,
target: target
});
}
function canConnectReverse(source, target) {
return canConnect(target, source);
}
// event handlers
eventBus.on('connect.hover', function(event) {
var context = event.context,
start = context.start,
hover = event.hover,
canExecute;
// cache hover state
context.hover = hover;
canExecute = context.canExecute = canConnect(start, hover);
// ignore hover
if (isNil(canExecute)) {
return;
}
if (canExecute !== false) {
context.source = start;
context.target = hover;
return;
}
canExecute = context.canExecute = canConnectReverse(start, hover);
// ignore hover
if (isNil(canExecute)) {
return;
}
if (canExecute !== false) {
context.source = hover;
context.target = start;
}
});
eventBus.on([ 'connect.out', 'connect.cleanup' ], function(event) {
var context = event.context;
context.hover = null;
context.source = null;
context.target = null;
context.canExecute = false;
});
eventBus.on('connect.end', function(event) {
var context = event.context,
canExecute = context.canExecute,
connectionStart = context.connectionStart,
connectionEnd = {
x: event.x,
y: event.y
},
source = context.source,
target = context.target;
if (!canExecute) {
return false;
}
var attrs = null,
hints = {
connectionStart: isReverse$1(context) ? connectionEnd : connectionStart,
connectionEnd: isReverse$1(context) ? connectionStart : connectionEnd
};
if (isObject(canExecute)) {
attrs = canExecute;
}
context.connection = modeling.connect(source, target, attrs, hints);
});
// API
/**
* Start connect operation.
*
* @param {DOMEvent} event
* @param {djs.model.Base} start
* @param {Point} [connectionStart]
* @param {boolean} [autoActivate=false]
*/
this.start = function(event, start, connectionStart, autoActivate) {
if (!isObject(connectionStart)) {
autoActivate = connectionStart;
connectionStart = getMid(start);
}
dragging.init(event, 'connect', {
autoActivate: autoActivate,
data: {
shape: start,
context: {
start: start,
connectionStart: connectionStart
}
}
});
};
}
Connect.$inject = [
'eventBus',
'dragging',
'modeling',
'rules'
];
// helpers //////////
function isReverse$1(context) {
var hover = context.hover,
source = context.source,
target = context.target;
return hover && source && hover === source && source !== target;
}
var HIGH_PRIORITY$h = 1100,
LOW_PRIORITY$j = 900;
var MARKER_OK$3 = 'connect-ok',
MARKER_NOT_OK$3 = 'connect-not-ok';
/**
* Shows connection preview during connect.
*
* @param {didi.Injector} injector
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function ConnectPreview(injector, eventBus, canvas) {
var connectionPreview = injector.get('connectionPreview', false);
connectionPreview && eventBus.on('connect.move', function(event) {
var context = event.context,
canConnect = context.canExecute,
hover = context.hover,
source = context.source,
start = context.start,
startPosition = context.startPosition,
target = context.target,
connectionStart = context.connectionStart || startPosition,
connectionEnd = context.connectionEnd || {
x: event.x,
y: event.y
},
previewStart = connectionStart,
previewEnd = connectionEnd;
if (isReverse$1(context)) {
previewStart = connectionEnd;
previewEnd = connectionStart;
}
connectionPreview.drawPreview(context, canConnect, {
source: source || start,
target: target || hover,
connectionStart: previewStart,
connectionEnd: previewEnd
});
});
eventBus.on('connect.hover', LOW_PRIORITY$j, function(event) {
var context = event.context,
hover = event.hover,
canExecute = context.canExecute;
// ignore hover
if (canExecute === null) {
return;
}
canvas.addMarker(hover, canExecute ? MARKER_OK$3 : MARKER_NOT_OK$3);
});
eventBus.on([
'connect.out',
'connect.cleanup'
], HIGH_PRIORITY$h, function(event) {
var hover = event.hover;
if (hover) {
canvas.removeMarker(hover, MARKER_OK$3);
canvas.removeMarker(hover, MARKER_NOT_OK$3);
}
});
connectionPreview && eventBus.on('connect.cleanup', function(event) {
connectionPreview.cleanUp(event.context);
});
}
ConnectPreview.$inject = [
'injector',
'eventBus',
'canvas'
];
var ConnectModule = {
__depends__: [
SelectionModule,
RulesModule$1,
DraggingModule
],
__init__: [
'connectPreview'
],
connect: [ 'type', Connect ],
connectPreview: [ 'type', ConnectPreview ]
};
var MARKER_CONNECTION_PREVIEW = 'djs-connection-preview';
/**
* Draws connection preview. Optionally, this can use layouter and connection docking to draw
* better looking previews.
*
* @param {didi.Injector} injector
* @param {Canvas} canvas
* @param {GraphicsFactory} graphicsFactory
* @param {ElementFactory} elementFactory
*/
function ConnectionPreview(
injector,
canvas,
graphicsFactory,
elementFactory
) {
this._canvas = canvas;
this._graphicsFactory = graphicsFactory;
this._elementFactory = elementFactory;
// optional components
this._connectionDocking = injector.get('connectionDocking', false);
this._layouter = injector.get('layouter', false);
}
ConnectionPreview.$inject = [
'injector',
'canvas',
'graphicsFactory',
'elementFactory'
];
/**
* Draw connection preview.
*
* Provide at least one of <source, connectionStart> and <target, connectionEnd> to create a preview.
* In the clean up stage, call `connectionPreview#cleanUp` with the context to remove preview.
*
* @param {Object} context
* @param {Object|boolean} canConnect
* @param {Object} hints
* @param {djs.model.shape} [hints.source] source element
* @param {djs.model.shape} [hints.target] target element
* @param {Point} [hints.connectionStart] connection preview start
* @param {Point} [hints.connectionEnd] connection preview end
* @param {Array<Point>} [hints.waypoints] provided waypoints for preview
* @param {boolean} [hints.noLayout] true if preview should not be laid out
* @param {boolean} [hints.noCropping] true if preview should not be cropped
* @param {boolean} [hints.noNoop] true if simple connection should not be drawn
*/
ConnectionPreview.prototype.drawPreview = function(context, canConnect, hints) {
hints = hints || {};
var connectionPreviewGfx = context.connectionPreviewGfx,
getConnection = context.getConnection,
source = hints.source,
target = hints.target,
waypoints = hints.waypoints,
connectionStart = hints.connectionStart,
connectionEnd = hints.connectionEnd,
noLayout = hints.noLayout,
noCropping = hints.noCropping,
noNoop = hints.noNoop,
connection;
var self = this;
if (!connectionPreviewGfx) {
connectionPreviewGfx = context.connectionPreviewGfx = this.createConnectionPreviewGfx();
}
clear(connectionPreviewGfx);
if (!getConnection) {
getConnection = context.getConnection = cacheReturnValues(function(canConnect, source, target) {
return self.getConnection(canConnect, source, target);
});
}
if (canConnect) {
connection = getConnection(canConnect, source, target);
}
if (!connection) {
!noNoop && this.drawNoopPreview(connectionPreviewGfx, hints);
return;
}
connection.waypoints = waypoints || [];
// optional layout
if (this._layouter && !noLayout) {
connection.waypoints = this._layouter.layoutConnection(connection, {
source: source,
target: target,
connectionStart: connectionStart,
connectionEnd: connectionEnd,
waypoints: hints.waypoints || connection.waypoints
});
}
// fallback if no waypoints were provided nor created with layouter
if (!connection.waypoints || !connection.waypoints.length) {
connection.waypoints = [
source ? getMid(source) : connectionStart,
target ? getMid(target) : connectionEnd
];
}
// optional cropping
if (this._connectionDocking && (source || target) && !noCropping) {
connection.waypoints = this._connectionDocking.getCroppedWaypoints(connection, source, target);
}
this._graphicsFactory.drawConnection(connectionPreviewGfx, connection);
};
/**
* Draw simple connection between source and target or provided points.
*
* @param {SVGElement} connectionPreviewGfx container for the connection
* @param {Object} hints
* @param {djs.model.shape} [hints.source] source element
* @param {djs.model.shape} [hints.target] target element
* @param {Point} [hints.connectionStart] required if source is not provided
* @param {Point} [hints.connectionEnd] required if target is not provided
*/
ConnectionPreview.prototype.drawNoopPreview = function(connectionPreviewGfx, hints) {
var source = hints.source,
target = hints.target,
start = hints.connectionStart || getMid(source),
end = hints.connectionEnd || getMid(target);
var waypoints = this.cropWaypoints(start, end, source, target);
var connection = this.createNoopConnection(waypoints[0], waypoints[1]);
append(connectionPreviewGfx, connection);
};
/**
* Return cropped waypoints.
*
* @param {Point} start
* @param {Point} end
* @param {djs.model.shape} source
* @param {djs.model.shape} target
*
* @returns {Array}
*/
ConnectionPreview.prototype.cropWaypoints = function(start, end, source, target) {
var graphicsFactory = this._graphicsFactory,
sourcePath = source && graphicsFactory.getShapePath(source),
targetPath = target && graphicsFactory.getShapePath(target),
connectionPath = graphicsFactory.getConnectionPath({ waypoints: [ start, end ] });
start = (source && getElementLineIntersection(sourcePath, connectionPath, true)) || start;
end = (target && getElementLineIntersection(targetPath, connectionPath, false)) || end;
return [ start, end ];
};
/**
* Remove connection preview container if it exists.
*
* @param {Object} [context]
* @param {SVGElement} [context.connectionPreviewGfx] preview container
*/
ConnectionPreview.prototype.cleanUp = function(context) {
if (context && context.connectionPreviewGfx) {
remove$1(context.connectionPreviewGfx);
}
};
/**
* Get connection that connects source and target.
*
* @param {Object|boolean} canConnect
*
* @returns {djs.model.connection}
*/
ConnectionPreview.prototype.getConnection = function(canConnect) {
var attrs = ensureConnectionAttrs(canConnect);
return this._elementFactory.createConnection(attrs);
};
/**
* Add and return preview graphics.
*
* @returns {SVGElement}
*/
ConnectionPreview.prototype.createConnectionPreviewGfx = function() {
var gfx = create$1('g');
attr(gfx, {
pointerEvents: 'none'
});
classes(gfx).add(MARKER_CONNECTION_PREVIEW);
append(this._canvas.getActiveLayer(), gfx);
return gfx;
};
/**
* Create and return simple connection.
*
* @param {Point} start
* @param {Point} end
*
* @returns {SVGElement}
*/
ConnectionPreview.prototype.createNoopConnection = function(start, end) {
var connection = create$1('polyline');
attr(connection, {
'stroke': '#333',
'strokeDasharray': [ 1 ],
'strokeWidth': 2,
'pointer-events': 'none'
});
attr(connection, { 'points': [ start.x, start.y, end.x, end.y ] });
return connection;
};
// helpers //////////
/**
* Returns function that returns cached return values referenced by stringified first argument.
*
* @param {Function} fn
*
* @return {Function}
*/
function cacheReturnValues(fn) {
var returnValues = {};
/**
* Return cached return value referenced by stringified first argument.
*
* @returns {*}
*/
return function(firstArgument) {
var key = JSON.stringify(firstArgument);
var returnValue = returnValues[key];
if (!returnValue) {
returnValue = returnValues[key] = fn.apply(null, arguments);
}
return returnValue;
};
}
/**
* Ensure connection attributes is object.
*
* @param {Object|boolean} canConnect
*
* @returns {Object}
*/
function ensureConnectionAttrs(canConnect) {
if (isObject(canConnect)) {
return canConnect;
} else {
return {};
}
}
var ConnectionPreviewModule = {
__init__: [ 'connectionPreview' ],
connectionPreview: [ 'type', ConnectionPreview ]
};
var min$3 = Math.min,
max$5 = Math.max;
function preventDefault(e) {
e.preventDefault();
}
function stopPropagation(e) {
e.stopPropagation();
}
function isTextNode(node) {
return node.nodeType === Node.TEXT_NODE;
}
function toArray(nodeList) {
return [].slice.call(nodeList);
}
/**
* Initializes a container for a content editable div.
*
* Structure:
*
* container
* parent
* content
* resize-handle
*
* @param {object} options
* @param {DOMElement} options.container The DOM element to append the contentContainer to
* @param {Function} options.keyHandler Handler for key events
* @param {Function} options.resizeHandler Handler for resize events
*/
function TextBox(options) {
this.container = options.container;
this.parent = domify(
'<div class="djs-direct-editing-parent">' +
'<div class="djs-direct-editing-content" contenteditable="true"></div>' +
'</div>'
);
this.content = query('[contenteditable]', this.parent);
this.keyHandler = options.keyHandler || function() {};
this.resizeHandler = options.resizeHandler || function() {};
this.autoResize = bind(this.autoResize, this);
this.handlePaste = bind(this.handlePaste, this);
}
/**
* Create a text box with the given position, size, style and text content
*
* @param {Object} bounds
* @param {Number} bounds.x absolute x position
* @param {Number} bounds.y absolute y position
* @param {Number} [bounds.width] fixed width value
* @param {Number} [bounds.height] fixed height value
* @param {Number} [bounds.maxWidth] maximum width value
* @param {Number} [bounds.maxHeight] maximum height value
* @param {Number} [bounds.minWidth] minimum width value
* @param {Number} [bounds.minHeight] minimum height value
* @param {Object} [style]
* @param {String} value text content
*
* @return {DOMElement} The created content DOM element
*/
TextBox.prototype.create = function(bounds, style, value, options) {
var self = this;
var parent = this.parent,
content = this.content,
container = this.container;
options = this.options = options || {};
style = this.style = style || {};
var parentStyle = pick(style, [
'width',
'height',
'maxWidth',
'maxHeight',
'minWidth',
'minHeight',
'left',
'top',
'backgroundColor',
'position',
'overflow',
'border',
'wordWrap',
'textAlign',
'outline',
'transform'
]);
assign(parent.style, {
width: bounds.width + 'px',
height: bounds.height + 'px',
maxWidth: bounds.maxWidth + 'px',
maxHeight: bounds.maxHeight + 'px',
minWidth: bounds.minWidth + 'px',
minHeight: bounds.minHeight + 'px',
left: bounds.x + 'px',
top: bounds.y + 'px',
backgroundColor: '#ffffff',
position: 'absolute',
overflow: 'visible',
border: '1px solid #ccc',
boxSizing: 'border-box',
wordWrap: 'normal',
textAlign: 'center',
outline: 'none'
}, parentStyle);
var contentStyle = pick(style, [
'fontFamily',
'fontSize',
'fontWeight',
'lineHeight',
'padding',
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft'
]);
assign(content.style, {
boxSizing: 'border-box',
width: '100%',
outline: 'none',
wordWrap: 'break-word'
}, contentStyle);
if (options.centerVertically) {
assign(content.style, {
position: 'absolute',
top: '50%',
transform: 'translate(0, -50%)'
}, contentStyle);
}
content.innerText = value;
componentEvent.bind(content, 'keydown', this.keyHandler);
componentEvent.bind(content, 'mousedown', stopPropagation);
componentEvent.bind(content, 'paste', self.handlePaste);
if (options.autoResize) {
componentEvent.bind(content, 'input', this.autoResize);
}
if (options.resizable) {
this.resizable(style);
}
container.appendChild(parent);
// set selection to end of text
this.setSelection(content.lastChild, content.lastChild && content.lastChild.length);
return parent;
};
/**
* Intercept paste events to remove formatting from pasted text.
*/
TextBox.prototype.handlePaste = function(e) {
var options = this.options,
style = this.style;
e.preventDefault();
var text;
if (e.clipboardData) {
// Chrome, Firefox, Safari
text = e.clipboardData.getData('text/plain');
} else {
// Internet Explorer
text = window.clipboardData.getData('Text');
}
this.insertText(text);
if (options.autoResize) {
var hasResized = this.autoResize(style);
if (hasResized) {
this.resizeHandler(hasResized);
}
}
};
TextBox.prototype.insertText = function(text) {
text = normalizeEndOfLineSequences(text);
// insertText command not supported by Internet Explorer
var success = document.execCommand('insertText', false, text);
if (success) {
return;
}
this._insertTextIE(text);
};
TextBox.prototype._insertTextIE = function(text) {
// Internet Explorer
var range = this.getSelection(),
startContainer = range.startContainer,
endContainer = range.endContainer,
startOffset = range.startOffset,
endOffset = range.endOffset,
commonAncestorContainer = range.commonAncestorContainer;
var childNodesArray = toArray(commonAncestorContainer.childNodes);
var container,
offset;
if (isTextNode(commonAncestorContainer)) {
var containerTextContent = startContainer.textContent;
startContainer.textContent =
containerTextContent.substring(0, startOffset)
+ text
+ containerTextContent.substring(endOffset);
container = startContainer;
offset = startOffset + text.length;
} else if (startContainer === this.content && endContainer === this.content) {
var textNode = document.createTextNode(text);
this.content.insertBefore(textNode, childNodesArray[startOffset]);
container = textNode;
offset = textNode.textContent.length;
} else {
var startContainerChildIndex = childNodesArray.indexOf(startContainer),
endContainerChildIndex = childNodesArray.indexOf(endContainer);
childNodesArray.forEach(function(childNode, index) {
if (index === startContainerChildIndex) {
childNode.textContent =
startContainer.textContent.substring(0, startOffset) +
text +
endContainer.textContent.substring(endOffset);
} else if (index > startContainerChildIndex && index <= endContainerChildIndex) {
remove$2(childNode);
}
});
container = startContainer;
offset = startOffset + text.length;
}
if (container && offset !== undefined) {
// is necessary in Internet Explorer
setTimeout(function() {
self.setSelection(container, offset);
});
}
};
/**
* Automatically resize element vertically to fit its content.
*/
TextBox.prototype.autoResize = function() {
var parent = this.parent,
content = this.content;
var fontSize = parseInt(this.style.fontSize) || 12;
if (content.scrollHeight > parent.offsetHeight ||
content.scrollHeight < parent.offsetHeight - fontSize) {
var bounds = parent.getBoundingClientRect();
var height = content.scrollHeight;
parent.style.height = height + 'px';
this.resizeHandler({
width: bounds.width,
height: bounds.height,
dx: 0,
dy: height - bounds.height
});
}
};
/**
* Make an element resizable by adding a resize handle.
*/
TextBox.prototype.resizable = function() {
var self = this;
var parent = this.parent,
resizeHandle = this.resizeHandle;
var minWidth = parseInt(this.style.minWidth) || 0,
minHeight = parseInt(this.style.minHeight) || 0,
maxWidth = parseInt(this.style.maxWidth) || Infinity,
maxHeight = parseInt(this.style.maxHeight) || Infinity;
if (!resizeHandle) {
resizeHandle = this.resizeHandle = domify(
'<div class="djs-direct-editing-resize-handle"></div>'
);
var startX, startY, startWidth, startHeight;
var onMouseDown = function(e) {
preventDefault(e);
stopPropagation(e);
startX = e.clientX;
startY = e.clientY;
var bounds = parent.getBoundingClientRect();
startWidth = bounds.width;
startHeight = bounds.height;
componentEvent.bind(document, 'mousemove', onMouseMove);
componentEvent.bind(document, 'mouseup', onMouseUp);
};
var onMouseMove = function(e) {
preventDefault(e);
stopPropagation(e);
var newWidth = min$3(max$5(startWidth + e.clientX - startX, minWidth), maxWidth);
var newHeight = min$3(max$5(startHeight + e.clientY - startY, minHeight), maxHeight);
parent.style.width = newWidth + 'px';
parent.style.height = newHeight + 'px';
self.resizeHandler({
width: startWidth,
height: startHeight,
dx: e.clientX - startX,
dy: e.clientY - startY
});
};
var onMouseUp = function(e) {
preventDefault(e);
stopPropagation(e);
componentEvent.unbind(document,'mousemove', onMouseMove, false);
componentEvent.unbind(document, 'mouseup', onMouseUp, false);
};
componentEvent.bind(resizeHandle, 'mousedown', onMouseDown);
}
assign(resizeHandle.style, {
position: 'absolute',
bottom: '0px',
right: '0px',
cursor: 'nwse-resize',
width: '0',
height: '0',
borderTop: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent',
borderRight: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc',
borderBottom: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc',
borderLeft: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent'
});
parent.appendChild(resizeHandle);
};
/**
* Clear content and style of the textbox, unbind listeners and
* reset CSS style.
*/
TextBox.prototype.destroy = function() {
var parent = this.parent,
content = this.content,
resizeHandle = this.resizeHandle;
// clear content
content.innerText = '';
// clear styles
parent.removeAttribute('style');
content.removeAttribute('style');
componentEvent.unbind(content, 'keydown', this.keyHandler);
componentEvent.unbind(content, 'mousedown', stopPropagation);
componentEvent.unbind(content, 'input', this.autoResize);
componentEvent.unbind(content, 'paste', this.handlePaste);
if (resizeHandle) {
resizeHandle.removeAttribute('style');
remove$2(resizeHandle);
}
remove$2(parent);
};
TextBox.prototype.getValue = function() {
return this.content.innerText.trim();
};
TextBox.prototype.getSelection = function() {
var selection = window.getSelection(),
range = selection.getRangeAt(0);
return range;
};
TextBox.prototype.setSelection = function(container, offset) {
var range = document.createRange();
if (container === null) {
range.selectNodeContents(this.content);
} else {
range.setStart(container, offset);
range.setEnd(container, offset);
}
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
};
// helpers //////////
function normalizeEndOfLineSequences(string) {
return string.replace(/\r\n|\r|\n/g, '\n');
}
/**
* A direct editing component that allows users
* to edit an elements text directly in the diagram
*
* @param {EventBus} eventBus the event bus
*/
function DirectEditing(eventBus, canvas) {
this._eventBus = eventBus;
this._providers = [];
this._textbox = new TextBox({
container: canvas.getContainer(),
keyHandler: bind(this._handleKey, this),
resizeHandler: bind(this._handleResize, this)
});
}
DirectEditing.$inject = [ 'eventBus', 'canvas' ];
/**
* Register a direct editing provider
* @param {Object} provider the provider, must expose an #activate(element) method that returns
* an activation context ({ bounds: {x, y, width, height }, text }) if
* direct editing is available for the given element.
* Additionally the provider must expose a #update(element, value) method
* to receive direct editing updates.
*/
DirectEditing.prototype.registerProvider = function(provider) {
this._providers.push(provider);
};
/**
* Returns true if direct editing is currently active
*
* @param {djs.model.Base} [element]
*
* @return {boolean}
*/
DirectEditing.prototype.isActive = function(element) {
return !!(this._active && (!element || this._active.element === element));
};
/**
* Cancel direct editing, if it is currently active
*/
DirectEditing.prototype.cancel = function() {
if (!this._active) {
return;
}
this._fire('cancel');
this.close();
};
DirectEditing.prototype._fire = function(event, context) {
this._eventBus.fire('directEditing.' + event, context || { active: this._active });
};
DirectEditing.prototype.close = function() {
this._textbox.destroy();
this._fire('deactivate');
this._active = null;
this.resizable = undefined;
};
DirectEditing.prototype.complete = function() {
var active = this._active;
if (!active) {
return;
}
var containerBounds,
previousBounds = active.context.bounds,
newBounds = this.$textbox.getBoundingClientRect(),
newText = this.getValue(),
previousText = active.context.text;
if (
newText !== previousText ||
newBounds.height !== previousBounds.height ||
newBounds.width !== previousBounds.width
) {
containerBounds = this._textbox.container.getBoundingClientRect();
active.provider.update(active.element, newText, active.context.text, {
x: newBounds.left - containerBounds.left,
y: newBounds.top - containerBounds.top,
width: newBounds.width,
height: newBounds.height
});
}
this._fire('complete');
this.close();
};
DirectEditing.prototype.getValue = function() {
return this._textbox.getValue();
};
DirectEditing.prototype._handleKey = function(e) {
// stop bubble
e.stopPropagation();
var key = e.keyCode || e.charCode;
// ESC
if (key === 27) {
e.preventDefault();
return this.cancel();
}
// Enter
if (key === 13 && !e.shiftKey) {
e.preventDefault();
return this.complete();
}
};
DirectEditing.prototype._handleResize = function(event) {
this._fire('resize', event);
};
/**
* Activate direct editing on the given element
*
* @param {Object} ElementDescriptor the descriptor for a shape or connection
* @return {Boolean} true if the activation was possible
*/
DirectEditing.prototype.activate = function(element) {
if (this.isActive()) {
this.cancel();
}
// the direct editing context
var context;
var provider = find(this._providers, function(p) {
return (context = p.activate(element)) ? p : null;
});
// check if activation took place
if (context) {
this.$textbox = this._textbox.create(
context.bounds,
context.style,
context.text,
context.options
);
this._active = {
element: element,
context: context,
provider: provider
};
if (context.options && context.options.resizable) {
this.resizable = true;
}
this._fire('activate');
}
return !!context;
};
var DirectEditingModule = {
__depends__: [
InteractionEventsModule$1
],
__init__: [ 'directEditing' ],
directEditing: [ 'type', DirectEditing ]
};
var MARKER_TYPES = [
'marker-start',
'marker-mid',
'marker-end'
];
var NODES_CAN_HAVE_MARKER = [
'circle',
'ellipse',
'line',
'path',
'polygon',
'polyline',
'rect'
];
/**
* Adds support for previews of moving/resizing elements.
*/
function PreviewSupport(elementRegistry, eventBus, canvas, styles) {
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._styles = styles;
this._clonedMarkers = {};
var self = this;
eventBus.on('drag.cleanup', function() {
forEach$1(self._clonedMarkers, function(clonedMarker) {
remove$1(clonedMarker);
});
self._clonedMarkers = {};
});
}
PreviewSupport.$inject = [
'elementRegistry',
'eventBus',
'canvas',
'styles'
];
/**
* Returns graphics of an element.
*
* @param {djs.model.Base} element
*
* @return {SVGElement}
*/
PreviewSupport.prototype.getGfx = function(element) {
return this._elementRegistry.getGraphics(element);
};
/**
* Adds a move preview of a given shape to a given svg group.
*
* @param {djs.model.Base} element
* @param {SVGElement} group
* @param {SVGElement} [gfx]
*
* @return {SVGElement} dragger
*/
PreviewSupport.prototype.addDragger = function(element, group, gfx) {
gfx = gfx || this.getGfx(element);
var dragger = clone$1(gfx);
var bbox = gfx.getBoundingClientRect();
this._cloneMarkers(getVisual(dragger));
attr(dragger, this._styles.cls('djs-dragger', [], {
x: bbox.top,
y: bbox.left
}));
append(group, dragger);
return dragger;
};
/**
* Adds a resize preview of a given shape to a given svg group.
*
* @param {djs.model.Base} element
* @param {SVGElement} group
*
* @return {SVGElement} frame
*/
PreviewSupport.prototype.addFrame = function(shape, group) {
var frame = create$1('rect', {
class: 'djs-resize-overlay',
width: shape.width,
height: shape.height,
x: shape.x,
y: shape.y
});
append(group, frame);
return frame;
};
/**
* Clone all markers referenced by a node and its child nodes.
*
* @param {SVGElement} gfx
*/
PreviewSupport.prototype._cloneMarkers = function(gfx) {
var self = this;
if (gfx.childNodes) {
// TODO: use forEach once we drop PhantomJS
for (var i = 0; i < gfx.childNodes.length; i++) {
// recursively clone markers of child nodes
self._cloneMarkers(gfx.childNodes[ i ]);
}
}
if (!canHaveMarker(gfx)) {
return;
}
MARKER_TYPES.forEach(function(markerType) {
if (attr(gfx, markerType)) {
var marker = getMarker(gfx, markerType, self._canvas.getContainer());
self._cloneMarker(gfx, marker, markerType);
}
});
};
/**
* Clone marker referenced by an element.
*
* @param {SVGElement} gfx
* @param {SVGElement} marker
* @param {string} markerType
*/
PreviewSupport.prototype._cloneMarker = function(gfx, marker, markerType) {
var markerId = marker.id;
var clonedMarker = this._clonedMarkers[ markerId ];
if (!clonedMarker) {
clonedMarker = clone$1(marker);
var clonedMarkerId = markerId + '-clone';
clonedMarker.id = clonedMarkerId;
classes(clonedMarker)
.add('djs-dragger')
.add('djs-dragger-marker');
this._clonedMarkers[ markerId ] = clonedMarker;
var defs = query('defs', this._canvas._svg);
if (!defs) {
defs = create$1('defs');
append(this._canvas._svg, defs);
}
append(defs, clonedMarker);
}
var reference = idToReference(this._clonedMarkers[ markerId ].id);
attr(gfx, markerType, reference);
};
// helpers //////////
/**
* Get marker of given type referenced by node.
*
* @param {Node} node
* @param {string} markerType
* @param {Node} [parentNode]
*
* @param {Node}
*/
function getMarker(node, markerType, parentNode) {
var id = referenceToId(attr(node, markerType));
return query('marker#' + id, parentNode || document);
}
/**
* Get ID of fragment within current document from its functional IRI reference.
* References may use single or double quotes.
*
* @param {string} reference
*
* @returns {string}
*/
function referenceToId(reference) {
return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1];
}
/**
* Get functional IRI reference for given ID of fragment within current document.
*
* @param {string} id
*
* @returns {string}
*/
function idToReference(id) {
return 'url(#' + id + ')';
}
/**
* Check wether node type can have marker attributes.
*
* @param {Node} node
*
* @returns {boolean}
*/
function canHaveMarker(node) {
return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1;
}
var PreviewSupportModule = {
__init__: [ 'previewSupport' ],
previewSupport: [ 'type', PreviewSupport ]
};
var MARKER_OK$2 = 'drop-ok',
MARKER_NOT_OK$2 = 'drop-not-ok',
MARKER_ATTACH$2 = 'attach-ok',
MARKER_NEW_PARENT$1 = 'new-parent';
var PREFIX = 'create';
var HIGH_PRIORITY$g = 2000;
/**
* Create new elements through drag and drop.
*
* @param {Canvas} canvas
* @param {Dragging} dragging
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {Rules} rules
*/
function Create(
canvas,
dragging,
eventBus,
modeling,
rules
) {
// rules //////////
/**
* Check wether elements can be created.
*
* @param {Array<djs.model.Base>} elements
* @param {djs.model.Base} target
* @param {Point} position
* @param {djs.model.Base} [source]
*
* @returns {boolean|null|Object}
*/
function canCreate(elements, target, position, source, hints) {
if (!target) {
return false;
}
// ignore child elements and external labels
elements = filter(elements, function(element) {
var labelTarget = element.labelTarget;
return !element.parent && !(isLabel$5(element) && elements.indexOf(labelTarget) !== -1);
});
var shape = find(elements, function(element) {
return !isConnection$c(element);
});
var attach = false,
connect = false,
create = false;
// (1) attaching single shapes
if (isSingleShape(elements)) {
attach = rules.allowed('shape.attach', {
position: position,
shape: shape,
target: target
});
}
if (!attach) {
// (2) creating elements
if (isSingleShape(elements)) {
create = rules.allowed('shape.create', {
position: position,
shape: shape,
source: source,
target: target
});
} else {
create = rules.allowed('elements.create', {
elements: elements,
position: position,
target: target
});
}
}
var connectionTarget = hints.connectionTarget;
// (3) appending single shapes
if (create || attach) {
if (shape && source) {
connect = rules.allowed('connection.create', {
source: connectionTarget === source ? shape : source,
target: connectionTarget === source ? source : shape,
hints: {
targetParent: target,
targetAttach: attach
}
});
}
return {
attach: attach,
connect: connect
};
}
// ignore wether or not elements can be created
if (create === null || attach === null) {
return null;
}
return false;
}
function setMarker(element, marker) {
[ MARKER_ATTACH$2, MARKER_OK$2, MARKER_NOT_OK$2, MARKER_NEW_PARENT$1 ].forEach(function(m) {
if (m === marker) {
canvas.addMarker(element, m);
} else {
canvas.removeMarker(element, m);
}
});
}
// event handling //////////
eventBus.on([ 'create.move', 'create.hover' ], function(event) {
var context = event.context,
elements = context.elements,
hover = event.hover,
source = context.source,
hints = context.hints || {};
if (!hover) {
context.canExecute = false;
context.target = null;
return;
}
ensureConstraints$2(event);
var position = {
x: event.x,
y: event.y
};
var canExecute = context.canExecute = hover && canCreate(elements, hover, position, source, hints);
if (hover && canExecute !== null) {
context.target = hover;
if (canExecute && canExecute.attach) {
setMarker(hover, MARKER_ATTACH$2);
} else {
setMarker(hover, canExecute ? MARKER_NEW_PARENT$1 : MARKER_NOT_OK$2);
}
}
});
eventBus.on([ 'create.end', 'create.out', 'create.cleanup' ], function(event) {
var hover = event.hover;
if (hover) {
setMarker(hover, null);
}
});
eventBus.on('create.end', function(event) {
var context = event.context,
source = context.source,
shape = context.shape,
elements = context.elements,
target = context.target,
canExecute = context.canExecute,
attach = canExecute && canExecute.attach,
connect = canExecute && canExecute.connect,
hints = context.hints || {};
if (canExecute === false || !target) {
return false;
}
ensureConstraints$2(event);
var position = {
x: event.x,
y: event.y
};
if (connect) {
shape = modeling.appendShape(source, shape, position, target, {
attach: attach,
connection: connect === true ? {} : connect,
connectionTarget: hints.connectionTarget
});
} else {
elements = modeling.createElements(elements, position, target, assign({}, hints, {
attach: attach
}));
// update shape
shape = find(elements, function(element) {
return !isConnection$c(element);
});
}
// update elements and shape
assign(context, {
elements: elements,
shape: shape
});
assign(event, {
elements: elements,
shape: shape
});
});
function cancel() {
var context = dragging.context();
if (context && context.prefix === PREFIX) {
dragging.cancel();
}
}
// cancel on <elements.changed> that is not result of <drag.end>
eventBus.on('create.init', function() {
eventBus.on('elements.changed', cancel);
eventBus.once([ 'create.cancel', 'create.end' ], HIGH_PRIORITY$g, function() {
eventBus.off('elements.changed', cancel);
});
});
// API //////////
this.start = function(event, elements, context) {
if (!isArray$3(elements)) {
elements = [ elements ];
}
var shape = find(elements, function(element) {
return !isConnection$c(element);
});
if (!shape) {
// at least one shape is required
return;
}
context = assign({
elements: elements,
hints: {},
shape: shape
}, context || {});
// make sure each element has x and y
forEach$1(elements, function(element) {
if (!isNumber(element.x)) {
element.x = 0;
}
if (!isNumber(element.y)) {
element.y = 0;
}
});
var visibleElements = filter(elements, function(element) {
return !element.hidden;
});
var bbox = getBBox(visibleElements);
// center elements around cursor
forEach$1(elements, function(element) {
if (isConnection$c(element)) {
element.waypoints = map(element.waypoints, function(waypoint) {
return {
x: waypoint.x - bbox.x - bbox.width / 2,
y: waypoint.y - bbox.y - bbox.height / 2
};
});
}
assign(element, {
x: element.x - bbox.x - bbox.width / 2,
y: element.y - bbox.y - bbox.height / 2
});
});
dragging.init(event, PREFIX, {
cursor: 'grabbing',
autoActivate: true,
data: {
shape: shape,
elements: elements,
context: context
}
});
};
}
Create.$inject = [
'canvas',
'dragging',
'eventBus',
'modeling',
'rules'
];
// helpers //////////
function ensureConstraints$2(event) {
var context = event.context,
createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
if (createConstraints.left) {
event.x = Math.max(event.x, createConstraints.left);
}
if (createConstraints.right) {
event.x = Math.min(event.x, createConstraints.right);
}
if (createConstraints.top) {
event.y = Math.max(event.y, createConstraints.top);
}
if (createConstraints.bottom) {
event.y = Math.min(event.y, createConstraints.bottom);
}
}
function isConnection$c(element) {
return !!element.waypoints;
}
function isSingleShape(elements) {
return elements && elements.length === 1 && !isConnection$c(elements[0]);
}
function isLabel$5(element) {
return !!element.labelTarget;
}
var LOW_PRIORITY$i = 750;
function CreatePreview(
canvas,
eventBus,
graphicsFactory,
previewSupport,
styles
) {
function createDragGroup(elements) {
var dragGroup = create$1('g');
attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));
var childrenGfx = create$1('g');
elements.forEach(function(element) {
// create graphics
var gfx;
if (element.hidden) {
return;
}
if (element.waypoints) {
gfx = graphicsFactory._createContainer('connection', childrenGfx);
graphicsFactory.drawConnection(getVisual(gfx), element);
} else {
gfx = graphicsFactory._createContainer('shape', childrenGfx);
graphicsFactory.drawShape(getVisual(gfx), element);
translate$2(gfx, element.x, element.y);
}
// add preview
previewSupport.addDragger(element, dragGroup, gfx);
});
return dragGroup;
}
eventBus.on('create.move', LOW_PRIORITY$i, function(event) {
var hover = event.hover,
context = event.context,
elements = context.elements,
dragGroup = context.dragGroup;
// lazily create previews
if (!dragGroup) {
dragGroup = context.dragGroup = createDragGroup(elements);
}
var activeLayer;
if (hover) {
if (!dragGroup.parentNode) {
activeLayer = canvas.getActiveLayer();
append(activeLayer, dragGroup);
}
translate$2(dragGroup, event.x, event.y);
} else {
remove$1(dragGroup);
}
});
eventBus.on('create.cleanup', function(event) {
var context = event.context,
dragGroup = context.dragGroup;
if (dragGroup) {
remove$1(dragGroup);
}
});
}
CreatePreview.$inject = [
'canvas',
'eventBus',
'graphicsFactory',
'previewSupport',
'styles'
];
var CreateModule = {
__depends__: [
DraggingModule,
PreviewSupportModule,
RulesModule$1,
SelectionModule
],
__init__: [
'create',
'createPreview'
],
create: [ 'type', Create ],
createPreview: [ 'type', CreatePreview ]
};
/**
* A clip board stub
*/
function Clipboard() {}
Clipboard.prototype.get = function() {
return this._data;
};
Clipboard.prototype.set = function(data) {
this._data = data;
};
Clipboard.prototype.clear = function() {
var data = this._data;
delete this._data;
return data;
};
Clipboard.prototype.isEmpty = function() {
return !this._data;
};
var ClipboardModule = {
clipboard: [ 'type', Clipboard ]
};
function Mouse(eventBus) {
var self = this;
this._lastMoveEvent = null;
function setLastMoveEvent(mousemoveEvent) {
self._lastMoveEvent = mousemoveEvent;
}
eventBus.on('canvas.init', function(context) {
var svg = self._svg = context.svg;
svg.addEventListener('mousemove', setLastMoveEvent);
});
eventBus.on('canvas.destroy', function() {
self._lastMouseEvent = null;
self._svg.removeEventListener('mousemove', setLastMoveEvent);
});
}
Mouse.$inject = [ 'eventBus' ];
Mouse.prototype.getLastMoveEvent = function() {
return this._lastMoveEvent || createMoveEvent(0, 0);
};
// helpers //////////
function createMoveEvent(x, y) {
var event = document.createEvent('MouseEvent');
var screenX = x,
screenY = y,
clientX = x,
clientY = y;
if (event.initMouseEvent) {
event.initMouseEvent(
'mousemove',
true,
true,
window,
0,
screenX,
screenY,
clientX,
clientY,
false,
false,
false,
false,
0,
null
);
}
return event;
}
var MouseModule = {
__init__: [ 'mouse' ],
mouse: [ 'type', Mouse ]
};
/**
* @typedef {Function} <copyPaste.canCopyElements> listener
*
* @param {Object} context
* @param {Array<djs.model.Base>} context.elements
*
* @returns {Array<djs.model.Base>|boolean} - Return elements to be copied or false to disallow
* copying.
*/
/**
* @typedef {Function} <copyPaste.copyElement> listener
*
* @param {Object} context
* @param {Object} context.descriptor
* @param {djs.model.Base} context.element
* @param {Array<djs.model.Base>} context.elements
*/
/**
* @typedef {Function} <copyPaste.createTree> listener
*
* @param {Object} context
* @param {djs.model.Base} context.element
* @param {Array<djs.model.Base>} context.children - Add children to be added to tree.
*/
/**
* @typedef {Function} <copyPaste.elementsCopied> listener
*
* @param {Object} context
* @param {Object} context.elements
* @param {Object} context.tree
*/
/**
* @typedef {Function} <copyPaste.pasteElement> listener
*
* @param {Object} context
* @param {Object} context.cache - Already created elements.
* @param {Object} context.descriptor
*/
/**
* @typedef {Function} <copyPaste.pasteElements> listener
*
* @param {Object} context
* @param {Object} context.hints - Add hints before pasting.
*/
/**
* Copy and paste elements.
*
* @param {Canvas} canvas
* @param {Create} create
* @param {Clipboard} clipboard
* @param {ElementFactory} elementFactory
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {Mouse} mouse
* @param {Rules} rules
*/
function CopyPaste(
canvas,
create,
clipboard,
elementFactory,
eventBus,
modeling,
mouse,
rules
) {
this._canvas = canvas;
this._create = create;
this._clipboard = clipboard;
this._elementFactory = elementFactory;
this._eventBus = eventBus;
this._modeling = modeling;
this._mouse = mouse;
this._rules = rules;
eventBus.on('copyPaste.copyElement', function(context) {
var descriptor = context.descriptor,
element = context.element,
elements = context.elements;
// default priority (priority = 1)
descriptor.priority = 1;
descriptor.id = element.id;
var parentCopied = find(elements, function(e) {
return e === element.parent;
});
// do NOT reference parent if parent wasn't copied
if (parentCopied) {
descriptor.parent = element.parent.id;
}
// attachers (priority = 2)
if (isAttacher$1(element)) {
descriptor.priority = 2;
descriptor.host = element.host.id;
}
// connections (priority = 3)
if (isConnection$b(element)) {
descriptor.priority = 3;
descriptor.source = element.source.id;
descriptor.target = element.target.id;
descriptor.waypoints = copyWaypoints$1(element);
}
// labels (priority = 4)
if (isLabel$4(element)) {
descriptor.priority = 4;
descriptor.labelTarget = element.labelTarget.id;
}
forEach$1([ 'x', 'y', 'width', 'height' ], function(property) {
if (isNumber(element[ property ])) {
descriptor[ property ] = element[ property ];
}
});
descriptor.hidden = element.hidden;
descriptor.collapsed = element.collapsed;
});
eventBus.on('copyPaste.pasteElements', function(context) {
var hints = context.hints;
assign(hints, {
createElementsBehavior: false
});
});
}
CopyPaste.$inject = [
'canvas',
'create',
'clipboard',
'elementFactory',
'eventBus',
'modeling',
'mouse',
'rules'
];
/**
* Copy elements.
*
* @param {Array<djs.model.Base>} elements
*
* @returns {Object}
*/
CopyPaste.prototype.copy = function(elements) {
var allowed,
tree;
if (!isArray$3(elements)) {
elements = elements ? [ elements ] : [];
}
allowed = this._eventBus.fire('copyPaste.canCopyElements', {
elements: elements
});
if (allowed === false) {
tree = {};
} else {
tree = this.createTree(isArray$3(allowed) ? allowed : elements);
}
// we set an empty tree, selection of elements
// to copy was empty.
this._clipboard.set(tree);
this._eventBus.fire('copyPaste.elementsCopied', {
elements: elements,
tree: tree
});
return tree;
};
/**
* Paste elements.
*
* @param {Object} [context]
* @param {djs.model.base} [context.element] - Parent.
* @param {Point} [context.point] - Position.
* @param {Object} [context.hints] - Hints.
*/
CopyPaste.prototype.paste = function(context) {
var tree = this._clipboard.get();
if (this._clipboard.isEmpty()) {
return;
}
var hints = context && context.hints || {};
this._eventBus.fire('copyPaste.pasteElements', {
hints: hints
});
var elements = this._createElements(tree);
// paste directly
if (context && context.element && context.point) {
return this._paste(elements, context.element, context.point, hints);
}
this._create.start(this._mouse.getLastMoveEvent(), elements, {
hints: hints || {}
});
};
/**
* Paste elements directly.
*
* @param {Array<djs.model.Base>} elements
* @param {djs.model.base} target
* @param {Point} position
* @param {Object} [hints]
*/
CopyPaste.prototype._paste = function(elements, target, position, hints) {
// make sure each element has x and y
forEach$1(elements, function(element) {
if (!isNumber(element.x)) {
element.x = 0;
}
if (!isNumber(element.y)) {
element.y = 0;
}
});
var bbox = getBBox(elements);
// center elements around cursor
forEach$1(elements, function(element) {
if (isConnection$b(element)) {
element.waypoints = map(element.waypoints, function(waypoint) {
return {
x: waypoint.x - bbox.x - bbox.width / 2,
y: waypoint.y - bbox.y - bbox.height / 2
};
});
}
assign(element, {
x: element.x - bbox.x - bbox.width / 2,
y: element.y - bbox.y - bbox.height / 2
});
});
return this._modeling.createElements(elements, position, target, assign({}, hints));
};
/**
* Create elements from tree.
*/
CopyPaste.prototype._createElements = function(tree) {
var self = this;
var eventBus = this._eventBus;
var cache = {};
var elements = [];
forEach$1(tree, function(branch, depth) {
// sort by priority
branch = sortBy(branch, 'priority');
forEach$1(branch, function(descriptor) {
// remove priority
var attrs = assign({}, omit(descriptor, [ 'priority' ]));
if (cache[ descriptor.parent ]) {
attrs.parent = cache[ descriptor.parent ];
} else {
delete attrs.parent;
}
eventBus.fire('copyPaste.pasteElement', {
cache: cache,
descriptor: attrs
});
var element;
if (isConnection$b(attrs)) {
attrs.source = cache[ descriptor.source ];
attrs.target = cache[ descriptor.target ];
element = cache[ descriptor.id ] = self.createConnection(attrs);
elements.push(element);
return;
}
if (isLabel$4(attrs)) {
attrs.labelTarget = cache[ attrs.labelTarget ];
element = cache[ descriptor.id ] = self.createLabel(attrs);
elements.push(element);
return;
}
if (attrs.host) {
attrs.host = cache[ attrs.host ];
}
element = cache[ descriptor.id ] = self.createShape(attrs);
elements.push(element);
});
});
return elements;
};
CopyPaste.prototype.createConnection = function(attrs) {
var connection = this._elementFactory.createConnection(omit(attrs, [ 'id' ]));
return connection;
};
CopyPaste.prototype.createLabel = function(attrs) {
var label = this._elementFactory.createLabel(omit(attrs, [ 'id' ]));
return label;
};
CopyPaste.prototype.createShape = function(attrs) {
var shape = this._elementFactory.createShape(omit(attrs, [ 'id' ]));
return shape;
};
/**
* Check wether element has relations to other elements e.g. attachers, labels and connections.
*
* @param {Object} element
* @param {Array<djs.model.Base>} elements
*
* @returns {boolean}
*/
CopyPaste.prototype.hasRelations = function(element, elements) {
var labelTarget,
source,
target;
if (isConnection$b(element)) {
source = find(elements, matchPattern({ id: element.source.id }));
target = find(elements, matchPattern({ id: element.target.id }));
if (!source || !target) {
return false;
}
}
if (isLabel$4(element)) {
labelTarget = find(elements, matchPattern({ id: element.labelTarget.id }));
if (!labelTarget) {
return false;
}
}
return true;
};
/**
* Create a tree-like structure from elements.
*
* @example
* tree: {
* 0: [
* { id: 'Shape_1', priority: 1, ... },
* { id: 'Shape_2', priority: 1, ... },
* { id: 'Connection_1', source: 'Shape_1', target: 'Shape_2', priority: 3, ... },
* ...
* ],
* 1: [
* { id: 'Shape_3', parent: 'Shape1', priority: 1, ... },
* ...
* ]
* };
*
* @param {Array<djs.model.base>} elements
*
* @return {Object}
*/
CopyPaste.prototype.createTree = function(elements) {
var rules = this._rules,
self = this;
var tree = {},
elementsData = [];
var parents = getParents$1(elements);
function canCopy(element, elements) {
return rules.allowed('element.copy', {
element: element,
elements: elements
});
}
function addElementData(element, depth) {
// (1) check wether element has already been added
var foundElementData = find(elementsData, function(elementsData) {
return element === elementsData.element;
});
// (2) add element if not already added
if (!foundElementData) {
elementsData.push({
element: element,
depth: depth
});
return;
}
// (3) update depth
if (foundElementData.depth < depth) {
elementsData = removeElementData(foundElementData, elementsData);
elementsData.push({
element: foundElementData.element,
depth: depth
});
}
}
function removeElementData(elementData, elementsData) {
var index = elementsData.indexOf(elementData);
if (index !== -1) {
elementsData.splice(index, 1);
}
return elementsData;
}
// (1) add elements
eachElement(parents, function(element, _index, depth) {
// do NOT add external labels directly
if (isLabel$4(element)) {
return;
}
// always copy external labels
forEach$1(element.labels, function(label) {
addElementData(label, depth);
});
function addRelatedElements(elements) {
elements && elements.length && forEach$1(elements, function(element) {
// add external labels
forEach$1(element.labels, function(label) {
addElementData(label, depth);
});
addElementData(element, depth);
});
}
forEach$1([ element.attachers, element.incoming, element.outgoing ], addRelatedElements);
addElementData(element, depth);
var children = [];
if (element.children) {
children = element.children.slice();
}
// allow others to add children to tree
self._eventBus.fire('copyPaste.createTree', {
element: element,
children: children
});
return children;
});
elements = map(elementsData, function(elementData) {
return elementData.element;
});
// (2) copy elements
elementsData = map(elementsData, function(elementData) {
elementData.descriptor = {};
self._eventBus.fire('copyPaste.copyElement', {
descriptor: elementData.descriptor,
element: elementData.element,
elements: elements
});
return elementData;
});
// (3) sort elements by priority
elementsData = sortBy(elementsData, function(elementData) {
return elementData.descriptor.priority;
});
elements = map(elementsData, function(elementData) {
return elementData.element;
});
// (4) create tree
forEach$1(elementsData, function(elementData) {
var depth = elementData.depth;
if (!self.hasRelations(elementData.element, elements)) {
removeElement(elementData.element, elements);
return;
}
if (!canCopy(elementData.element, elements)) {
removeElement(elementData.element, elements);
return;
}
if (!tree[depth]) {
tree[depth] = [];
}
tree[depth].push(elementData.descriptor);
});
return tree;
};
// helpers //////////
function isAttacher$1(element) {
return !!element.host;
}
function isConnection$b(element) {
return !!element.waypoints;
}
function isLabel$4(element) {
return !!element.labelTarget;
}
function copyWaypoints$1(element) {
return map(element.waypoints, function(waypoint) {
waypoint = copyWaypoint$1(waypoint);
if (waypoint.original) {
waypoint.original = copyWaypoint$1(waypoint.original);
}
return waypoint;
});
}
function copyWaypoint$1(waypoint) {
return assign({}, waypoint);
}
function removeElement(element, elements) {
var index = elements.indexOf(element);
if (index === -1) {
return elements;
}
return elements.splice(index, 1);
}
var CopyPasteModule$1 = {
__depends__: [
ClipboardModule,
CreateModule,
MouseModule,
RulesModule$1
],
__init__: [ 'copyPaste' ],
copyPaste: [ 'type', CopyPaste ]
};
function copyProperties$1(source, target, properties) {
if (!isArray$3(properties)) {
properties = [ properties ];
}
forEach$1(properties, function(property) {
if (!isUndefined$2(source[property])) {
target[property] = source[property];
}
});
}
var LOW_PRIORITY$h = 750;
function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) {
function copy(bo, clone) {
var targetBo = bpmnFactory.create(bo.$type);
return moddleCopy.copyElement(bo, targetBo, null, clone);
}
eventBus.on('copyPaste.copyElement', LOW_PRIORITY$h, function(context) {
var descriptor = context.descriptor,
element = context.element,
businessObject = getBusinessObject(element);
// do not copy business object + di for labels;
// will be pulled from the referenced label target
if (isLabel$3(element)) {
return descriptor;
}
var businessObjectCopy = descriptor.businessObject = copy(businessObject, true);
var diCopy = descriptor.di = copy(getDi(element), true);
diCopy.bpmnElement = businessObjectCopy;
copyProperties$1(businessObjectCopy, descriptor, 'name');
copyProperties$1(diCopy, descriptor, 'isExpanded');
// default sequence flow
if (businessObject.default) {
descriptor.default = businessObject.default.id;
}
});
var referencesKey = '-bpmn-js-refs';
function getReferences(cache) {
return (cache[referencesKey] = cache[referencesKey] || {});
}
function setReferences(cache, references) {
cache[referencesKey] = references;
}
function resolveReferences(descriptor, cache, references) {
var businessObject = getBusinessObject(descriptor);
// default sequence flows
if (descriptor.default) {
// relationship cannot be resolved immediately
references[ descriptor.default ] = {
element: businessObject,
property: 'default'
};
}
// boundary events
if (descriptor.host) {
// relationship can be resolved immediately
getBusinessObject(descriptor).attachedToRef = getBusinessObject(cache[ descriptor.host ]);
}
return omit(references, reduce(references, function(array, reference, key) {
var element = reference.element,
property = reference.property;
if (key === descriptor.id) {
element[ property ] = businessObject;
array.push(descriptor.id);
}
return array;
}, []));
}
eventBus.on('copyPaste.pasteElement', function(context) {
var cache = context.cache,
descriptor = context.descriptor,
businessObject = descriptor.businessObject,
di = descriptor.di;
// wire existing di + businessObject for external label
if (isLabel$3(descriptor)) {
descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]);
descriptor.di = getDi(cache[ descriptor.labelTarget ]);
return;
}
businessObject = descriptor.businessObject = copy(businessObject);
di = descriptor.di = copy(di);
di.bpmnElement = businessObject;
copyProperties$1(descriptor, businessObject, [
'isExpanded',
'name'
]);
descriptor.type = businessObject.$type;
});
// copy + paste processRef with participant
eventBus.on('copyPaste.copyElement', LOW_PRIORITY$h, function(context) {
var descriptor = context.descriptor,
element = context.element;
if (!is$1(element, 'bpmn:Participant')) {
return;
}
var participantBo = getBusinessObject(element);
if (participantBo.processRef) {
descriptor.processRef = copy(participantBo.processRef, true);
}
});
eventBus.on('copyPaste.pasteElement', function(context) {
var descriptor = context.descriptor,
processRef = descriptor.processRef;
if (processRef) {
descriptor.processRef = copy(processRef);
}
});
// resolve references
eventBus.on('copyPaste.pasteElement', LOW_PRIORITY$h, function(context) {
var cache = context.cache,
descriptor = context.descriptor;
// resolve references e.g. default sequence flow
setReferences(
cache,
resolveReferences(descriptor, cache, getReferences(cache))
);
});
}
BpmnCopyPaste.$inject = [
'bpmnFactory',
'eventBus',
'moddleCopy'
];
// helpers //////////
function isLabel$3(element) {
return !!element.labelTarget;
}
var DISALLOWED_PROPERTIES = [
'artifacts',
'dataInputAssociations',
'dataOutputAssociations',
'default',
'flowElements',
'lanes',
'incoming',
'outgoing',
'categoryValue'
];
/**
* @typedef {Function} <moddleCopy.canCopyProperties> listener
*
* @param {Object} context
* @param {Array<string>} context.propertyNames
* @param {ModdleElement} context.sourceElement
* @param {ModdleElement} context.targetElement
*
* @returns {Array<string>|boolean} - Return properties to be copied or false to disallow
* copying.
*/
/**
* @typedef {Function} <moddleCopy.canCopyProperty> listener
*
* @param {Object} context
* @param {ModdleElement} context.parent
* @param {*} context.property
* @param {string} context.propertyName
*
* @returns {*|boolean} - Return copied property or false to disallow
* copying.
*/
/**
* @typedef {Function} <moddleCopy.canSetCopiedProperty> listener
*
* @param {Object} context
* @param {ModdleElement} context.parent
* @param {*} context.property
* @param {string} context.propertyName
*
* @returns {boolean} - Return false to disallow
* setting copied property.
*/
/**
* Utility for copying model properties from source element to target element.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
* @param {BpmnModdle} moddle
*/
function ModdleCopy(eventBus, bpmnFactory, moddle) {
this._bpmnFactory = bpmnFactory;
this._eventBus = eventBus;
this._moddle = moddle;
// copy extension elements last
eventBus.on('moddleCopy.canCopyProperties', function(context) {
var propertyNames = context.propertyNames;
if (!propertyNames || !propertyNames.length) {
return;
}
return sortBy(propertyNames, function(propertyName) {
return propertyName === 'extensionElements';
});
});
// default check whether property can be copied
eventBus.on('moddleCopy.canCopyProperty', function(context) {
var parent = context.parent,
parentDescriptor = isObject(parent) && parent.$descriptor,
propertyName = context.propertyName;
if (propertyName && DISALLOWED_PROPERTIES.indexOf(propertyName) !== -1) {
// disallow copying property
return false;
}
if (propertyName &&
parentDescriptor &&
!find(parentDescriptor.properties, matchPattern({ name: propertyName }))) {
// disallow copying property
return false;
}
});
// do NOT allow to copy empty extension elements
eventBus.on('moddleCopy.canSetCopiedProperty', function(context) {
var property = context.property;
if (is(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) {
// disallow setting copied property
return false;
}
});
}
ModdleCopy.$inject = [
'eventBus',
'bpmnFactory',
'moddle'
];
/**
* Copy model properties of source element to target element.
*
* @param {ModdleElement} sourceElement
* @param {ModdleElement} targetElement
* @param {Array<string>} [propertyNames]
* @param {boolean} clone
*
* @param {ModdleElement}
*/
ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames, clone) {
var self = this;
if (propertyNames && !isArray$3(propertyNames)) {
propertyNames = [ propertyNames ];
}
propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor);
var canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', {
propertyNames: propertyNames,
sourceElement: sourceElement,
targetElement: targetElement,
clone: clone
});
if (canCopyProperties === false) {
return targetElement;
}
if (isArray$3(canCopyProperties)) {
propertyNames = canCopyProperties;
}
// copy properties
forEach$1(propertyNames, function(propertyName) {
var sourceProperty;
if (has$1(sourceElement, propertyName)) {
sourceProperty = sourceElement.get(propertyName);
}
var copiedProperty = self.copyProperty(sourceProperty, targetElement, propertyName, clone);
if (!isDefined(copiedProperty)) {
return;
}
var canSetProperty = self._eventBus.fire('moddleCopy.canSetCopiedProperty', {
parent: targetElement,
property: copiedProperty,
propertyName: propertyName
});
if (canSetProperty === false) {
return;
}
// TODO(nikku): unclaim old IDs if ID property is copied over
// this._moddle.getPropertyDescriptor(parent, propertyName)
targetElement.set(propertyName, copiedProperty);
});
return targetElement;
};
/**
* Copy model property.
*
* @param {*} property
* @param {ModdleElement} parent
* @param {string} propertyName
* @param {boolean} clone
*
* @returns {*}
*/
ModdleCopy.prototype.copyProperty = function(property, parent, propertyName, clone) {
var self = this;
// allow others to copy property
var copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', {
parent: parent,
property: property,
propertyName: propertyName,
clone: clone
});
// return if copying is NOT allowed
if (copiedProperty === false) {
return;
}
if (copiedProperty) {
if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) {
copiedProperty.$parent = parent;
}
return copiedProperty;
}
var propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName);
// do NOT copy references
if (propertyDescriptor.isReference) {
return;
}
// copy id
if (propertyDescriptor.isId) {
return property && this._copyId(property, parent, clone);
}
// copy arrays
if (isArray$3(property)) {
return reduce(property, function(childProperties, childProperty) {
// recursion
copiedProperty = self.copyProperty(childProperty, parent, propertyName, clone);
// copying might NOT be allowed
if (copiedProperty) {
return childProperties.concat(copiedProperty);
}
return childProperties;
}, []);
}
// copy model elements
if (isObject(property) && property.$type) {
if (this._moddle.getElementDescriptor(property).isGeneric) {
return;
}
copiedProperty = self._bpmnFactory.create(property.$type);
copiedProperty.$parent = parent;
// recursion
copiedProperty = self.copyElement(property, copiedProperty, null, clone);
return copiedProperty;
}
// copy primitive properties
return property;
};
ModdleCopy.prototype._copyId = function(id, element, clone) {
if (clone) {
return id;
}
// disallow if already taken
if (this._moddle.ids.assigned(id)) {
return;
} else {
this._moddle.ids.claim(id, element);
return id;
}
};
// helpers //////////
function getPropertyNames(descriptor, keepDefaultProperties) {
return reduce(descriptor.properties, function(properties, property) {
if (keepDefaultProperties && property.default) {
return properties;
}
return properties.concat(property.name);
}, []);
}
function is(element, type) {
return element && (typeof element.$instanceOf === 'function') && element.$instanceOf(type);
}
var CopyPasteModule = {
__depends__: [
CopyPasteModule$1
],
__init__: [ 'bpmnCopyPaste', 'moddleCopy' ],
bpmnCopyPaste: [ 'type', BpmnCopyPaste ],
moddleCopy: [ 'type', ModdleCopy ]
};
var round$5 = Math.round;
/**
* Service that allow replacing of elements.
*/
function Replace(modeling) {
this._modeling = modeling;
}
Replace.$inject = [ 'modeling' ];
/**
* @param {Element} oldElement - Element to be replaced
* @param {Object} newElementData - Containing information about the new element,
* for example the new bounds and type.
* @param {Object} options - Custom options that will be attached to the context. It can be used to inject data
* that is needed in the command chain. For example it could be used in
* eventbus.on('commandStack.shape.replace.postExecute') to change shape attributes after
* shape creation.
*/
Replace.prototype.replaceElement = function(oldElement, newElementData, options) {
if (oldElement.waypoints) {
// TODO(nikku): we do not replace connections, yet
return null;
}
var modeling = this._modeling;
var width = newElementData.width || oldElement.width,
height = newElementData.height || oldElement.height,
x = newElementData.x || oldElement.x,
y = newElementData.y || oldElement.y,
centerX = round$5(x + width / 2),
centerY = round$5(y + height / 2);
// modeling API requires center coordinates,
// account for that when handling shape bounds
return modeling.replaceShape(
oldElement,
assign(
{},
newElementData,
{
x: centerX,
y: centerY,
width: width,
height: height
}
),
options
);
};
var ReplaceModule$1 = {
__init__: [ 'replace' ],
replace: [ 'type', Replace ]
};
function copyProperties(source, target, properties) {
if (!isArray$3(properties)) {
properties = [ properties ];
}
forEach$1(properties, function(property) {
if (!isUndefined$2(source[property])) {
target[property] = source[property];
}
});
}
var CUSTOM_PROPERTIES = [
'cancelActivity',
'instantiate',
'eventGatewayType',
'triggeredByEvent',
'isInterrupting'
];
/**
* Check if element should be collapsed or expanded.
*/
function shouldToggleCollapsed(element, targetElement) {
var oldCollapsed = (
element && has$1(element, 'collapsed') ? element.collapsed : !isExpanded(element)
);
var targetCollapsed;
if (targetElement && (has$1(targetElement, 'collapsed') || has$1(targetElement, 'isExpanded'))) {
// property is explicitly set so use it
targetCollapsed = (
has$1(targetElement, 'collapsed') ? targetElement.collapsed : !targetElement.isExpanded
);
} else {
// keep old state
targetCollapsed = oldCollapsed;
}
if (oldCollapsed !== targetCollapsed) {
return true;
}
return false;
}
/**
* This module takes care of replacing BPMN elements
*/
function BpmnReplace(
bpmnFactory,
elementFactory,
moddleCopy,
modeling,
replace,
rules,
selection
) {
/**
* Prepares a new business object for the replacement element
* and triggers the replace operation.
*
* @param {djs.model.Base} element
* @param {Object} target
* @param {Object} [hints]
*
* @return {djs.model.Base} the newly created element
*/
function replaceElement(element, target, hints) {
hints = hints || {};
var type = target.type,
oldBusinessObject = element.businessObject;
if (isSubProcess(oldBusinessObject) && type === 'bpmn:SubProcess') {
if (shouldToggleCollapsed(element, target)) {
// expanding or collapsing process
modeling.toggleCollapse(element);
return element;
}
}
var newBusinessObject = bpmnFactory.create(type);
var newElement = {
type: type,
businessObject: newBusinessObject,
};
newElement.di = {};
// colors will be set to DI
copyProperties(element.di, newElement.di, [
'fill',
'stroke',
'background-color',
'border-color',
'color'
]);
var elementProps = getPropertyNames(oldBusinessObject.$descriptor),
newElementProps = getPropertyNames(newBusinessObject.$descriptor, true),
copyProps = intersection(elementProps, newElementProps);
// initialize special properties defined in target definition
assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES));
var properties = filter(copyProps, function(propertyName) {
// copying event definitions, unless we replace
if (propertyName === 'eventDefinitions') {
return hasEventDefinition$1(element, target.eventDefinitionType);
}
// retain loop characteristics if the target element
// is not an event sub process
if (propertyName === 'loopCharacteristics') {
return !isEventSubProcess(newBusinessObject);
}
// so the applied properties from 'target' don't get lost
if (has$1(newBusinessObject, propertyName)) {
return false;
}
if (propertyName === 'processRef' && target.isExpanded === false) {
return false;
}
if (propertyName === 'triggeredByEvent') {
return false;
}
return true;
});
newBusinessObject = moddleCopy.copyElement(
oldBusinessObject,
newBusinessObject,
properties
);
// initialize custom BPMN extensions
if (target.eventDefinitionType) {
// only initialize with new eventDefinition
// if we did not set an event definition yet,
// i.e. because we copied it
if (!hasEventDefinition$1(newBusinessObject, target.eventDefinitionType)) {
newElement.eventDefinitionType = target.eventDefinitionType;
newElement.eventDefinitionAttrs = target.eventDefinitionAttrs;
}
}
if (is$1(oldBusinessObject, 'bpmn:Activity')) {
if (isSubProcess(oldBusinessObject)) {
// no toggeling, so keep old state
newElement.isExpanded = isExpanded(element);
}
// else if property is explicitly set, use it
else if (target && has$1(target, 'isExpanded')) {
newElement.isExpanded = target.isExpanded;
// assign default size of new expanded element
var defaultSize = elementFactory.getDefaultSize(newBusinessObject, {
isExpanded: newElement.isExpanded
});
newElement.width = defaultSize.width;
newElement.height = defaultSize.height;
// keep element centered
newElement.x = element.x - (newElement.width - element.width) / 2;
newElement.y = element.y - (newElement.height - element.height) / 2;
}
// TODO: need also to respect min/max Size
// copy size, from an expanded subprocess to an expanded alternative subprocess
// except bpmn:Task, because Task is always expanded
if ((isExpanded(element) && !is$1(oldBusinessObject, 'bpmn:Task')) && newElement.isExpanded) {
newElement.width = element.width;
newElement.height = element.height;
}
}
// remove children if not expanding sub process
if (isSubProcess(oldBusinessObject) && !isSubProcess(newBusinessObject)) {
hints.moveChildren = false;
}
// transform collapsed/expanded pools
if (is$1(oldBusinessObject, 'bpmn:Participant')) {
// create expanded pool
if (target.isExpanded === true) {
newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
} else {
// remove children when transforming to collapsed pool
hints.moveChildren = false;
}
// apply same width and default height
newElement.width = element.width;
newElement.height = elementFactory.getDefaultSize(newElement).height;
}
if (!rules.allowed('shape.resize', { shape: newBusinessObject })) {
newElement.height = elementFactory.getDefaultSize(newElement).height;
newElement.width = elementFactory.getDefaultSize(newElement).width;
}
newBusinessObject.name = oldBusinessObject.name;
// retain default flow's reference between inclusive <-> exclusive gateways and activities
if (
isAny(oldBusinessObject, [
'bpmn:ExclusiveGateway',
'bpmn:InclusiveGateway',
'bpmn:Activity'
]) &&
isAny(newBusinessObject, [
'bpmn:ExclusiveGateway',
'bpmn:InclusiveGateway',
'bpmn:Activity'
])
) {
newBusinessObject.default = oldBusinessObject.default;
}
if (
target.host &&
!is$1(oldBusinessObject, 'bpmn:BoundaryEvent') &&
is$1(newBusinessObject, 'bpmn:BoundaryEvent')
) {
newElement.host = target.host;
}
// The DataStoreReference element is 14px wider than the DataObjectReference element
// This ensures that they stay centered on the x axis when replaced
if (
newElement.type === 'bpmn:DataStoreReference' ||
newElement.type === 'bpmn:DataObjectReference'
) {
newElement.x = element.x + (element.width - newElement.width) / 2;
}
newElement = replace.replaceElement(element, newElement, hints);
if (hints.select !== false) {
selection.select(newElement);
}
return newElement;
}
this.replaceElement = replaceElement;
}
BpmnReplace.$inject = [
'bpmnFactory',
'elementFactory',
'moddleCopy',
'modeling',
'replace',
'rules',
'selection'
];
function isSubProcess(bo) {
return is$1(bo, 'bpmn:SubProcess');
}
function hasEventDefinition$1(element, type) {
var bo = getBusinessObject(element);
return type && bo.get('eventDefinitions').some(function(definition) {
return is$1(definition, type);
});
}
/**
* Compute intersection between two arrays.
*/
function intersection(a1, a2) {
return a1.filter(function(el) {
return a2.indexOf(el) !== -1;
});
}
var ReplaceModule = {
__depends__: [
CopyPasteModule,
ReplaceModule$1,
SelectionModule
],
bpmnReplace: [ 'type', BpmnReplace ]
};
/**
* Returns true, if an element is from a different type
* than a target definition. Takes into account the type,
* event definition type and triggeredByEvent property.
*
* @param {djs.model.Base} element
*
* @return {boolean}
*/
function isDifferentType(element) {
return function(entry) {
var target = entry.target;
var businessObject = getBusinessObject(element),
eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0];
var isTypeEqual = businessObject.$type === target.type;
var isEventDefinitionEqual = (
(eventDefinition && eventDefinition.$type) === target.eventDefinitionType
);
var isTriggeredByEventEqual = (
businessObject.triggeredByEvent === target.triggeredByEvent
);
var isExpandedEqual = (
target.isExpanded === undefined ||
target.isExpanded === isExpanded(element)
);
return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual || !isExpandedEqual;
};
}
var START_EVENT = [
{
label: 'Start Event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate Throw Event',
actionName: 'replace-with-none-intermediate-throwing',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End Event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
},
{
label: 'Message Start Event',
actionName: 'replace-with-message-start',
className: 'bpmn-icon-start-event-message',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Timer Start Event',
actionName: 'replace-with-timer-start',
className: 'bpmn-icon-start-event-timer',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
}
},
{
label: 'Conditional Start Event',
actionName: 'replace-with-conditional-start',
className: 'bpmn-icon-start-event-condition',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
}
},
{
label: 'Signal Start Event',
actionName: 'replace-with-signal-start',
className: 'bpmn-icon-start-event-signal',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
}
];
var START_EVENT_SUB_PROCESS = [
{
label: 'Start Event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate Throw Event',
actionName: 'replace-with-none-intermediate-throwing',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End Event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
}
];
var INTERMEDIATE_EVENT = [
{
label: 'Start Event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate Throw Event',
actionName: 'replace-with-none-intermediate-throw',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End Event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
},
{
label: 'Message Intermediate Catch Event',
actionName: 'replace-with-message-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-message',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Message Intermediate Throw Event',
actionName: 'replace-with-message-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-message',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Timer Intermediate Catch Event',
actionName: 'replace-with-timer-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-timer',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
}
},
{
label: 'Escalation Intermediate Throw Event',
actionName: 'replace-with-escalation-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-escalation',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
},
{
label: 'Conditional Intermediate Catch Event',
actionName: 'replace-with-conditional-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-condition',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
}
},
{
label: 'Link Intermediate Catch Event',
actionName: 'replace-with-link-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-link',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:LinkEventDefinition',
eventDefinitionAttrs: {
name: ''
}
}
},
{
label: 'Link Intermediate Throw Event',
actionName: 'replace-with-link-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-link',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:LinkEventDefinition',
eventDefinitionAttrs: {
name: ''
}
}
},
{
label: 'Compensation Intermediate Throw Event',
actionName: 'replace-with-compensation-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-compensation',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
},
{
label: 'Signal Intermediate Catch Event',
actionName: 'replace-with-signal-intermediate-catch',
className: 'bpmn-icon-intermediate-event-catch-signal',
target: {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Signal Intermediate Throw Event',
actionName: 'replace-with-signal-intermediate-throw',
className: 'bpmn-icon-intermediate-event-throw-signal',
target: {
type: 'bpmn:IntermediateThrowEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
}
];
var END_EVENT = [
{
label: 'Start Event',
actionName: 'replace-with-none-start',
className: 'bpmn-icon-start-event-none',
target: {
type: 'bpmn:StartEvent'
}
},
{
label: 'Intermediate Throw Event',
actionName: 'replace-with-none-intermediate-throw',
className: 'bpmn-icon-intermediate-event-none',
target: {
type: 'bpmn:IntermediateThrowEvent'
}
},
{
label: 'End Event',
actionName: 'replace-with-none-end',
className: 'bpmn-icon-end-event-none',
target: {
type: 'bpmn:EndEvent'
}
},
{
label: 'Message End Event',
actionName: 'replace-with-message-end',
className: 'bpmn-icon-end-event-message',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Escalation End Event',
actionName: 'replace-with-escalation-end',
className: 'bpmn-icon-end-event-escalation',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
},
{
label: 'Error End Event',
actionName: 'replace-with-error-end',
className: 'bpmn-icon-end-event-error',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
}
},
{
label: 'Cancel End Event',
actionName: 'replace-with-cancel-end',
className: 'bpmn-icon-end-event-cancel',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
}
},
{
label: 'Compensation End Event',
actionName: 'replace-with-compensation-end',
className: 'bpmn-icon-end-event-compensation',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
},
{
label: 'Signal End Event',
actionName: 'replace-with-signal-end',
className: 'bpmn-icon-end-event-signal',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Terminate End Event',
actionName: 'replace-with-terminate-end',
className: 'bpmn-icon-end-event-terminate',
target: {
type: 'bpmn:EndEvent',
eventDefinitionType: 'bpmn:TerminateEventDefinition'
}
}
];
var GATEWAY = [
{
label: 'Exclusive Gateway',
actionName: 'replace-with-exclusive-gateway',
className: 'bpmn-icon-gateway-xor',
target: {
type: 'bpmn:ExclusiveGateway'
}
},
{
label: 'Parallel Gateway',
actionName: 'replace-with-parallel-gateway',
className: 'bpmn-icon-gateway-parallel',
target: {
type: 'bpmn:ParallelGateway'
}
},
{
label: 'Inclusive Gateway',
actionName: 'replace-with-inclusive-gateway',
className: 'bpmn-icon-gateway-or',
target: {
type: 'bpmn:InclusiveGateway'
}
},
{
label: 'Complex Gateway',
actionName: 'replace-with-complex-gateway',
className: 'bpmn-icon-gateway-complex',
target: {
type: 'bpmn:ComplexGateway'
}
},
{
label: 'Event based Gateway',
actionName: 'replace-with-event-based-gateway',
className: 'bpmn-icon-gateway-eventbased',
target: {
type: 'bpmn:EventBasedGateway',
instantiate: false,
eventGatewayType: 'Exclusive'
}
}
// Gateways deactivated until https://github.com/bpmn-io/bpmn-js/issues/194
// {
// label: 'Event based instantiating Gateway',
// actionName: 'replace-with-exclusive-event-based-gateway',
// className: 'bpmn-icon-exclusive-event-based',
// target: {
// type: 'bpmn:EventBasedGateway'
// },
// options: {
// businessObject: { instantiate: true, eventGatewayType: 'Exclusive' }
// }
// },
// {
// label: 'Parallel Event based instantiating Gateway',
// actionName: 'replace-with-parallel-event-based-instantiate-gateway',
// className: 'bpmn-icon-parallel-event-based-instantiate-gateway',
// target: {
// type: 'bpmn:EventBasedGateway'
// },
// options: {
// businessObject: { instantiate: true, eventGatewayType: 'Parallel' }
// }
// }
];
var SUBPROCESS_EXPANDED = [
{
label: 'Transaction',
actionName: 'replace-with-transaction',
className: 'bpmn-icon-transaction',
target: {
type: 'bpmn:Transaction',
isExpanded: true
}
},
{
label: 'Event Sub Process',
actionName: 'replace-with-event-subprocess',
className: 'bpmn-icon-event-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
triggeredByEvent: true,
isExpanded: true
}
},
{
label: 'Sub Process (collapsed)',
actionName: 'replace-with-collapsed-subprocess',
className: 'bpmn-icon-subprocess-collapsed',
target: {
type: 'bpmn:SubProcess',
isExpanded: false
}
}
];
var TRANSACTION = [
{
label: 'Sub Process',
actionName: 'replace-with-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
isExpanded: true
}
},
{
label: 'Event Sub Process',
actionName: 'replace-with-event-subprocess',
className: 'bpmn-icon-event-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
triggeredByEvent: true,
isExpanded: true
}
}
];
var EVENT_SUB_PROCESS = [
{
label: 'Sub Process',
actionName: 'replace-with-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
isExpanded: true
}
},
{
label: 'Transaction',
actionName: 'replace-with-transaction',
className: 'bpmn-icon-transaction',
target: {
type: 'bpmn:Transaction',
isExpanded: true
}
}
];
var TASK = [
{
label: 'Task',
actionName: 'replace-with-task',
className: 'bpmn-icon-task',
target: {
type: 'bpmn:Task'
}
},
{
label: 'Send Task',
actionName: 'replace-with-send-task',
className: 'bpmn-icon-send',
target: {
type: 'bpmn:SendTask'
}
},
{
label: 'Receive Task',
actionName: 'replace-with-receive-task',
className: 'bpmn-icon-receive',
target: {
type: 'bpmn:ReceiveTask'
}
},
{
label: 'User Task',
actionName: 'replace-with-user-task',
className: 'bpmn-icon-user',
target: {
type: 'bpmn:UserTask'
}
},
{
label: 'Manual Task',
actionName: 'replace-with-manual-task',
className: 'bpmn-icon-manual',
target: {
type: 'bpmn:ManualTask'
}
},
{
label: 'Business Rule Task',
actionName: 'replace-with-rule-task',
className: 'bpmn-icon-business-rule',
target: {
type: 'bpmn:BusinessRuleTask'
}
},
{
label: 'Service Task',
actionName: 'replace-with-service-task',
className: 'bpmn-icon-service',
target: {
type: 'bpmn:ServiceTask'
}
},
{
label: 'Script Task',
actionName: 'replace-with-script-task',
className: 'bpmn-icon-script',
target: {
type: 'bpmn:ScriptTask'
}
},
{
label: 'Call Activity',
actionName: 'replace-with-call-activity',
className: 'bpmn-icon-call-activity',
target: {
type: 'bpmn:CallActivity'
}
},
{
label: 'Sub Process (collapsed)',
actionName: 'replace-with-collapsed-subprocess',
className: 'bpmn-icon-subprocess-collapsed',
target: {
type: 'bpmn:SubProcess',
isExpanded: false
}
},
{
label: 'Sub Process (expanded)',
actionName: 'replace-with-expanded-subprocess',
className: 'bpmn-icon-subprocess-expanded',
target: {
type: 'bpmn:SubProcess',
isExpanded: true
}
}
];
var DATA_OBJECT_REFERENCE = [
{
label: 'Data Store Reference',
actionName: 'replace-with-data-store-reference',
className: 'bpmn-icon-data-store',
target: {
type: 'bpmn:DataStoreReference'
}
}
];
var DATA_STORE_REFERENCE = [
{
label: 'Data Object Reference',
actionName: 'replace-with-data-object-reference',
className: 'bpmn-icon-data-object',
target: {
type: 'bpmn:DataObjectReference'
}
}
];
var BOUNDARY_EVENT = [
{
label: 'Message Boundary Event',
actionName: 'replace-with-message-boundary',
className: 'bpmn-icon-intermediate-event-catch-message',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Timer Boundary Event',
actionName: 'replace-with-timer-boundary',
className: 'bpmn-icon-intermediate-event-catch-timer',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
}
},
{
label: 'Escalation Boundary Event',
actionName: 'replace-with-escalation-boundary',
className: 'bpmn-icon-intermediate-event-catch-escalation',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
},
{
label: 'Conditional Boundary Event',
actionName: 'replace-with-conditional-boundary',
className: 'bpmn-icon-intermediate-event-catch-condition',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
}
},
{
label: 'Error Boundary Event',
actionName: 'replace-with-error-boundary',
className: 'bpmn-icon-intermediate-event-catch-error',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
}
},
{
label: 'Cancel Boundary Event',
actionName: 'replace-with-cancel-boundary',
className: 'bpmn-icon-intermediate-event-catch-cancel',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CancelEventDefinition'
}
},
{
label: 'Signal Boundary Event',
actionName: 'replace-with-signal-boundary',
className: 'bpmn-icon-intermediate-event-catch-signal',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Compensation Boundary Event',
actionName: 'replace-with-compensation-boundary',
className: 'bpmn-icon-intermediate-event-catch-compensation',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
},
{
label: 'Message Boundary Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-message-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-message',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
cancelActivity: false
}
},
{
label: 'Timer Boundary Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-timer-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-timer',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
cancelActivity: false
}
},
{
label: 'Escalation Boundary Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-escalation-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-escalation',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
cancelActivity: false
}
},
{
label: 'Conditional Boundary Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-conditional-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-condition',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
cancelActivity: false
}
},
{
label: 'Signal Boundary Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-signal-boundary',
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-signal',
target: {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
cancelActivity: false
}
}
];
var EVENT_SUB_PROCESS_START_EVENT = [
{
label: 'Message Start Event',
actionName: 'replace-with-message-start',
className: 'bpmn-icon-start-event-message',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition'
}
},
{
label: 'Timer Start Event',
actionName: 'replace-with-timer-start',
className: 'bpmn-icon-start-event-timer',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
}
},
{
label: 'Conditional Start Event',
actionName: 'replace-with-conditional-start',
className: 'bpmn-icon-start-event-condition',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
}
},
{
label: 'Signal Start Event',
actionName: 'replace-with-signal-start',
className: 'bpmn-icon-start-event-signal',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition'
}
},
{
label: 'Error Start Event',
actionName: 'replace-with-error-start',
className: 'bpmn-icon-start-event-error',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ErrorEventDefinition'
}
},
{
label: 'Escalation Start Event',
actionName: 'replace-with-escalation-start',
className: 'bpmn-icon-start-event-escalation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
}
},
{
label: 'Compensation Start Event',
actionName: 'replace-with-compensation-start',
className: 'bpmn-icon-start-event-compensation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:CompensateEventDefinition'
}
},
{
label: 'Message Start Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-message-start',
className: 'bpmn-icon-start-event-non-interrupting-message',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:MessageEventDefinition',
isInterrupting: false
}
},
{
label: 'Timer Start Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-timer-start',
className: 'bpmn-icon-start-event-non-interrupting-timer',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition',
isInterrupting: false
}
},
{
label: 'Conditional Start Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-conditional-start',
className: 'bpmn-icon-start-event-non-interrupting-condition',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
isInterrupting: false
}
},
{
label: 'Signal Start Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-signal-start',
className: 'bpmn-icon-start-event-non-interrupting-signal',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
isInterrupting: false
}
},
{
label: 'Escalation Start Event (non-interrupting)',
actionName: 'replace-with-non-interrupting-escalation-start',
className: 'bpmn-icon-start-event-non-interrupting-escalation',
target: {
type: 'bpmn:StartEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition',
isInterrupting: false
}
}
];
var SEQUENCE_FLOW = [
{
label: 'Sequence Flow',
actionName: 'replace-with-sequence-flow',
className: 'bpmn-icon-connection'
},
{
label: 'Default Flow',
actionName: 'replace-with-default-flow',
className: 'bpmn-icon-default-flow'
},
{
label: 'Conditional Flow',
actionName: 'replace-with-conditional-flow',
className: 'bpmn-icon-conditional-flow'
}
];
var PARTICIPANT = [
{
label: 'Expanded Pool',
actionName: 'replace-with-expanded-pool',
className: 'bpmn-icon-participant',
target: {
type: 'bpmn:Participant',
isExpanded: true
}
},
{
label: function(element) {
var label = 'Empty Pool';
if (element.children && element.children.length) {
label += ' (removes content)';
}
return label;
},
actionName: 'replace-with-collapsed-pool',
// TODO(@janstuemmel): maybe design new icon
className: 'bpmn-icon-lane',
target: {
type: 'bpmn:Participant',
isExpanded: false
}
}
];
/**
* This module is an element agnostic replace menu provider for the popup menu.
*/
function ReplaceMenuProvider(
bpmnFactory, popupMenu, modeling, moddle,
bpmnReplace, rules, translate) {
this._bpmnFactory = bpmnFactory;
this._popupMenu = popupMenu;
this._modeling = modeling;
this._moddle = moddle;
this._bpmnReplace = bpmnReplace;
this._rules = rules;
this._translate = translate;
this.register();
}
ReplaceMenuProvider.$inject = [
'bpmnFactory',
'popupMenu',
'modeling',
'moddle',
'bpmnReplace',
'rules',
'translate'
];
/**
* Register replace menu provider in the popup menu
*/
ReplaceMenuProvider.prototype.register = function() {
this._popupMenu.registerProvider('bpmn-replace', this);
};
/**
* Get all entries from replaceOptions for the given element and apply filters
* on them. Get for example only elements, which are different from the current one.
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu entry items
*/
ReplaceMenuProvider.prototype.getEntries = function(element) {
var businessObject = element.businessObject;
var rules = this._rules;
var entries;
if (!rules.allowed('shape.replace', { element: element })) {
return [];
}
var differentType = isDifferentType(element);
if (is$1(businessObject, 'bpmn:DataObjectReference')) {
return this._createEntries(element, DATA_OBJECT_REFERENCE);
}
if (is$1(businessObject, 'bpmn:DataStoreReference') && !is$1(element.parent, 'bpmn:Collaboration')) {
return this._createEntries(element, DATA_STORE_REFERENCE);
}
// start events outside sub processes
if (is$1(businessObject, 'bpmn:StartEvent') && !is$1(businessObject.$parent, 'bpmn:SubProcess')) {
entries = filter(START_EVENT, differentType);
return this._createEntries(element, entries);
}
// expanded/collapsed pools
if (is$1(businessObject, 'bpmn:Participant')) {
entries = filter(PARTICIPANT, function(entry) {
return isExpanded(element) !== entry.target.isExpanded;
});
return this._createEntries(element, entries);
}
// start events inside event sub processes
if (is$1(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
entries = filter(EVENT_SUB_PROCESS_START_EVENT, function(entry) {
var target = entry.target;
var isInterrupting = target.isInterrupting !== false;
var isInterruptingEqual = getBusinessObject(element).isInterrupting === isInterrupting;
// filters elements which types and event definition are equal but have have different interrupting types
return differentType(entry) || !differentType(entry) && !isInterruptingEqual;
});
return this._createEntries(element, entries);
}
// start events inside sub processes
if (is$1(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)
&& is$1(businessObject.$parent, 'bpmn:SubProcess')) {
entries = filter(START_EVENT_SUB_PROCESS, differentType);
return this._createEntries(element, entries);
}
// end events
if (is$1(businessObject, 'bpmn:EndEvent')) {
entries = filter(END_EVENT, function(entry) {
var target = entry.target;
// hide cancel end events outside transactions
if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is$1(businessObject.$parent, 'bpmn:Transaction')) {
return false;
}
return differentType(entry);
});
return this._createEntries(element, entries);
}
// boundary events
if (is$1(businessObject, 'bpmn:BoundaryEvent')) {
entries = filter(BOUNDARY_EVENT, function(entry) {
var target = entry.target;
if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' &&
!is$1(businessObject.attachedToRef, 'bpmn:Transaction')) {
return false;
}
var cancelActivity = target.cancelActivity !== false;
var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;
return differentType(entry) || !differentType(entry) && !isCancelActivityEqual;
});
return this._createEntries(element, entries);
}
// intermediate events
if (is$1(businessObject, 'bpmn:IntermediateCatchEvent') ||
is$1(businessObject, 'bpmn:IntermediateThrowEvent')) {
entries = filter(INTERMEDIATE_EVENT, differentType);
return this._createEntries(element, entries);
}
// gateways
if (is$1(businessObject, 'bpmn:Gateway')) {
entries = filter(GATEWAY, differentType);
return this._createEntries(element, entries);
}
// transactions
if (is$1(businessObject, 'bpmn:Transaction')) {
entries = filter(TRANSACTION, differentType);
return this._createEntries(element, entries);
}
// expanded event sub processes
if (isEventSubProcess(businessObject) && isExpanded(element)) {
entries = filter(EVENT_SUB_PROCESS, differentType);
return this._createEntries(element, entries);
}
// expanded sub processes
if (is$1(businessObject, 'bpmn:SubProcess') && isExpanded(element)) {
entries = filter(SUBPROCESS_EXPANDED, differentType);
return this._createEntries(element, entries);
}
// collapsed ad hoc sub processes
if (is$1(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(element)) {
entries = filter(TASK, function(entry) {
var target = entry.target;
var isTargetSubProcess = target.type === 'bpmn:SubProcess';
var isTargetExpanded = target.isExpanded === true;
return isDifferentType(element) && (!isTargetSubProcess || isTargetExpanded);
});
return this._createEntries(element, entries);
}
// sequence flows
if (is$1(businessObject, 'bpmn:SequenceFlow')) {
return this._createSequenceFlowEntries(element, SEQUENCE_FLOW);
}
// flow nodes
if (is$1(businessObject, 'bpmn:FlowNode')) {
entries = filter(TASK, differentType);
// collapsed SubProcess can not be replaced with itself
if (is$1(businessObject, 'bpmn:SubProcess') && !isExpanded(element)) {
entries = filter(entries, function(entry) {
return entry.label !== 'Sub Process (collapsed)';
});
}
return this._createEntries(element, entries);
}
return [];
};
/**
* Get a list of header items for the given element. This includes buttons
* for multi instance markers and for the ad hoc marker.
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu entry items
*/
ReplaceMenuProvider.prototype.getHeaderEntries = function(element) {
var headerEntries = [];
if (is$1(element, 'bpmn:Activity') && !isEventSubProcess(element)) {
headerEntries = headerEntries.concat(this._getLoopEntries(element));
}
if (is$1(element, 'bpmn:DataObjectReference')) {
headerEntries = headerEntries.concat(this._getDataObjectIsCollection(element));
}
if (is$1(element, 'bpmn:Participant')) {
headerEntries = headerEntries.concat(this._getParticipantMultiplicity(element));
}
if (is$1(element, 'bpmn:SubProcess') &&
!is$1(element, 'bpmn:Transaction') &&
!isEventSubProcess(element)) {
headerEntries.push(this._getAdHocEntry(element));
}
return headerEntries;
};
/**
* Creates an array of menu entry objects for a given element and filters the replaceOptions
* according to a filter function.
*
* @param {djs.model.Base} element
* @param {Object} replaceOptions
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) {
var menuEntries = [];
var self = this;
forEach$1(replaceOptions, function(definition) {
var entry = self._createMenuEntry(definition, element);
menuEntries.push(entry);
});
return menuEntries;
};
/**
* Creates an array of menu entry objects for a given sequence flow.
*
* @param {djs.model.Base} element
* @param {Object} replaceOptions
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(element, replaceOptions) {
var businessObject = getBusinessObject(element);
var menuEntries = [];
var modeling = this._modeling,
moddle = this._moddle;
var self = this;
forEach$1(replaceOptions, function(entry) {
switch (entry.actionName) {
case 'replace-with-default-flow':
if (businessObject.sourceRef.default !== businessObject &&
(is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
is$1(businessObject.sourceRef, 'bpmn:Activity'))) {
menuEntries.push(self._createMenuEntry(entry, element, function() {
modeling.updateProperties(element.source, { default: businessObject });
}));
}
break;
case 'replace-with-conditional-flow':
if (!businessObject.conditionExpression && is$1(businessObject.sourceRef, 'bpmn:Activity')) {
menuEntries.push(self._createMenuEntry(entry, element, function() {
var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });
modeling.updateProperties(element, { conditionExpression: conditionExpression });
}));
}
break;
default:
// default flows
if (is$1(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
return menuEntries.push(self._createMenuEntry(entry, element, function() {
modeling.updateProperties(element, { conditionExpression: undefined });
}));
}
// conditional flows
if ((is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
is$1(businessObject.sourceRef, 'bpmn:Activity')) &&
businessObject.sourceRef.default === businessObject) {
return menuEntries.push(self._createMenuEntry(entry, element, function() {
modeling.updateProperties(element.source, { default: undefined });
}));
}
}
});
return menuEntries;
};
/**
* Creates and returns a single menu entry item.
*
* @param {Object} definition a single replace options definition object
* @param {djs.model.Base} element
* @param {Function} [action] an action callback function which gets called when
* the menu entry is being triggered.
*
* @return {Object} menu entry item
*/
ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) {
var translate = this._translate;
var replaceElement = this._bpmnReplace.replaceElement;
var replaceAction = function() {
return replaceElement(element, definition.target);
};
var label = definition.label;
if (label && typeof label === 'function') {
label = label(element);
}
action = action || replaceAction;
var menuEntry = {
label: translate(label),
className: definition.className,
id: definition.actionName,
action: action
};
return menuEntry;
};
/**
* Get a list of menu items containing buttons for multi instance markers
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._getLoopEntries = function(element) {
var self = this;
var translate = this._translate;
function toggleLoopEntry(event, entry) {
var newLoopCharacteristics = getBusinessObject(element).loopCharacteristics;
if (entry.active) {
newLoopCharacteristics = undefined;
} else {
if (isUndefined$2(entry.options.isSequential) || !newLoopCharacteristics
|| !is$1(newLoopCharacteristics, entry.options.loopCharacteristics)) {
newLoopCharacteristics = self._moddle.create(entry.options.loopCharacteristics);
}
newLoopCharacteristics.isSequential = entry.options.isSequential;
}
self._modeling.updateProperties(element, { loopCharacteristics: newLoopCharacteristics });
}
var businessObject = getBusinessObject(element),
loopCharacteristics = businessObject.loopCharacteristics;
var isSequential,
isLoop,
isParallel;
if (loopCharacteristics) {
isSequential = loopCharacteristics.isSequential;
isLoop = loopCharacteristics.isSequential === undefined;
isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
}
var loopEntries = [
{
id: 'toggle-parallel-mi',
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Parallel Multi Instance'),
active: isParallel,
action: toggleLoopEntry,
options: {
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
isSequential: false
}
},
{
id: 'toggle-sequential-mi',
className: 'bpmn-icon-sequential-mi-marker',
title: translate('Sequential Multi Instance'),
active: isSequential,
action: toggleLoopEntry,
options: {
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
isSequential: true
}
},
{
id: 'toggle-loop',
className: 'bpmn-icon-loop-marker',
title: translate('Loop'),
active: isLoop,
action: toggleLoopEntry,
options: {
loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
}
}
];
return loopEntries;
};
/**
* Get a list of menu items containing a button for the collection marker
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._getDataObjectIsCollection = function(element) {
var self = this;
var translate = this._translate;
function toggleIsCollection(event, entry) {
self._modeling.updateModdleProperties(
element,
dataObject,
{ isCollection: !entry.active });
}
var dataObject = element.businessObject.dataObjectRef,
isCollection = dataObject.isCollection;
var dataObjectEntries = [
{
id: 'toggle-is-collection',
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Collection'),
active: isCollection,
action: toggleIsCollection,
}
];
return dataObjectEntries;
};
/**
* Get a list of menu items containing a button for the participant multiplicity marker
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._getParticipantMultiplicity = function(element) {
var self = this;
var bpmnFactory = this._bpmnFactory;
var translate = this._translate;
function toggleParticipantMultiplicity(event, entry) {
var isActive = entry.active;
var participantMultiplicity;
if (!isActive) {
participantMultiplicity = bpmnFactory.create('bpmn:ParticipantMultiplicity');
}
self._modeling.updateProperties(
element,
{ participantMultiplicity: participantMultiplicity });
}
var participantMultiplicity = element.businessObject.participantMultiplicity;
var participantEntries = [
{
id: 'toggle-participant-multiplicity',
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Participant Multiplicity'),
active: !!participantMultiplicity,
action: toggleParticipantMultiplicity,
}
];
return participantEntries;
};
/**
* Get the menu items containing a button for the ad hoc marker
*
* @param {djs.model.Base} element
*
* @return {Object} a menu item
*/
ReplaceMenuProvider.prototype._getAdHocEntry = function(element) {
var translate = this._translate;
var businessObject = getBusinessObject(element);
var isAdHoc = is$1(businessObject, 'bpmn:AdHocSubProcess');
var replaceElement = this._bpmnReplace.replaceElement;
var adHocEntry = {
id: 'toggle-adhoc',
className: 'bpmn-icon-ad-hoc-marker',
title: translate('Ad-hoc'),
active: isAdHoc,
action: function(event, entry) {
if (isAdHoc) {
return replaceElement(element, { type: 'bpmn:SubProcess' }, {
autoResize: false,
layoutConnection: false
});
} else {
return replaceElement(element, { type: 'bpmn:AdHocSubProcess' }, {
autoResize: false,
layoutConnection: false
});
}
}
};
return adHocEntry;
};
var PopupMenuModule = {
__depends__: [
PopupMenuModule$1,
ReplaceModule
],
__init__: [ 'replaceMenuProvider' ],
replaceMenuProvider: [ 'type', ReplaceMenuProvider ]
};
var max$4 = Math.max,
min$2 = Math.min;
var DEFAULT_CHILD_BOX_PADDING = 20;
/**
* Substract a TRBL from another
*
* @param {TRBL} trblA
* @param {TRBL} trblB
*
* @return {TRBL}
*/
function substractTRBL(trblA, trblB) {
return {
top: trblA.top - trblB.top,
right: trblA.right - trblB.right,
bottom: trblA.bottom - trblB.bottom,
left: trblA.left - trblB.left
};
}
/**
* Resize the given bounds by the specified delta from a given anchor point.
*
* @param {Bounds} bounds the bounding box that should be resized
* @param {string} direction in which the element is resized (nw, ne, se, sw)
* @param {Point} delta of the resize operation
*
* @return {Bounds} resized bounding box
*/
function resizeBounds$1(bounds, direction, delta) {
var dx = delta.x,
dy = delta.y;
var newBounds = {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
};
if (direction.indexOf('n') !== -1) {
newBounds.y = bounds.y + dy;
newBounds.height = bounds.height - dy;
} else if (direction.indexOf('s') !== -1) {
newBounds.height = bounds.height + dy;
}
if (direction.indexOf('e') !== -1) {
newBounds.width = bounds.width + dx;
} else if (direction.indexOf('w') !== -1) {
newBounds.x = bounds.x + dx;
newBounds.width = bounds.width - dx;
}
return newBounds;
}
/**
* Resize the given bounds by applying the passed
* { top, right, bottom, left } delta.
*
* @param {Bounds} bounds
* @param {TRBL} trblResize
*
* @return {Bounds}
*/
function resizeTRBL(bounds, resize) {
return {
x: bounds.x + (resize.left || 0),
y: bounds.y + (resize.top || 0),
width: bounds.width - (resize.left || 0) + (resize.right || 0),
height: bounds.height - (resize.top || 0) + (resize.bottom || 0)
};
}
function applyConstraints(attr, trbl, resizeConstraints) {
var value = trbl[attr],
minValue = resizeConstraints.min && resizeConstraints.min[attr],
maxValue = resizeConstraints.max && resizeConstraints.max[attr];
if (isNumber(minValue)) {
value = (/top|left/.test(attr) ? min$2 : max$4)(value, minValue);
}
if (isNumber(maxValue)) {
value = (/top|left/.test(attr) ? max$4 : min$2)(value, maxValue);
}
return value;
}
function ensureConstraints$1(currentBounds, resizeConstraints) {
if (!resizeConstraints) {
return currentBounds;
}
var currentTrbl = asTRBL(currentBounds);
return asBounds({
top: applyConstraints('top', currentTrbl, resizeConstraints),
right: applyConstraints('right', currentTrbl, resizeConstraints),
bottom: applyConstraints('bottom', currentTrbl, resizeConstraints),
left: applyConstraints('left', currentTrbl, resizeConstraints)
});
}
function getMinResizeBounds(direction, currentBounds, minDimensions, childrenBounds) {
var currentBox = asTRBL(currentBounds);
var minBox = {
top: /n/.test(direction) ? currentBox.bottom - minDimensions.height : currentBox.top,
left: /w/.test(direction) ? currentBox.right - minDimensions.width : currentBox.left,
bottom: /s/.test(direction) ? currentBox.top + minDimensions.height : currentBox.bottom,
right: /e/.test(direction) ? currentBox.left + minDimensions.width : currentBox.right
};
var childrenBox = childrenBounds ? asTRBL(childrenBounds) : minBox;
var combinedBox = {
top: min$2(minBox.top, childrenBox.top),
left: min$2(minBox.left, childrenBox.left),
bottom: max$4(minBox.bottom, childrenBox.bottom),
right: max$4(minBox.right, childrenBox.right)
};
return asBounds(combinedBox);
}
function asPadding(mayBePadding, defaultValue) {
if (typeof mayBePadding !== 'undefined') {
return mayBePadding;
} else {
return DEFAULT_CHILD_BOX_PADDING;
}
}
function addPadding$1(bbox, padding) {
var left, right, top, bottom;
if (typeof padding === 'object') {
left = asPadding(padding.left);
right = asPadding(padding.right);
top = asPadding(padding.top);
bottom = asPadding(padding.bottom);
} else {
left = right = top = bottom = asPadding(padding);
}
return {
x: bbox.x - left,
y: bbox.y - top,
width: bbox.width + left + right,
height: bbox.height + top + bottom
};
}
/**
* Is the given element part of the resize
* targets min boundary box?
*
* This is the default implementation which excludes
* connections and labels.
*
* @param {djs.model.Base} element
*/
function isBBoxChild(element) {
// exclude connections
if (element.waypoints) {
return false;
}
// exclude labels
if (element.type === 'label') {
return false;
}
return true;
}
/**
* Return children bounding computed from a shapes children
* or a list of prefiltered children.
*
* @param {djs.model.Shape|Array<djs.model.Shape>} shapeOrChildren
* @param {number|Object} padding
*
* @return {Bounds}
*/
function computeChildrenBBox(shapeOrChildren, padding) {
var elements;
// compute based on shape
if (shapeOrChildren.length === undefined) {
// grab all the children that are part of the
// parents children box
elements = filter(shapeOrChildren.children, isBBoxChild);
} else {
elements = shapeOrChildren;
}
if (elements.length) {
return addPadding$1(getBBox(elements), padding);
}
}
var abs$4 = Math.abs;
function getTRBLResize(oldBounds, newBounds) {
return substractTRBL(asTRBL(newBounds), asTRBL(oldBounds));
}
var LANE_PARENTS = [
'bpmn:Participant',
'bpmn:Process',
'bpmn:SubProcess'
];
var LANE_INDENTATION = 30;
/**
* Collect all lane shapes in the given paren
*
* @param {djs.model.Shape} shape
* @param {Array<djs.model.Base>} [collectedShapes]
*
* @return {Array<djs.model.Base>}
*/
function collectLanes(shape, collectedShapes) {
collectedShapes = collectedShapes || [];
shape.children.filter(function(s) {
if (is$1(s, 'bpmn:Lane')) {
collectLanes(s, collectedShapes);
collectedShapes.push(s);
}
});
return collectedShapes;
}
/**
* Return the lane children of the given element.
*
* @param {djs.model.Shape} shape
*
* @return {Array<djs.model.Shape>}
*/
function getChildLanes(shape) {
return shape.children.filter(function(c) {
return is$1(c, 'bpmn:Lane');
});
}
/**
* Return the root element containing the given lane shape
*
* @param {djs.model.Shape} shape
*
* @return {djs.model.Shape}
*/
function getLanesRoot(shape) {
return getParent(shape, LANE_PARENTS) || shape;
}
/**
* Compute the required resize operations for lanes
* adjacent to the given shape, assuming it will be
* resized to the given new bounds.
*
* @param {djs.model.Shape} shape
* @param {Bounds} newBounds
*
* @return {Array<Object>}
*/
function computeLanesResize(shape, newBounds) {
var rootElement = getLanesRoot(shape);
var initialShapes = is$1(rootElement, 'bpmn:Process') ? [] : [ rootElement ];
var allLanes = collectLanes(rootElement, initialShapes),
shapeTrbl = asTRBL(shape),
shapeNewTrbl = asTRBL(newBounds),
trblResize = getTRBLResize(shape, newBounds),
resizeNeeded = [];
allLanes.forEach(function(other) {
if (other === shape) {
return;
}
var topResize = 0,
rightResize = trblResize.right,
bottomResize = 0,
leftResize = trblResize.left;
var otherTrbl = asTRBL(other);
if (trblResize.top) {
if (abs$4(otherTrbl.bottom - shapeTrbl.top) < 10) {
bottomResize = shapeNewTrbl.top - otherTrbl.bottom;
}
if (abs$4(otherTrbl.top - shapeTrbl.top) < 5) {
topResize = shapeNewTrbl.top - otherTrbl.top;
}
}
if (trblResize.bottom) {
if (abs$4(otherTrbl.top - shapeTrbl.bottom) < 10) {
topResize = shapeNewTrbl.bottom - otherTrbl.top;
}
if (abs$4(otherTrbl.bottom - shapeTrbl.bottom) < 5) {
bottomResize = shapeNewTrbl.bottom - otherTrbl.bottom;
}
}
if (topResize || rightResize || bottomResize || leftResize) {
resizeNeeded.push({
shape: other,
newBounds: resizeTRBL(other, {
top: topResize,
right: rightResize,
bottom: bottomResize,
left: leftResize
})
});
}
});
return resizeNeeded;
}
/**
* A provider for BPMN 2.0 elements context pad
*/
function ContextPadProvider(
config, injector, eventBus,
contextPad, modeling, elementFactory,
connect, create, popupMenu,
canvas, rules, translate) {
config = config || {};
contextPad.registerProvider(this);
this._contextPad = contextPad;
this._modeling = modeling;
this._elementFactory = elementFactory;
this._connect = connect;
this._create = create;
this._popupMenu = popupMenu;
this._canvas = canvas;
this._rules = rules;
this._translate = translate;
if (config.autoPlace !== false) {
this._autoPlace = injector.get('autoPlace', false);
}
eventBus.on('create.end', 250, function(event) {
var context = event.context,
shape = context.shape;
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
return;
}
var entries = contextPad.getEntries(shape);
if (entries.replace) {
entries.replace.action.click(event, shape);
}
});
}
ContextPadProvider.$inject = [
'config.contextPad',
'injector',
'eventBus',
'contextPad',
'modeling',
'elementFactory',
'connect',
'create',
'popupMenu',
'canvas',
'rules',
'translate'
];
ContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) {
var modeling = this._modeling;
var actions = {};
if (this._isDeleteAllowed(elements)) {
assign(actions, {
'delete': {
group: 'edit',
className: 'bpmn-icon-trash',
title: this._translate('Remove'),
action: {
click: function(event, elements) {
modeling.removeElements(elements.slice());
}
}
}
});
}
return actions;
};
/**
* @param {djs.model.Base[]} elements
* @return {boolean}
*/
ContextPadProvider.prototype._isDeleteAllowed = function(elements) {
var baseAllowed = this._rules.allowed('elements.delete', {
elements: elements
});
if (isArray$3(baseAllowed)) {
return every(baseAllowed, function(element) {
return includes$7(baseAllowed, element);
});
}
return baseAllowed;
};
ContextPadProvider.prototype.getContextPadEntries = function(element) {
var contextPad = this._contextPad,
modeling = this._modeling,
elementFactory = this._elementFactory,
connect = this._connect,
create = this._create,
popupMenu = this._popupMenu,
canvas = this._canvas,
rules = this._rules,
autoPlace = this._autoPlace,
translate = this._translate;
var actions = {};
if (element.type === 'label') {
return actions;
}
var businessObject = element.businessObject;
function startConnect(event, element) {
connect.start(event, element);
}
function removeElement(e, element) {
modeling.removeElements([ element ]);
}
function getReplaceMenuPosition(element) {
var Y_OFFSET = 5;
var diagramContainer = canvas.getContainer(),
pad = contextPad.getPad(element).html;
var diagramRect = diagramContainer.getBoundingClientRect(),
padRect = pad.getBoundingClientRect();
var top = padRect.top - diagramRect.top;
var left = padRect.left - diagramRect.left;
var pos = {
x: left,
y: top + padRect.height + Y_OFFSET
};
return pos;
}
/**
* Create an append action
*
* @param {string} type
* @param {string} className
* @param {string} [title]
* @param {Object} [options]
*
* @return {Object} descriptor
*/
function appendAction(type, className, title, options) {
if (typeof title !== 'string') {
options = title;
title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
}
function appendStart(event, element) {
var shape = elementFactory.createShape(assign({ type: type }, options));
create.start(event, shape, {
source: element
});
}
var append = autoPlace ? function(event, element) {
var shape = elementFactory.createShape(assign({ type: type }, options));
autoPlace.append(element, shape);
} : appendStart;
return {
group: 'model',
className: className,
title: title,
action: {
dragstart: appendStart,
click: append
}
};
}
function splitLaneHandler(count) {
return function(event, element) {
// actual split
modeling.splitLane(element, count);
// refresh context pad after split to
// get rid of split icons
contextPad.open(element, true);
};
}
if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(element)) {
var childLanes = getChildLanes(element);
assign(actions, {
'lane-insert-above': {
group: 'lane-insert-above',
className: 'bpmn-icon-lane-insert-above',
title: translate('Add Lane above'),
action: {
click: function(event, element) {
modeling.addLane(element, 'top');
}
}
}
});
if (childLanes.length < 2) {
if (element.height >= 120) {
assign(actions, {
'lane-divide-two': {
group: 'lane-divide',
className: 'bpmn-icon-lane-divide-two',
title: translate('Divide into two Lanes'),
action: {
click: splitLaneHandler(2)
}
}
});
}
if (element.height >= 180) {
assign(actions, {
'lane-divide-three': {
group: 'lane-divide',
className: 'bpmn-icon-lane-divide-three',
title: translate('Divide into three Lanes'),
action: {
click: splitLaneHandler(3)
}
}
});
}
}
assign(actions, {
'lane-insert-below': {
group: 'lane-insert-below',
className: 'bpmn-icon-lane-insert-below',
title: translate('Add Lane below'),
action: {
click: function(event, element) {
modeling.addLane(element, 'bottom');
}
}
}
});
}
if (is$1(businessObject, 'bpmn:FlowNode')) {
if (is$1(businessObject, 'bpmn:EventBasedGateway')) {
assign(actions, {
'append.receive-task': appendAction(
'bpmn:ReceiveTask',
'bpmn-icon-receive-task',
translate('Append ReceiveTask')
),
'append.message-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-message',
translate('Append MessageIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:MessageEventDefinition' }
),
'append.timer-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-timer',
translate('Append TimerIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:TimerEventDefinition' }
),
'append.condition-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-condition',
translate('Append ConditionIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
),
'append.signal-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-signal',
translate('Append SignalIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:SignalEventDefinition' }
)
});
} else
if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
assign(actions, {
'append.compensation-activity':
appendAction(
'bpmn:Task',
'bpmn-icon-task',
translate('Append compensation activity'),
{
isForCompensation: true
}
)
});
} else
if (!is$1(businessObject, 'bpmn:EndEvent') &&
!businessObject.isForCompensation &&
!isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
!isEventSubProcess(businessObject)) {
assign(actions, {
'append.end-event': appendAction(
'bpmn:EndEvent',
'bpmn-icon-end-event-none',
translate('Append EndEvent')
),
'append.gateway': appendAction(
'bpmn:ExclusiveGateway',
'bpmn-icon-gateway-none',
translate('Append Gateway')
),
'append.append-task': appendAction(
'bpmn:Task',
'bpmn-icon-task',
translate('Append Task')
),
'append.intermediate-event': appendAction(
'bpmn:IntermediateThrowEvent',
'bpmn-icon-intermediate-event-none',
translate('Append Intermediate/Boundary Event')
)
});
}
}
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
// Replace menu entry
assign(actions, {
'replace': {
group: 'edit',
className: 'bpmn-icon-screw-wrench',
title: translate('Change type'),
action: {
click: function(event, element) {
var position = assign(getReplaceMenuPosition(element), {
cursor: { x: event.x, y: event.y }
});
popupMenu.open(element, 'bpmn-replace', position);
}
}
}
});
}
if (is$1(businessObject, 'bpmn:SequenceFlow')) {
assign(actions, {
'append.text-annotation': appendAction(
'bpmn:TextAnnotation',
'bpmn-icon-text-annotation'
)
});
}
if (
isAny(businessObject, [
'bpmn:FlowNode',
'bpmn:InteractionNode',
'bpmn:DataObjectReference',
'bpmn:DataStoreReference',
])
) {
assign(actions, {
'append.text-annotation': appendAction(
'bpmn:TextAnnotation',
'bpmn-icon-text-annotation'
),
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate(
'Connect using ' +
(businessObject.isForCompensation
? ''
: 'Sequence/MessageFlow or ') +
'Association'
),
action: {
click: startConnect,
dragstart: startConnect,
},
},
});
}
if (is$1(businessObject, 'bpmn:TextAnnotation')) {
assign(actions, {
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('Connect using Association'),
action: {
click: startConnect,
dragstart: startConnect,
},
},
});
}
if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
assign(actions, {
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('Connect using DataInputAssociation'),
action: {
click: startConnect,
dragstart: startConnect
}
}
});
}
if (is$1(businessObject, 'bpmn:Group')) {
assign(actions, {
'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
});
}
// delete element entry, only show if allowed by rules
var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });
if (isArray$3(deleteAllowed)) {
// was the element returned as a deletion candidate?
deleteAllowed = deleteAllowed[0] === element;
}
if (deleteAllowed) {
assign(actions, {
'delete': {
group: 'edit',
className: 'bpmn-icon-trash',
title: translate('Remove'),
action: {
click: removeElement
}
}
});
}
return actions;
};
// helpers /////////
function isEventType(eventBo, type, definition) {
var isType = eventBo.$instanceOf(type);
var isDefinition = false;
var definitions = eventBo.eventDefinitions || [];
forEach$1(definitions, function(def) {
if (def.$type === definition) {
isDefinition = true;
}
});
return isType && isDefinition;
}
function includes$7(array, item) {
return array.indexOf(item) !== -1;
}
var ContextPadModule = {
__depends__: [
DirectEditingModule,
ContextPadModule$1,
SelectionModule,
ConnectModule,
CreateModule,
PopupMenuModule
],
__init__: [ 'contextPadProvider' ],
contextPadProvider: [ 'type', ContextPadProvider ]
};
var AXIS_DIMENSIONS = {
horizontal: [ 'x', 'width' ],
vertical: [ 'y', 'height' ]
};
var THRESHOLD = 5;
/**
* Groups and filters elements and then trigger even distribution.
*/
function DistributeElements$1(modeling, rules) {
this._modeling = modeling;
this._filters = [];
this.registerFilter(function(elements) {
var allowed = rules.allowed('elements.distribute', { elements: elements });
if (isArray$3(allowed)) {
return allowed;
}
return allowed ? elements : [];
});
}
DistributeElements$1.$inject = [ 'modeling', 'rules' ];
/**
* Registers filter functions that allow external parties to filter
* out certain elements.
*
* @param {Function} filterFn
*/
DistributeElements$1.prototype.registerFilter = function(filterFn) {
if (typeof filterFn !== 'function') {
throw new Error('the filter has to be a function');
}
this._filters.push(filterFn);
};
/**
* Distributes the elements with a given orientation
*
* @param {Array} elements
* @param {string} orientation
*/
DistributeElements$1.prototype.trigger = function(elements, orientation) {
var modeling = this._modeling;
var groups,
distributableElements;
if (elements.length < 3) {
return;
}
this._setOrientation(orientation);
distributableElements = this._filterElements(elements);
groups = this._createGroups(distributableElements);
// nothing to distribute
if (groups.length <= 2) {
return;
}
modeling.distributeElements(groups, this._axis, this._dimension);
return groups;
};
/**
* Filters the elements with provided filters by external parties
*
* @param {Array[Elements]} elements
*
* @return {Array[Elements]}
*/
DistributeElements$1.prototype._filterElements = function(elements) {
var filters = this._filters,
axis = this._axis,
dimension = this._dimension,
distributableElements = [].concat(elements);
if (!filters.length) {
return elements;
}
forEach$1(filters, function(filterFn) {
distributableElements = filterFn(distributableElements, axis, dimension);
});
return distributableElements;
};
/**
* Create range (min, max) groups. Also tries to group elements
* together that share the same range.
*
* @example
* var distributableElements = [
* {
* range: {
* min: 100,
* max: 200
* },
* elements: [ { id: 'shape1', .. }]
* }
* ]
*
* @param {Array} elements
*
* @return {Array[Objects]}
*/
DistributeElements$1.prototype._createGroups = function(elements) {
var rangeGroups = [],
self = this,
axis = this._axis,
dimension = this._dimension;
if (!axis) {
throw new Error('must have a defined "axis" and "dimension"');
}
// sort by 'left->right' or 'top->bottom'
var sortedElements = sortBy(elements, axis);
forEach$1(sortedElements, function(element, idx) {
var elementRange = self._findRange(element, axis, dimension),
range;
var previous = rangeGroups[rangeGroups.length - 1];
if (previous && self._hasIntersection(previous.range, elementRange)) {
rangeGroups[rangeGroups.length - 1].elements.push(element);
} else {
range = { range: elementRange, elements: [ element ] };
rangeGroups.push(range);
}
});
return rangeGroups;
};
/**
* Maps a direction to the according axis and dimension
*
* @param {string} direction 'horizontal' or 'vertical'
*/
DistributeElements$1.prototype._setOrientation = function(direction) {
var orientation = AXIS_DIMENSIONS[direction];
this._axis = orientation[0];
this._dimension = orientation[1];
};
/**
* Checks if the two ranges intercept each other
*
* @param {Object} rangeA {min, max}
* @param {Object} rangeB {min, max}
*
* @return {boolean}
*/
DistributeElements$1.prototype._hasIntersection = function(rangeA, rangeB) {
return Math.max(rangeA.min, rangeA.max) >= Math.min(rangeB.min, rangeB.max) &&
Math.min(rangeA.min, rangeA.max) <= Math.max(rangeB.min, rangeB.max);
};
/**
* Returns the min and max values for an element
*
* @param {Bounds} element
* @param {string} axis
* @param {string} dimension
*
* @return {{ min: number, max: number }}
*/
DistributeElements$1.prototype._findRange = function(element) {
var axis = element[this._axis],
dimension = element[this._dimension];
return {
min: axis + THRESHOLD,
max: axis + dimension - THRESHOLD
};
};
var DistributeElementsModule$1 = {
__init__: [ 'distributeElements' ],
distributeElements: [ 'type', DistributeElements$1 ]
};
/**
* Registers element exclude filters for elements that
* currently do not support distribution.
*/
function BpmnDistributeElements(distributeElements, eventBus, rules) {
RuleProvider.call(this, eventBus);
}
BpmnDistributeElements.$inject = [ 'distributeElements', 'eventBus', 'rules' ];
e(BpmnDistributeElements, RuleProvider);
BpmnDistributeElements.prototype.init = function() {
this.addRule('elements.distribute', function(context) {
var elements = context.elements;
elements = filter(elements, function(element) {
var cannotDistribute = isAny(element, [
'bpmn:Association',
'bpmn:BoundaryEvent',
'bpmn:DataInputAssociation',
'bpmn:DataOutputAssociation',
'bpmn:Lane',
'bpmn:MessageFlow',
'bpmn:SequenceFlow',
'bpmn:TextAnnotation'
]);
return !(element.labelTarget || cannotDistribute);
});
// filter out elements which are children of any of the selected elements
elements = getParents$1(elements);
if (elements.length < 3) {
return false;
}
return elements;
});
};
/**
* To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`,
* and then replace respective icons with the optimized data URIs in `./dist`.
*/
var icons = {
horizontal: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linejoin%3Around%22%20d%3D%22M450%20400V150h900v250%22%2F%3E%3Crect%20x%3D%22150%22%20y%3D%22450%22%20width%3D%22600%22%20height%3D%221200%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%221050%22%20y%3D%22450%22%20width%3D%22600%22%20height%3D%22800%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
vertical: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201800%201800%22%3E%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bstroke-linejoin%3Around%22%20d%3D%22M400%201350H150V450h250%22%2F%3E%3Crect%20x%3D%22450%22%20y%3D%22150%22%20width%3D%221200%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3Anone%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%22%2F%3E%3Crect%20x%3D%22450%22%20y%3D%221050%22%20width%3D%22800%22%20height%3D%22600%22%20rx%3D%221%22%20style%3D%22fill%3AcurrentColor%3Bstroke%3AcurrentColor%3Bstroke-width%3A100%3Bopacity%3A.5%22%2F%3E%3C%2Fsvg%3E',
};
var LOW_PRIORITY$g = 900;
/**
* A provider for distribute elements popup menu.
*/
function DistributeElementsMenuProvider(
popupMenu, distributeElements, translate, rules) {
this._distributeElements = distributeElements;
this._translate = translate;
this._popupMenu = popupMenu;
this._rules = rules;
popupMenu.registerProvider('align-elements', LOW_PRIORITY$g, this);
}
DistributeElementsMenuProvider.$inject = [
'popupMenu',
'distributeElements',
'translate',
'rules'
];
DistributeElementsMenuProvider.prototype.getPopupMenuEntries = function(elements) {
var entries = {};
if (this._isAllowed(elements)) {
assign(entries, this._getEntries(elements));
}
return entries;
};
DistributeElementsMenuProvider.prototype._isAllowed = function(elements) {
return this._rules.allowed('elements.distribute', { elements: elements });
};
DistributeElementsMenuProvider.prototype._getEntries = function(elements) {
var distributeElements = this._distributeElements,
translate = this._translate,
popupMenu = this._popupMenu;
var entries = {
'distribute-elements-horizontal': {
group: 'distribute',
title: translate('Distribute elements horizontally'),
className: 'bjs-align-elements-menu-entry',
imageUrl: icons['horizontal'],
action: function(event, entry) {
distributeElements.trigger(elements, 'horizontal');
popupMenu.close();
}
},
'distribute-elements-vertical': {
group: 'distribute',
title: translate('Distribute elements vertically'),
imageUrl: icons['vertical'],
action: function(event, entry) {
distributeElements.trigger(elements, 'vertical');
popupMenu.close();
}
},
};
return entries;
};
var DistributeElementsModule = {
__depends__: [
PopupMenuModule$1,
DistributeElementsModule$1
],
__init__: [
'bpmnDistributeElements',
'distributeElementsMenuProvider'
],
bpmnDistributeElements: [ 'type', BpmnDistributeElements ],
distributeElementsMenuProvider: [ 'type', DistributeElementsMenuProvider ]
};
var NOT_REGISTERED_ERROR = 'is not a registered action',
IS_REGISTERED_ERROR = 'is already registered';
/**
* An interface that provides access to modeling actions by decoupling
* the one who requests the action to be triggered and the trigger itself.
*
* It's possible to add new actions by registering them with ´registerAction´
* and likewise unregister existing ones with ´unregisterAction´.
*
*
* ## Life-Cycle and configuration
*
* The editor actions will wait for diagram initialization before
* registering default actions _and_ firing an `editorActions.init` event.
*
* Interested parties may listen to the `editorActions.init` event with
* low priority to check, which actions got registered. Other components
* may use the event to register their own actions via `registerAction`.
*
* @param {EventBus} eventBus
* @param {Injector} injector
*/
function EditorActions(eventBus, injector) {
// initialize actions
this._actions = {};
var self = this;
eventBus.on('diagram.init', function() {
// all diagram modules got loaded; check which ones
// are available and register the respective default actions
self._registerDefaultActions(injector);
// ask interested parties to register available editor
// actions on diagram initialization
eventBus.fire('editorActions.init', {
editorActions: self
});
});
}
EditorActions.$inject = [
'eventBus',
'injector'
];
/**
* Register default actions.
*
* @param {Injector} injector
*/
EditorActions.prototype._registerDefaultActions = function(injector) {
// (1) retrieve optional components to integrate with
var commandStack = injector.get('commandStack', false);
var modeling = injector.get('modeling', false);
var selection = injector.get('selection', false);
var zoomScroll = injector.get('zoomScroll', false);
var copyPaste = injector.get('copyPaste', false);
var canvas = injector.get('canvas', false);
var rules = injector.get('rules', false);
var keyboardMove = injector.get('keyboardMove', false);
var keyboardMoveSelection = injector.get('keyboardMoveSelection', false);
// (2) check components and register actions
if (commandStack) {
this.register('undo', function() {
commandStack.undo();
});
this.register('redo', function() {
commandStack.redo();
});
}
if (copyPaste && selection) {
this.register('copy', function() {
var selectedElements = selection.get();
if (selectedElements.length) {
return copyPaste.copy(selectedElements);
}
});
}
if (copyPaste) {
this.register('paste', function() {
copyPaste.paste();
});
}
if (zoomScroll) {
this.register('stepZoom', function(opts) {
zoomScroll.stepZoom(opts.value);
});
}
if (canvas) {
this.register('zoom', function(opts) {
canvas.zoom(opts.value);
});
}
if (modeling && selection && rules) {
this.register('removeSelection', function() {
var selectedElements = selection.get();
if (!selectedElements.length) {
return;
}
var allowed = rules.allowed('elements.delete', { elements: selectedElements }),
removableElements;
if (allowed === false) {
return;
}
else if (isArray$3(allowed)) {
removableElements = allowed;
}
else {
removableElements = selectedElements;
}
if (removableElements.length) {
modeling.removeElements(removableElements.slice());
}
});
}
if (keyboardMove) {
this.register('moveCanvas', function(opts) {
keyboardMove.moveCanvas(opts);
});
}
if (keyboardMoveSelection) {
this.register('moveSelection', function(opts) {
keyboardMoveSelection.moveSelection(opts.direction, opts.accelerated);
});
}
};
/**
* Triggers a registered action
*
* @param {string} action
* @param {Object} opts
*
* @return {Unknown} Returns what the registered listener returns
*/
EditorActions.prototype.trigger = function(action, opts) {
if (!this._actions[action]) {
throw error(action, NOT_REGISTERED_ERROR);
}
return this._actions[action](opts);
};
/**
* Registers a collections of actions.
* The key of the object will be the name of the action.
*
* @example
* ´´´
* var actions = {
* spaceTool: function() {
* spaceTool.activateSelection();
* },
* lassoTool: function() {
* lassoTool.activateSelection();
* }
* ];
*
* editorActions.register(actions);
*
* editorActions.isRegistered('spaceTool'); // true
* ´´´
*
* @param {Object} actions
*/
EditorActions.prototype.register = function(actions, listener) {
var self = this;
if (typeof actions === 'string') {
return this._registerAction(actions, listener);
}
forEach$1(actions, function(listener, action) {
self._registerAction(action, listener);
});
};
/**
* Registers a listener to an action key
*
* @param {string} action
* @param {Function} listener
*/
EditorActions.prototype._registerAction = function(action, listener) {
if (this.isRegistered(action)) {
throw error(action, IS_REGISTERED_ERROR);
}
this._actions[action] = listener;
};
/**
* Unregister an existing action
*
* @param {string} action
*/
EditorActions.prototype.unregister = function(action) {
if (!this.isRegistered(action)) {
throw error(action, NOT_REGISTERED_ERROR);
}
this._actions[action] = undefined;
};
/**
* Returns the number of actions that are currently registered
*
* @return {number}
*/
EditorActions.prototype.getActions = function() {
return Object.keys(this._actions);
};
/**
* Checks wether the given action is registered
*
* @param {string} action
*
* @return {boolean}
*/
EditorActions.prototype.isRegistered = function(action) {
return !!this._actions[action];
};
function error(action, message) {
return new Error(action + ' ' + message);
}
var EditorActionsModule$1 = {
__init__: [ 'editorActions' ],
editorActions: [ 'type', EditorActions ]
};
/**
* Registers and executes BPMN specific editor actions.
*
* @param {Injector} injector
*/
function BpmnEditorActions(injector) {
injector.invoke(EditorActions, this);
}
e(BpmnEditorActions, EditorActions);
BpmnEditorActions.$inject = [
'injector'
];
/**
* Register default actions.
*
* @param {Injector} injector
*/
BpmnEditorActions.prototype._registerDefaultActions = function(injector) {
// (0) invoke super method
EditorActions.prototype._registerDefaultActions.call(this, injector);
// (1) retrieve optional components to integrate with
var canvas = injector.get('canvas', false);
var elementRegistry = injector.get('elementRegistry', false);
var selection = injector.get('selection', false);
var spaceTool = injector.get('spaceTool', false);
var lassoTool = injector.get('lassoTool', false);
var handTool = injector.get('handTool', false);
var globalConnect = injector.get('globalConnect', false);
var distributeElements = injector.get('distributeElements', false);
var alignElements = injector.get('alignElements', false);
var directEditing = injector.get('directEditing', false);
var searchPad = injector.get('searchPad', false);
var modeling = injector.get('modeling', false);
// (2) check components and register actions
if (canvas && elementRegistry && selection) {
this._registerAction('selectElements', function() {
// select all elements except for the invisible
// root element
var rootElement = canvas.getRootElement();
var elements = elementRegistry.filter(function(element) {
return element !== rootElement;
});
selection.select(elements);
return elements;
});
}
if (spaceTool) {
this._registerAction('spaceTool', function() {
spaceTool.toggle();
});
}
if (lassoTool) {
this._registerAction('lassoTool', function() {
lassoTool.toggle();
});
}
if (handTool) {
this._registerAction('handTool', function() {
handTool.toggle();
});
}
if (globalConnect) {
this._registerAction('globalConnectTool', function() {
globalConnect.toggle();
});
}
if (selection && distributeElements) {
this._registerAction('distributeElements', function(opts) {
var currentSelection = selection.get(),
type = opts.type;
if (currentSelection.length) {
distributeElements.trigger(currentSelection, type);
}
});
}
if (selection && alignElements) {
this._registerAction('alignElements', function(opts) {
var currentSelection = selection.get(),
aligneableElements = [],
type = opts.type;
if (currentSelection.length) {
aligneableElements = filter(currentSelection, function(element) {
return !is$1(element, 'bpmn:Lane');
});
alignElements.trigger(aligneableElements, type);
}
});
}
if (selection && modeling) {
this._registerAction('setColor', function(opts) {
var currentSelection = selection.get();
if (currentSelection.length) {
modeling.setColor(currentSelection, opts);
}
});
}
if (selection && directEditing) {
this._registerAction('directEditing', function() {
var currentSelection = selection.get();
if (currentSelection.length) {
directEditing.activate(currentSelection[0]);
}
});
}
if (searchPad) {
this._registerAction('find', function() {
searchPad.toggle();
});
}
if (canvas && modeling) {
this._registerAction('moveToOrigin', function() {
var rootElement = canvas.getRootElement(),
boundingBox,
elements;
if (is$1(rootElement, 'bpmn:Collaboration')) {
elements = elementRegistry.filter(function(element) {
return is$1(element.parent, 'bpmn:Collaboration');
});
} else {
elements = elementRegistry.filter(function(element) {
return element !== rootElement && !is$1(element.parent, 'bpmn:SubProcess');
});
}
boundingBox = getBBox(elements);
modeling.moveElements(
elements,
{ x: -boundingBox.x, y: -boundingBox.y },
rootElement
);
});
}
};
var EditorActionsModule = {
__depends__: [
EditorActionsModule$1
],
editorActions: [ 'type', BpmnEditorActions ]
};
function BpmnGridSnapping(eventBus) {
eventBus.on([
'create.init',
'shape.move.init'
], function(event) {
var context = event.context,
shape = event.shape;
if (isAny(shape, [
'bpmn:Participant',
'bpmn:SubProcess',
'bpmn:TextAnnotation'
])) {
if (!context.gridSnappingContext) {
context.gridSnappingContext = {};
}
context.gridSnappingContext.snapLocation = 'top-left';
}
});
}
BpmnGridSnapping.$inject = [ 'eventBus' ];
var SPACING = 10;
function quantize(value, quantum, fn) {
if (!fn) {
fn = 'round';
}
return Math[ fn ](value / quantum) * quantum;
}
var LOWER_PRIORITY$1 = 1200;
var LOW_PRIORITY$f = 800;
/**
* Basic grid snapping that covers connecting, creating, moving, resizing shapes, moving bendpoints
* and connection segments.
*/
function GridSnapping(elementRegistry, eventBus, config) {
var active = !config || config.active !== false;
this._eventBus = eventBus;
var self = this;
eventBus.on('diagram.init', LOW_PRIORITY$f, function() {
self.setActive(active);
});
eventBus.on([
'create.move',
'create.end',
'bendpoint.move.move',
'bendpoint.move.end',
'connect.move',
'connect.end',
'connectionSegment.move.move',
'connectionSegment.move.end',
'resize.move',
'resize.end',
'shape.move.move',
'shape.move.end'
], LOWER_PRIORITY$1, function(event) {
var originalEvent = event.originalEvent;
if (!self.active || (originalEvent && isCmd(originalEvent))) {
return;
}
var context = event.context,
gridSnappingContext = context.gridSnappingContext;
if (!gridSnappingContext) {
gridSnappingContext = context.gridSnappingContext = {};
}
[ 'x', 'y' ].forEach(function(axis) {
var options = {};
// allow snapping with offset
var snapOffset = getSnapOffset(event, axis, elementRegistry);
if (snapOffset) {
options.offset = snapOffset;
}
// allow snapping with min and max
var snapConstraints = getSnapConstraints(event, axis);
if (snapConstraints) {
assign(options, snapConstraints);
}
if (!isSnapped(event, axis)) {
self.snapEvent(event, axis, options);
}
});
});
}
/**
* Snap an events x or y with optional min, max and offset.
*
* @param {Object} event
* @param {string} axis
* @param {number} [options.min]
* @param {number} [options.max]
* @param {number} [options.offset]
*/
GridSnapping.prototype.snapEvent = function(event, axis, options) {
var snappedValue = this.snapValue(event[ axis ], options);
setSnapped(event, axis, snappedValue);
};
/**
* Expose grid spacing for third parties (i.e. extensions).
*
* @return {number} spacing of grid dots
*/
GridSnapping.prototype.getGridSpacing = function() {
return SPACING;
};
/**
* Snap value with optional min, max and offset.
*
* @param {number} value
* @param {Object} options
* @param {number} [options.min]
* @param {number} [options.max]
* @param {number} [options.offset]
*/
GridSnapping.prototype.snapValue = function(value, options) {
var offset = 0;
if (options && options.offset) {
offset = options.offset;
}
value += offset;
value = quantize(value, SPACING);
var min, max;
if (options && options.min) {
min = options.min;
if (isNumber(min)) {
min = quantize(min + offset, SPACING, 'ceil');
value = Math.max(value, min);
}
}
if (options && options.max) {
max = options.max;
if (isNumber(max)) {
max = quantize(max + offset, SPACING, 'floor');
value = Math.min(value, max);
}
}
value -= offset;
return value;
};
GridSnapping.prototype.isActive = function() {
return this.active;
};
GridSnapping.prototype.setActive = function(active) {
this.active = active;
this._eventBus.fire('gridSnapping.toggle', { active: active });
};
GridSnapping.prototype.toggleActive = function() {
this.setActive(!this.active);
};
GridSnapping.$inject = [
'elementRegistry',
'eventBus',
'config.gridSnapping'
];
// helpers //////////
/**
* Get minimum and maximum snap constraints.
* Constraints are cached.
*
* @param {Object} event
* @param {Object} event.context
* @param {string} axis
*
* @returns {boolean|Object}
*/
function getSnapConstraints(event, axis) {
var context = event.context,
createConstraints = context.createConstraints,
resizeConstraints = context.resizeConstraints || {},
gridSnappingContext = context.gridSnappingContext,
snapConstraints = gridSnappingContext.snapConstraints;
// cache snap constraints
if (snapConstraints && snapConstraints[ axis ]) {
return snapConstraints[ axis ];
}
if (!snapConstraints) {
snapConstraints = gridSnappingContext.snapConstraints = {};
}
if (!snapConstraints[ axis ]) {
snapConstraints[ axis ] = {};
}
var direction = context.direction;
// create
if (createConstraints) {
if (isHorizontal$3(axis)) {
snapConstraints.x.min = createConstraints.left;
snapConstraints.x.max = createConstraints.right;
} else {
snapConstraints.y.min = createConstraints.top;
snapConstraints.y.max = createConstraints.bottom;
}
}
// resize
var minResizeConstraints = resizeConstraints.min,
maxResizeConstraints = resizeConstraints.max;
if (minResizeConstraints) {
if (isHorizontal$3(axis)) {
if (isWest(direction)) {
snapConstraints.x.max = minResizeConstraints.left;
} else {
snapConstraints.x.min = minResizeConstraints.right;
}
} else {
if (isNorth(direction)) {
snapConstraints.y.max = minResizeConstraints.top;
} else {
snapConstraints.y.min = minResizeConstraints.bottom;
}
}
}
if (maxResizeConstraints) {
if (isHorizontal$3(axis)) {
if (isWest(direction)) {
snapConstraints.x.min = maxResizeConstraints.left;
} else {
snapConstraints.x.max = maxResizeConstraints.right;
}
} else {
if (isNorth(direction)) {
snapConstraints.y.min = maxResizeConstraints.top;
} else {
snapConstraints.y.max = maxResizeConstraints.bottom;
}
}
}
return snapConstraints[ axis ];
}
/**
* Get snap offset.
* Offset is cached.
*
* @param {Object} event
* @param {string} axis
* @param {ElementRegistry} elementRegistry
*
* @returns {number}
*/
function getSnapOffset(event, axis, elementRegistry) {
var context = event.context,
shape = event.shape,
gridSnappingContext = context.gridSnappingContext,
snapLocation = gridSnappingContext.snapLocation,
snapOffset = gridSnappingContext.snapOffset;
// cache snap offset
if (snapOffset && isNumber(snapOffset[ axis ])) {
return snapOffset[ axis ];
}
if (!snapOffset) {
snapOffset = gridSnappingContext.snapOffset = {};
}
if (!isNumber(snapOffset[ axis ])) {
snapOffset[ axis ] = 0;
}
if (!shape) {
return snapOffset[ axis ];
}
if (!elementRegistry.get(shape.id)) {
if (isHorizontal$3(axis)) {
snapOffset[ axis ] += shape[ axis ] + shape.width / 2;
} else {
snapOffset[ axis ] += shape[ axis ] + shape.height / 2;
}
}
if (!snapLocation) {
return snapOffset[ axis ];
}
if (axis === 'x') {
if (/left/.test(snapLocation)) {
snapOffset[ axis ] -= shape.width / 2;
} else if (/right/.test(snapLocation)) {
snapOffset[ axis ] += shape.width / 2;
}
} else {
if (/top/.test(snapLocation)) {
snapOffset[ axis ] -= shape.height / 2;
} else if (/bottom/.test(snapLocation)) {
snapOffset[ axis ] += shape.height / 2;
}
}
return snapOffset[ axis ];
}
function isHorizontal$3(axis) {
return axis === 'x';
}
function isNorth(direction) {
return direction.indexOf('n') !== -1;
}
function isWest(direction) {
return direction.indexOf('w') !== -1;
}
/**
* Integrates resizing with grid snapping.
*/
function ResizeBehavior$1(eventBus, gridSnapping) {
CommandInterceptor.call(this, eventBus);
this._gridSnapping = gridSnapping;
var self = this;
this.preExecute('shape.resize', function(event) {
var context = event.context,
hints = context.hints || {},
autoResize = hints.autoResize;
if (!autoResize) {
return;
}
var shape = context.shape,
newBounds = context.newBounds;
if (isString(autoResize)) {
context.newBounds = self.snapComplex(newBounds, autoResize);
} else {
context.newBounds = self.snapSimple(shape, newBounds);
}
});
}
ResizeBehavior$1.$inject = [
'eventBus',
'gridSnapping',
'modeling'
];
e(ResizeBehavior$1, CommandInterceptor);
/**
* Snap width and height in relation to center.
*
* @param {djs.model.shape} shape
* @param {Bounds} newBounds
*
* @returns {Bounds} Snapped bounds.
*/
ResizeBehavior$1.prototype.snapSimple = function(shape, newBounds) {
var gridSnapping = this._gridSnapping;
newBounds.width = gridSnapping.snapValue(newBounds.width, {
min: newBounds.width
});
newBounds.height = gridSnapping.snapValue(newBounds.height, {
min: newBounds.height
});
newBounds.x = shape.x + (shape.width / 2) - (newBounds.width / 2);
newBounds.y = shape.y + (shape.height / 2) - (newBounds.height / 2);
return newBounds;
};
/**
* Snap x, y, width and height according to given directions.
*
* @param {Bounds} newBounds
* @param {string} directions - Directions as {n|w|s|e}.
*
* @returns {Bounds} Snapped bounds.
*/
ResizeBehavior$1.prototype.snapComplex = function(newBounds, directions) {
if (/w|e/.test(directions)) {
newBounds = this.snapHorizontally(newBounds, directions);
}
if (/n|s/.test(directions)) {
newBounds = this.snapVertically(newBounds, directions);
}
return newBounds;
};
/**
* Snap in one or both directions horizontally.
*
* @param {Bounds} newBounds
* @param {string} directions - Directions as {n|w|s|e}.
*
* @returns {Bounds} Snapped bounds.
*/
ResizeBehavior$1.prototype.snapHorizontally = function(newBounds, directions) {
var gridSnapping = this._gridSnapping,
west = /w/.test(directions),
east = /e/.test(directions);
var snappedNewBounds = {};
snappedNewBounds.width = gridSnapping.snapValue(newBounds.width, {
min: newBounds.width
});
if (east) {
// handle <we>
if (west) {
snappedNewBounds.x = gridSnapping.snapValue(newBounds.x, {
max: newBounds.x
});
snappedNewBounds.width += gridSnapping.snapValue(newBounds.x - snappedNewBounds.x, {
min: newBounds.x - snappedNewBounds.x
});
}
// handle <e>
else {
newBounds.x = newBounds.x + newBounds.width - snappedNewBounds.width;
}
}
// assign snapped x and width
assign(newBounds, snappedNewBounds);
return newBounds;
};
/**
* Snap in one or both directions vertically.
*
* @param {Bounds} newBounds
* @param {string} directions - Directions as {n|w|s|e}.
*
* @returns {Bounds} Snapped bounds.
*/
ResizeBehavior$1.prototype.snapVertically = function(newBounds, directions) {
var gridSnapping = this._gridSnapping,
north = /n/.test(directions),
south = /s/.test(directions);
var snappedNewBounds = {};
snappedNewBounds.height = gridSnapping.snapValue(newBounds.height, {
min: newBounds.height
});
if (north) {
// handle <ns>
if (south) {
snappedNewBounds.y = gridSnapping.snapValue(newBounds.y, {
max: newBounds.y
});
snappedNewBounds.height += gridSnapping.snapValue(newBounds.y - snappedNewBounds.y, {
min: newBounds.y - snappedNewBounds.y
});
}
// handle <n>
else {
newBounds.y = newBounds.y + newBounds.height - snappedNewBounds.height;
}
}
// assign snapped y and height
assign(newBounds, snappedNewBounds);
return newBounds;
};
var HIGH_PRIORITY$f = 2000;
/**
* Integrates space tool with grid snapping.
*/
function SpaceToolBehavior$1(eventBus, gridSnapping) {
eventBus.on([
'spaceTool.move',
'spaceTool.end'
], HIGH_PRIORITY$f, function(event) {
var context = event.context;
if (!context.initialized) {
return;
}
var axis = context.axis;
var snapped;
if (axis === 'x') {
// snap delta x to multiple of 10
snapped = gridSnapping.snapValue(event.dx);
event.x = event.x + snapped - event.dx;
event.dx = snapped;
} else {
// snap delta y to multiple of 10
snapped = gridSnapping.snapValue(event.dy);
event.y = event.y + snapped - event.dy;
event.dy = snapped;
}
});
}
SpaceToolBehavior$1.$inject = [
'eventBus',
'gridSnapping'
];
var GridSnappingBehaviorModule$1 = {
__init__: [
'gridSnappingResizeBehavior',
'gridSnappingSpaceToolBehavior'
],
gridSnappingResizeBehavior: [ 'type', ResizeBehavior$1 ],
gridSnappingSpaceToolBehavior: [ 'type', SpaceToolBehavior$1 ]
};
var GridSnappingModule$1 = {
__depends__: [ GridSnappingBehaviorModule$1 ],
__init__: [ 'gridSnapping' ],
gridSnapping: [ 'type', GridSnapping ]
};
var HIGH_PRIORITY$e = 2000;
function GridSnappingAutoPlaceBehavior(eventBus, gridSnapping) {
eventBus.on('autoPlace', HIGH_PRIORITY$e, function(context) {
var source = context.source,
sourceMid = getMid(source),
shape = context.shape;
var position = getNewShapePosition(source, shape);
[ 'x', 'y' ].forEach(function(axis) {
var options = {};
// do not snap if x/y equal
if (position[ axis ] === sourceMid[ axis ]) {
return;
}
if (position[ axis ] > sourceMid[ axis ]) {
options.min = position[ axis ];
} else {
options.max = position[ axis ];
}
if (is$1(shape, 'bpmn:TextAnnotation')) {
if (isHorizontal$2(axis)) {
options.offset = -shape.width / 2;
} else {
options.offset = -shape.height / 2;
}
}
position[ axis ] = gridSnapping.snapValue(position[ axis ], options);
});
// must be returned to be considered by auto place
return position;
});
}
GridSnappingAutoPlaceBehavior.$inject = [
'eventBus',
'gridSnapping'
];
// helpers //////////
function isHorizontal$2(axis) {
return axis === 'x';
}
var HIGHER_PRIORITY$4 = 1750;
function GridSnappingParticipantBehavior(canvas, eventBus, gridSnapping) {
eventBus.on([
'create.start',
'shape.move.start'
], HIGHER_PRIORITY$4, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (!is$1(shape, 'bpmn:Participant') ||
!is$1(rootElement, 'bpmn:Process') ||
!rootElement.children.length) {
return;
}
var createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
shape.width = gridSnapping.snapValue(shape.width, { min: shape.width });
shape.height = gridSnapping.snapValue(shape.height, { min: shape.height });
});
}
GridSnappingParticipantBehavior.$inject = [
'canvas',
'eventBus',
'gridSnapping'
];
var HIGH_PRIORITY$d = 3000;
/**
* Snaps connections with Manhattan layout.
*/
function GridSnappingLayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
CommandInterceptor.call(this, eventBus);
this._gridSnapping = gridSnapping;
var self = this;
this.postExecuted([
'connection.create',
'connection.layout'
], HIGH_PRIORITY$d, function(event) {
var context = event.context,
connection = context.connection,
hints = context.hints || {},
waypoints = connection.waypoints;
if (hints.connectionStart || hints.connectionEnd || hints.createElementsBehavior === false) {
return;
}
if (!hasMiddleSegments(waypoints)) {
return;
}
modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints));
});
}
GridSnappingLayoutConnectionBehavior.$inject = [
'eventBus',
'gridSnapping',
'modeling'
];
e(GridSnappingLayoutConnectionBehavior, CommandInterceptor);
/**
* Snap middle segments of a given connection.
*
* @param {Array<Point>} waypoints
*
* @returns {Array<Point>}
*/
GridSnappingLayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) {
var gridSnapping = this._gridSnapping,
snapped;
waypoints = waypoints.slice();
for (var i = 1; i < waypoints.length - 2; i++) {
snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]);
waypoints[i] = snapped[0];
waypoints[i + 1] = snapped[1];
}
return waypoints;
};
// helpers //////////
/**
* Check whether a connection has a middle segments.
*
* @param {Array} waypoints
*
* @returns {boolean}
*/
function hasMiddleSegments(waypoints) {
return waypoints.length > 3;
}
/**
* Check whether an alignment is horizontal.
*
* @param {string} aligned
*
* @returns {boolean}
*/
function horizontallyAligned(aligned) {
return aligned === 'h';
}
/**
* Check whether an alignment is vertical.
*
* @param {string} aligned
*
* @returns {boolean}
*/
function verticallyAligned(aligned) {
return aligned === 'v';
}
/**
* Get middle segments from a given connection.
*
* @param {Array} waypoints
*
* @returns {Array}
*/
function snapSegment(gridSnapping, segmentStart, segmentEnd) {
var aligned = pointsAligned(segmentStart, segmentEnd);
var snapped = {};
if (horizontallyAligned(aligned)) {
// snap horizontally
snapped.y = gridSnapping.snapValue(segmentStart.y);
}
if (verticallyAligned(aligned)) {
// snap vertically
snapped.x = gridSnapping.snapValue(segmentStart.x);
}
if ('x' in snapped || 'y' in snapped) {
segmentStart = assign({}, segmentStart, snapped);
segmentEnd = assign({}, segmentEnd, snapped);
}
return [ segmentStart, segmentEnd ];
}
var GridSnappingBehaviorModule = {
__init__: [
'gridSnappingAutoPlaceBehavior',
'gridSnappingParticipantBehavior',
'gridSnappingLayoutConnectionBehavior',
],
gridSnappingAutoPlaceBehavior: [ 'type', GridSnappingAutoPlaceBehavior ],
gridSnappingParticipantBehavior: [ 'type', GridSnappingParticipantBehavior ],
gridSnappingLayoutConnectionBehavior: [ 'type', GridSnappingLayoutConnectionBehavior ]
};
var GridSnappingModule = {
__depends__: [
GridSnappingModule$1,
GridSnappingBehaviorModule
],
__init__: [ 'bpmnGridSnapping' ],
bpmnGridSnapping: [ 'type', BpmnGridSnapping ]
};
var LABEL_WIDTH = 30,
LABEL_HEIGHT = 30;
/**
* BPMN-specific hit zones and interaction fixes.
*
* @param {EventBus} eventBus
* @param {InteractionEvents} interactionEvents
*/
function BpmnInteractionEvents(eventBus, interactionEvents) {
this._interactionEvents = interactionEvents;
var self = this;
eventBus.on([
'interactionEvents.createHit',
'interactionEvents.updateHit'
], function(context) {
var element = context.element,
gfx = context.gfx;
if (is$1(element, 'bpmn:Lane')) {
return self.createParticipantHit(element, gfx);
} else
if (is$1(element, 'bpmn:Participant')) {
if (isExpanded(element)) {
return self.createParticipantHit(element, gfx);
} else {
return self.createDefaultHit(element, gfx);
}
} else
if (is$1(element, 'bpmn:SubProcess')) {
if (isExpanded(element)) {
return self.createSubProcessHit(element, gfx);
} else {
return self.createDefaultHit(element, gfx);
}
}
});
}
BpmnInteractionEvents.$inject = [
'eventBus',
'interactionEvents'
];
BpmnInteractionEvents.prototype.createDefaultHit = function(element, gfx) {
this._interactionEvents.removeHits(gfx);
this._interactionEvents.createDefaultHit(element, gfx);
// indicate that we created a hit
return true;
};
BpmnInteractionEvents.prototype.createParticipantHit = function(element, gfx) {
// remove existing hits
this._interactionEvents.removeHits(gfx);
// add body hit
this._interactionEvents.createBoxHit(gfx, 'no-move', {
width: element.width,
height: element.height
});
// add outline hit
this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
width: element.width,
height: element.height
});
// add label hit
this._interactionEvents.createBoxHit(gfx, 'all', {
width: LABEL_WIDTH,
height: element.height
});
// indicate that we created a hit
return true;
};
BpmnInteractionEvents.prototype.createSubProcessHit = function(element, gfx) {
// remove existing hits
this._interactionEvents.removeHits(gfx);
// add body hit
this._interactionEvents.createBoxHit(gfx, 'no-move', {
width: element.width,
height: element.height
});
// add outline hit
this._interactionEvents.createBoxHit(gfx, 'click-stroke', {
width: element.width,
height: element.height
});
// add label hit
this._interactionEvents.createBoxHit(gfx, 'all', {
width: element.width,
height: LABEL_HEIGHT
});
// indicate that we created a hit
return true;
};
var InteractionEventsModule = {
__init__: [ 'bpmnInteractionEvents' ],
bpmnInteractionEvents: [ 'type', BpmnInteractionEvents ]
};
/**
* BPMN 2.0 specific keyboard bindings.
*
* @param {Injector} injector
*/
function BpmnKeyboardBindings(injector) {
injector.invoke(KeyboardBindings, this);
}
e(BpmnKeyboardBindings, KeyboardBindings);
BpmnKeyboardBindings.$inject = [
'injector'
];
/**
* Register available keyboard bindings.
*
* @param {Keyboard} keyboard
* @param {EditorActions} editorActions
*/
BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
// inherit default bindings
KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions);
/**
* Add keyboard binding if respective editor action
* is registered.
*
* @param {string} action name
* @param {Function} fn that implements the key binding
*/
function addListener(action, fn) {
if (editorActions.isRegistered(action)) {
keyboard.addListener(fn);
}
}
// select all elements
// CTRL + A
addListener('selectElements', function(context) {
var event = context.keyEvent;
if (keyboard.isKey([ 'a', 'A' ], event) && keyboard.isCmd(event)) {
editorActions.trigger('selectElements');
return true;
}
});
// search labels
// CTRL + F
addListener('find', function(context) {
var event = context.keyEvent;
if (keyboard.isKey([ 'f', 'F' ], event) && keyboard.isCmd(event)) {
editorActions.trigger('find');
return true;
}
});
// activate space tool
// S
addListener('spaceTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 's', 'S' ], event)) {
editorActions.trigger('spaceTool');
return true;
}
});
// activate lasso tool
// L
addListener('lassoTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'l', 'L' ], event)) {
editorActions.trigger('lassoTool');
return true;
}
});
// activate hand tool
// H
addListener('handTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'h', 'H' ], event)) {
editorActions.trigger('handTool');
return true;
}
});
// activate global connect tool
// C
addListener('globalConnectTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'c', 'C' ], event)) {
editorActions.trigger('globalConnectTool');
return true;
}
});
// activate direct editing
// E
addListener('directEditing', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey([ 'e', 'E' ], event)) {
editorActions.trigger('directEditing');
return true;
}
});
};
var KeyboardModule = {
__depends__: [
KeyboardModule$1
],
__init__: [ 'keyboardBindings' ],
keyboardBindings: [ 'type', BpmnKeyboardBindings ]
};
var DEFAULT_CONFIG = {
moveSpeed: 1,
moveSpeedAccelerated: 10
};
var HIGHER_PRIORITY$3 = 1500;
var LEFT = 'left';
var UP = 'up';
var RIGHT = 'right';
var DOWN = 'down';
var KEY_TO_DIRECTION = {
ArrowLeft: LEFT,
Left: LEFT,
ArrowUp: UP,
Up: UP,
ArrowRight: RIGHT,
Right: RIGHT,
ArrowDown: DOWN,
Down: DOWN
};
var DIRECTIONS_DELTA = {
left: function(speed) {
return {
x: -speed,
y: 0
};
},
up: function(speed) {
return {
x: 0,
y: -speed
};
},
right: function(speed) {
return {
x: speed,
y: 0
};
},
down: function(speed) {
return {
x: 0,
y: speed
};
}
};
/**
* Enables to move selection with keyboard arrows.
* Use with Shift for modified speed (default=1, with Shift=10).
* Pressed Cmd/Ctrl turns the feature off.
*
* @param {Object} config
* @param {number} [config.moveSpeed=1]
* @param {number} [config.moveSpeedAccelerated=10]
* @param {Keyboard} keyboard
* @param {Modeling} modeling
* @param {Selection} selection
*/
function KeyboardMoveSelection(
config,
keyboard,
modeling,
rules,
selection
) {
var self = this;
this._config = assign({}, DEFAULT_CONFIG, config || {});
keyboard.addListener(HIGHER_PRIORITY$3, function(event) {
var keyEvent = event.keyEvent;
var direction = KEY_TO_DIRECTION[keyEvent.key];
if (!direction) {
return;
}
if (keyboard.isCmd(keyEvent)) {
return;
}
var accelerated = keyboard.isShift(keyEvent);
self.moveSelection(direction, accelerated);
return true;
});
/**
* Move selected elements in the given direction,
* optionally specifying accelerated movement.
*
* @param {string} direction
* @param {boolean} [accelerated=false]
*/
this.moveSelection = function(direction, accelerated) {
var selectedElements = selection.get();
if (!selectedElements.length) {
return;
}
var speed = this._config[
accelerated ?
'moveSpeedAccelerated' :
'moveSpeed'
];
var delta = DIRECTIONS_DELTA[direction](speed);
var canMove = rules.allowed('elements.move', {
shapes: selectedElements
});
if (canMove) {
modeling.moveElements(selectedElements, delta);
}
};
}
KeyboardMoveSelection.$inject = [
'config.keyboardMoveSelection',
'keyboard',
'modeling',
'rules',
'selection'
];
var KeyboardMoveSelectionModule = {
__depends__: [
KeyboardModule$1,
SelectionModule
],
__init__: [
'keyboardMoveSelection'
],
keyboardMoveSelection: [ 'type', KeyboardMoveSelection ]
};
var DEFAULT_MIN_WIDTH = 10;
/**
* A component that provides resizing of shapes on the canvas.
*
* The following components are part of shape resize:
*
* * adding resize handles,
* * creating a visual during resize
* * checking resize rules
* * committing a change once finished
*
*
* ## Customizing
*
* It's possible to customize the resizing behaviour by intercepting 'resize.start'
* and providing the following parameters through the 'context':
*
* * minDimensions ({ width, height }): minimum shape dimensions
*
* * childrenBoxPadding ({ left, top, bottom, right } || number):
* gap between the minimum bounding box and the container
*
* f.ex:
*
* ```javascript
* eventBus.on('resize.start', 1500, function(event) {
* var context = event.context,
*
* context.minDimensions = { width: 140, height: 120 };
*
* // Passing general padding
* context.childrenBoxPadding = 30;
*
* // Passing padding to a specific side
* context.childrenBoxPadding.left = 20;
* });
* ```
*/
function Resize(eventBus, rules, modeling, dragging) {
this._dragging = dragging;
this._rules = rules;
var self = this;
/**
* Handle resize move by specified delta.
*
* @param {Object} context
* @param {Point} delta
*/
function handleMove(context, delta) {
var shape = context.shape,
direction = context.direction,
resizeConstraints = context.resizeConstraints,
newBounds;
context.delta = delta;
newBounds = resizeBounds$1(shape, direction, delta);
// ensure constraints during resize
context.newBounds = ensureConstraints$1(newBounds, resizeConstraints);
// update + cache executable state
context.canExecute = self.canResize(context);
}
/**
* Handle resize start.
*
* @param {Object} context
*/
function handleStart(context) {
var resizeConstraints = context.resizeConstraints,
// evaluate minBounds for backwards compatibility
minBounds = context.minBounds;
if (resizeConstraints !== undefined) {
return;
}
if (minBounds === undefined) {
minBounds = self.computeMinResizeBox(context);
}
context.resizeConstraints = {
min: asTRBL(minBounds)
};
}
/**
* Handle resize end.
*
* @param {Object} context
*/
function handleEnd(context) {
var shape = context.shape,
canExecute = context.canExecute,
newBounds = context.newBounds;
if (canExecute) {
// ensure we have actual pixel values for new bounds
// (important when zoom level was > 1 during move)
newBounds = roundBounds(newBounds);
if (!boundsChanged(shape, newBounds)) {
// no resize necessary
return;
}
// perform the actual resize
modeling.resizeShape(shape, newBounds);
}
}
eventBus.on('resize.start', function(event) {
handleStart(event.context);
});
eventBus.on('resize.move', function(event) {
var delta = {
x: event.dx,
y: event.dy
};
handleMove(event.context, delta);
});
eventBus.on('resize.end', function(event) {
handleEnd(event.context);
});
}
Resize.prototype.canResize = function(context) {
var rules = this._rules;
var ctx = pick(context, [ 'newBounds', 'shape', 'delta', 'direction' ]);
return rules.allowed('shape.resize', ctx);
};
/**
* Activate a resize operation.
*
* You may specify additional contextual information and must specify a
* resize direction during activation of the resize event.
*
* @param {MouseEvent} event
* @param {djs.model.Shape} shape
* @param {Object|string} contextOrDirection
*/
Resize.prototype.activate = function(event, shape, contextOrDirection) {
var dragging = this._dragging,
context,
direction;
if (typeof contextOrDirection === 'string') {
contextOrDirection = {
direction: contextOrDirection
};
}
context = assign({ shape: shape }, contextOrDirection);
direction = context.direction;
if (!direction) {
throw new Error('must provide a direction (n|w|s|e|nw|se|ne|sw)');
}
dragging.init(event, getReferencePoint$1(shape, direction), 'resize', {
autoActivate: true,
cursor: getCursor(direction),
data: {
shape: shape,
context: context
}
});
};
Resize.prototype.computeMinResizeBox = function(context) {
var shape = context.shape,
direction = context.direction,
minDimensions,
childrenBounds;
minDimensions = context.minDimensions || {
width: DEFAULT_MIN_WIDTH,
height: DEFAULT_MIN_WIDTH
};
// get children bounds
childrenBounds = computeChildrenBBox(shape, context.childrenBoxPadding);
// get correct minimum bounds from given resize direction
// basically ensures that the minBounds is max(childrenBounds, minDimensions)
return getMinResizeBounds(direction, shape, minDimensions, childrenBounds);
};
Resize.$inject = [
'eventBus',
'rules',
'modeling',
'dragging'
];
// helpers //////////
function boundsChanged(shape, newBounds) {
return shape.x !== newBounds.x ||
shape.y !== newBounds.y ||
shape.width !== newBounds.width ||
shape.height !== newBounds.height;
}
function getReferencePoint$1(shape, direction) {
var mid = getMid(shape),
trbl = asTRBL(shape);
var referencePoint = {
x: mid.x,
y: mid.y
};
if (direction.indexOf('n') !== -1) {
referencePoint.y = trbl.top;
} else if (direction.indexOf('s') !== -1) {
referencePoint.y = trbl.bottom;
}
if (direction.indexOf('e') !== -1) {
referencePoint.x = trbl.right;
} else if (direction.indexOf('w') !== -1) {
referencePoint.x = trbl.left;
}
return referencePoint;
}
function getCursor(direction) {
var prefix = 'resize-';
if (direction === 'n' || direction === 's') {
return prefix + 'ns';
} else if (direction === 'e' || direction === 'w') {
return prefix + 'ew';
} else if (direction === 'nw' || direction === 'se') {
return prefix + 'nwse';
} else {
return prefix + 'nesw';
}
}
var MARKER_RESIZING$1 = 'djs-resizing',
MARKER_RESIZE_NOT_OK = 'resize-not-ok';
var LOW_PRIORITY$e = 500;
/**
* Provides previews for resizing shapes when resizing.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {PreviewSupport} previewSupport
*/
function ResizePreview(eventBus, canvas, previewSupport) {
/**
* Update resizer frame.
*
* @param {Object} context
*/
function updateFrame(context) {
var shape = context.shape,
bounds = context.newBounds,
frame = context.frame;
if (!frame) {
frame = context.frame = previewSupport.addFrame(shape, canvas.getActiveLayer());
canvas.addMarker(shape, MARKER_RESIZING$1);
}
if (bounds.width > 5) {
attr(frame, { x: bounds.x, width: bounds.width });
}
if (bounds.height > 5) {
attr(frame, { y: bounds.y, height: bounds.height });
}
if (context.canExecute) {
classes(frame).remove(MARKER_RESIZE_NOT_OK);
} else {
classes(frame).add(MARKER_RESIZE_NOT_OK);
}
}
/**
* Remove resizer frame.
*
* @param {Object} context
*/
function removeFrame(context) {
var shape = context.shape,
frame = context.frame;
if (frame) {
remove$1(context.frame);
}
canvas.removeMarker(shape, MARKER_RESIZING$1);
}
// add and update previews
eventBus.on('resize.move', LOW_PRIORITY$e, function(event) {
updateFrame(event.context);
});
// remove previews
eventBus.on('resize.cleanup', function(event) {
removeFrame(event.context);
});
}
ResizePreview.$inject = [
'eventBus',
'canvas',
'previewSupport'
];
var HANDLE_OFFSET = -6,
HANDLE_SIZE = 8,
HANDLE_HIT_SIZE = 20;
var CLS_RESIZER = 'djs-resizer';
var directions = [ 'n', 'w', 's', 'e', 'nw', 'ne', 'se', 'sw' ];
/**
* This component is responsible for adding resize handles.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {Selection} selection
* @param {Resize} resize
*/
function ResizeHandles(eventBus, canvas, selection, resize) {
this._resize = resize;
this._canvas = canvas;
var self = this;
eventBus.on('selection.changed', function(e) {
var newSelection = e.newSelection;
// remove old selection markers
self.removeResizers();
// add new selection markers ONLY if single selection
if (newSelection.length === 1) {
forEach$1(newSelection, bind(self.addResizer, self));
}
});
eventBus.on('shape.changed', function(e) {
var shape = e.element;
if (selection.isSelected(shape)) {
self.removeResizers();
self.addResizer(shape);
}
});
}
ResizeHandles.prototype.makeDraggable = function(element, gfx, direction) {
var resize = this._resize;
function startResize(event) {
// only trigger on left mouse button
if (isPrimaryButton(event)) {
resize.activate(event, element, direction);
}
}
componentEvent.bind(gfx, 'mousedown', startResize);
componentEvent.bind(gfx, 'touchstart', startResize);
};
ResizeHandles.prototype._createResizer = function(element, x, y, direction) {
var resizersParent = this._getResizersParent();
var offset = getHandleOffset(direction);
var group = create$1('g');
classes(group).add(CLS_RESIZER);
classes(group).add(CLS_RESIZER + '-' + element.id);
classes(group).add(CLS_RESIZER + '-' + direction);
append(resizersParent, group);
var visual = create$1('rect');
attr(visual, {
x: -HANDLE_SIZE / 2 + offset.x,
y: -HANDLE_SIZE / 2 + offset.y,
width: HANDLE_SIZE,
height: HANDLE_SIZE
});
classes(visual).add(CLS_RESIZER + '-visual');
append(group, visual);
var hit = create$1('rect');
attr(hit, {
x: -HANDLE_HIT_SIZE / 2 + offset.x,
y: -HANDLE_HIT_SIZE / 2 + offset.y,
width: HANDLE_HIT_SIZE,
height: HANDLE_HIT_SIZE
});
classes(hit).add(CLS_RESIZER + '-hit');
append(group, hit);
transform(group, x, y);
return group;
};
ResizeHandles.prototype.createResizer = function(element, direction) {
var point = getReferencePoint$1(element, direction);
var resizer = this._createResizer(element, point.x, point.y, direction);
this.makeDraggable(element, resizer, direction);
};
// resize handles implementation ///////////////////////////////
/**
* Add resizers for a given element.
*
* @param {djs.model.Element} element
*/
ResizeHandles.prototype.addResizer = function(element) {
var self = this;
if (isConnection$a(element) || !this._resize.canResize({ shape: element })) {
return;
}
forEach$1(directions, function(direction) {
self.createResizer(element, direction);
});
};
/**
* Remove all resizers
*/
ResizeHandles.prototype.removeResizers = function() {
var resizersParent = this._getResizersParent();
clear(resizersParent);
};
ResizeHandles.prototype._getResizersParent = function() {
return this._canvas.getLayer('resizers');
};
ResizeHandles.$inject = [
'eventBus',
'canvas',
'selection',
'resize'
];
// helpers //////////
function getHandleOffset(direction) {
var offset = {
x: 0,
y: 0
};
if (direction.indexOf('e') !== -1) {
offset.x = -HANDLE_OFFSET;
} else if (direction.indexOf('w') !== -1) {
offset.x = HANDLE_OFFSET;
}
if (direction.indexOf('s') !== -1) {
offset.y = -HANDLE_OFFSET;
} else if (direction.indexOf('n') !== -1) {
offset.y = HANDLE_OFFSET;
}
return offset;
}
function isConnection$a(element) {
return !!element.waypoints;
}
var ResizeModule = {
__depends__: [
RulesModule$1,
DraggingModule,
PreviewSupportModule
],
__init__: [
'resize',
'resizePreview',
'resizeHandles'
],
resize: [ 'type', Resize ],
resizePreview: [ 'type', ResizePreview ],
resizeHandles: [ 'type', ResizeHandles ]
};
var HIGH_PRIORITY$c = 2000;
function LabelEditingProvider(
eventBus, bpmnFactory, canvas, directEditing,
modeling, resizeHandles, textRenderer) {
this._bpmnFactory = bpmnFactory;
this._canvas = canvas;
this._modeling = modeling;
this._textRenderer = textRenderer;
directEditing.registerProvider(this);
// listen to dblclick on non-root elements
eventBus.on('element.dblclick', function(event) {
activateDirectEdit(event.element, true);
});
// complete on followup canvas operation
eventBus.on([
'autoPlace.start',
'canvas.viewbox.changing',
'drag.init',
'element.mousedown',
'popupMenu.open',
'root.set',
'selection.changed'
], function(event) {
if (directEditing.isActive()) {
directEditing.complete();
}
});
eventBus.on([
'shape.remove',
'connection.remove'
], HIGH_PRIORITY$c, function(event) {
if (directEditing.isActive(event.element)) {
directEditing.cancel();
}
});
// cancel on command stack changes
eventBus.on([ 'commandStack.changed' ], function(e) {
if (directEditing.isActive()) {
directEditing.cancel();
}
});
eventBus.on('directEditing.activate', function(event) {
resizeHandles.removeResizers();
});
eventBus.on('create.end', 500, function(event) {
var context = event.context,
element = context.shape,
canExecute = event.context.canExecute,
isTouch = event.isTouch;
// TODO(nikku): we need to find a way to support the
// direct editing on mobile devices; right now this will
// break for desworkflowediting on mobile devices
// as it breaks the user interaction workflow
// TODO(nre): we should temporarily focus the edited element
// here and release the focused viewport after the direct edit
// operation is finished
if (isTouch) {
return;
}
if (!canExecute) {
return;
}
if (context.hints && context.hints.createElementsBehavior === false) {
return;
}
activateDirectEdit(element);
});
eventBus.on('autoPlace.end', 500, function(event) {
activateDirectEdit(event.shape);
});
function activateDirectEdit(element, force) {
if (force ||
isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation' ]) ||
isCollapsedSubProcess(element)) {
directEditing.activate(element);
}
}
}
LabelEditingProvider.$inject = [
'eventBus',
'bpmnFactory',
'canvas',
'directEditing',
'modeling',
'resizeHandles',
'textRenderer'
];
/**
* Activate direct editing for activities and text annotations.
*
* @param {djs.model.Base} element
*
* @return {Object} an object with properties bounds (position and size), text and options
*/
LabelEditingProvider.prototype.activate = function(element) {
// text
var text = getLabel(element);
if (text === undefined) {
return;
}
var context = {
text: text
};
// bounds
var bounds = this.getEditingBBox(element);
assign(context, bounds);
var options = {};
// tasks
if (
isAny(element, [
'bpmn:Task',
'bpmn:Participant',
'bpmn:Lane',
'bpmn:CallActivity'
]) ||
isCollapsedSubProcess(element)
) {
assign(options, {
centerVertically: true
});
}
// external labels
if (isLabelExternal(element)) {
assign(options, {
autoResize: true
});
}
// text annotations
if (is$1(element, 'bpmn:TextAnnotation')) {
assign(options, {
resizable: true,
autoResize: true
});
}
assign(context, {
options: options
});
return context;
};
/**
* Get the editing bounding box based on the element's size and position
*
* @param {djs.model.Base} element
*
* @return {Object} an object containing information about position
* and size (fixed or minimum and/or maximum)
*/
LabelEditingProvider.prototype.getEditingBBox = function(element) {
var canvas = this._canvas;
var target = element.label || element;
var bbox = canvas.getAbsoluteBBox(target);
var mid = {
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
};
// default position
var bounds = { x: bbox.x, y: bbox.y };
var zoom = canvas.zoom();
var defaultStyle = this._textRenderer.getDefaultStyle(),
externalStyle = this._textRenderer.getExternalStyle();
// take zoom into account
var externalFontSize = externalStyle.fontSize * zoom,
externalLineHeight = externalStyle.lineHeight,
defaultFontSize = defaultStyle.fontSize * zoom,
defaultLineHeight = defaultStyle.lineHeight;
var style = {
fontFamily: this._textRenderer.getDefaultStyle().fontFamily,
fontWeight: this._textRenderer.getDefaultStyle().fontWeight
};
// adjust for expanded pools AND lanes
if (is$1(element, 'bpmn:Lane') || isExpandedPool(element)) {
assign(bounds, {
width: bbox.height,
height: 30 * zoom,
x: bbox.x - bbox.height / 2 + (15 * zoom),
y: mid.y - (30 * zoom) / 2
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
transform: 'rotate(-90deg)'
});
}
// internal labels for tasks and collapsed call activities,
// sub processes and participants
if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity' ]) ||
isCollapsedPool(element) ||
isCollapsedSubProcess(element)) {
assign(bounds, {
width: bbox.width,
height: bbox.height
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
// internal labels for expanded sub processes
if (isExpandedSubProcess$1(element)) {
assign(bounds, {
width: bbox.width,
x: bbox.x
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
var width = 90 * zoom,
paddingTop = 7 * zoom,
paddingBottom = 4 * zoom;
// external labels for events, data elements, gateways, groups and connections
if (target.labelTarget) {
assign(bounds, {
width: width,
height: bbox.height + paddingTop + paddingBottom,
x: mid.x - width / 2,
y: bbox.y - paddingTop
});
assign(style, {
fontSize: externalFontSize + 'px',
lineHeight: externalLineHeight,
paddingTop: paddingTop + 'px',
paddingBottom: paddingBottom + 'px'
});
}
// external label not yet created
if (isLabelExternal(target)
&& !hasExternalLabel(target)
&& !isLabel$6(target)) {
var externalLabelMid = getExternalLabelMid(element);
var absoluteBBox = canvas.getAbsoluteBBox({
x: externalLabelMid.x,
y: externalLabelMid.y,
width: 0,
height: 0
});
var height = externalFontSize + paddingTop + paddingBottom;
assign(bounds, {
width: width,
height: height,
x: absoluteBBox.x - width / 2,
y: absoluteBBox.y - height / 2
});
assign(style, {
fontSize: externalFontSize + 'px',
lineHeight: externalLineHeight,
paddingTop: paddingTop + 'px',
paddingBottom: paddingBottom + 'px'
});
}
// text annotations
if (is$1(element, 'bpmn:TextAnnotation')) {
assign(bounds, {
width: bbox.width,
height: bbox.height,
minWidth: 30 * zoom,
minHeight: 10 * zoom
});
assign(style, {
textAlign: 'left',
paddingTop: (5 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (7 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight
});
}
return { bounds: bounds, style: style };
};
LabelEditingProvider.prototype.update = function(
element, newLabel,
activeContextText, bounds) {
var newBounds,
bbox;
if (is$1(element, 'bpmn:TextAnnotation')) {
bbox = this._canvas.getAbsoluteBBox(element);
newBounds = {
x: element.x,
y: element.y,
width: element.width / bbox.width * bounds.width,
height: element.height / bbox.height * bounds.height
};
}
if (isEmptyText$1(newLabel)) {
newLabel = null;
}
this._modeling.updateLabel(element, newLabel, newBounds);
};
// helpers //////////////////////
function isCollapsedSubProcess(element) {
return is$1(element, 'bpmn:SubProcess') && !isExpanded(element);
}
function isExpandedSubProcess$1(element) {
return is$1(element, 'bpmn:SubProcess') && isExpanded(element);
}
function isCollapsedPool(element) {
return is$1(element, 'bpmn:Participant') && !isExpanded(element);
}
function isExpandedPool(element) {
return is$1(element, 'bpmn:Participant') && isExpanded(element);
}
function isEmptyText$1(label) {
return !label || !label.trim();
}
var MARKER_HIDDEN = 'djs-element-hidden',
MARKER_LABEL_HIDDEN = 'djs-label-hidden';
function LabelEditingPreview(
eventBus, canvas, elementRegistry,
pathMap) {
var self = this;
var defaultLayer = canvas.getDefaultLayer();
var element, absoluteElementBBox, gfx;
eventBus.on('directEditing.activate', function(context) {
var activeProvider = context.active;
element = activeProvider.element.label || activeProvider.element;
// text annotation
if (is$1(element, 'bpmn:TextAnnotation')) {
absoluteElementBBox = canvas.getAbsoluteBBox(element);
gfx = create$1('g');
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.0,
my: 0.0
}
});
var path = self.path = create$1('path');
attr(path, {
d: textPathData,
strokeWidth: 2,
stroke: getStrokeColor(element)
});
append(gfx, path);
append(defaultLayer, gfx);
translate$2(gfx, element.x, element.y);
}
if (is$1(element, 'bpmn:TextAnnotation') ||
element.labelTarget) {
canvas.addMarker(element, MARKER_HIDDEN);
} else if (is$1(element, 'bpmn:Task') ||
is$1(element, 'bpmn:CallActivity') ||
is$1(element, 'bpmn:SubProcess') ||
is$1(element, 'bpmn:Participant')) {
canvas.addMarker(element, MARKER_LABEL_HIDDEN);
}
});
eventBus.on('directEditing.resize', function(context) {
// text annotation
if (is$1(element, 'bpmn:TextAnnotation')) {
var height = context.height,
dy = context.dy;
var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0);
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: newElementHeight,
position: {
mx: 0.0,
my: 0.0
}
});
attr(self.path, {
d: textPathData
});
}
});
eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
var activeProvider = context.active;
if (activeProvider) {
canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
}
element = undefined;
absoluteElementBBox = undefined;
if (gfx) {
remove$1(gfx);
gfx = undefined;
}
});
}
LabelEditingPreview.$inject = [
'eventBus',
'canvas',
'elementRegistry',
'pathMap'
];
// helpers ///////////////////
function getStrokeColor(element, defaultColor) {
var di = getDi(element);
return di.get('stroke') || defaultColor || 'black';
}
var LabelEditingModule = {
__depends__: [
ChangeSupportModule,
ResizeModule,
DirectEditingModule
],
__init__: [
'labelEditingProvider',
'labelEditingPreview'
],
labelEditingProvider: [ 'type', LabelEditingProvider ],
labelEditingPreview: [ 'type', LabelEditingPreview ]
};
var ALIGNMENTS = [
'top',
'bottom',
'left',
'right'
];
var ELEMENT_LABEL_DISTANCE = 10;
/**
* A component that makes sure that external labels are added
* together with respective elements and properly updated (DI wise)
* during move.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.postExecuted([
'connection.create',
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context,
connection = context.connection,
source = connection.source,
target = connection.target,
hints = context.hints || {};
if (hints.createElementsBehavior !== false) {
checkLabelAdjustment(source);
checkLabelAdjustment(target);
}
});
this.postExecuted([
'label.create'
], function(event) {
var context = event.context,
shape = context.shape,
hints = context.hints || {};
if (hints.createElementsBehavior !== false) {
checkLabelAdjustment(shape.labelTarget);
}
});
this.postExecuted([
'elements.create'
], function(event) {
var context = event.context,
elements = context.elements,
hints = context.hints || {};
if (hints.createElementsBehavior !== false) {
elements.forEach(function(element) {
checkLabelAdjustment(element);
});
}
});
function checkLabelAdjustment(element) {
// skip non-existing labels
if (!hasExternalLabel(element)) {
return;
}
var optimalPosition = getOptimalPosition(element);
// no optimal position found
if (!optimalPosition) {
return;
}
adjustLabelPosition(element, optimalPosition);
}
function adjustLabelPosition(element, orientation) {
var elementMid = getMid(element),
label = element.label,
labelMid = getMid(label);
// ignore labels that are being created
if (!label.parent) {
return;
}
var elementTrbl = asTRBL(element);
var newLabelMid;
switch (orientation) {
case 'top':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2
};
break;
case 'left':
newLabelMid = {
x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2,
y: elementMid.y
};
break;
case 'bottom':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2
};
break;
case 'right':
newLabelMid = {
x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2,
y: elementMid.y
};
break;
}
var delta$1 = delta(newLabelMid, labelMid);
modeling.moveShape(label, delta$1);
}
}
e(AdaptiveLabelPositioningBehavior, CommandInterceptor);
AdaptiveLabelPositioningBehavior.$inject = [
'eventBus',
'modeling'
];
// helpers //////////////////////
/**
* Return alignments which are taken by a boundary's host element
*
* @param {Shape} element
*
* @return {Array<string>}
*/
function getTakenHostAlignments(element) {
var hostElement = element.host,
elementMid = getMid(element),
hostOrientation = getOrientation(elementMid, hostElement);
var freeAlignments;
// check whether there is a multi-orientation, e.g. 'top-left'
if (hostOrientation.indexOf('-') >= 0) {
freeAlignments = hostOrientation.split('-');
} else {
freeAlignments = [ hostOrientation ];
}
var takenAlignments = ALIGNMENTS.filter(function(alignment) {
return freeAlignments.indexOf(alignment) === -1;
});
return takenAlignments;
}
/**
* Return alignments which are taken by related connections
*
* @param {Shape} element
*
* @return {Array<string>}
*/
function getTakenConnectionAlignments(element) {
var elementMid = getMid(element);
var takenAlignments = [].concat(
element.incoming.map(function(c) {
return c.waypoints[c.waypoints.length - 2 ];
}),
element.outgoing.map(function(c) {
return c.waypoints[1];
})
).map(function(point) {
return getApproximateOrientation(elementMid, point);
});
return takenAlignments;
}
/**
* Return the optimal label position around an element
* or _undefined_, if none was found.
*
* @param {Shape} element
*
* @return {string} positioning identifier
*/
function getOptimalPosition(element) {
var labelMid = getMid(element.label);
var elementMid = getMid(element);
var labelOrientation = getApproximateOrientation(elementMid, labelMid);
if (!isAligned(labelOrientation)) {
return;
}
var takenAlignments = getTakenConnectionAlignments(element);
if (element.host) {
var takenHostAlignments = getTakenHostAlignments(element);
takenAlignments = takenAlignments.concat(takenHostAlignments);
}
var freeAlignments = ALIGNMENTS.filter(function(alignment) {
return takenAlignments.indexOf(alignment) === -1;
});
// NOTHING TO DO; label already aligned a.O.K.
if (freeAlignments.indexOf(labelOrientation) !== -1) {
return;
}
return freeAlignments[0];
}
function getApproximateOrientation(p0, p1) {
return getOrientation(p1, p0, 5);
}
function isAligned(orientation) {
return ALIGNMENTS.indexOf(orientation) !== -1;
}
function AppendBehavior(eventBus, elementFactory, bpmnRules) {
CommandInterceptor.call(this, eventBus);
// assign correct shape position unless already set
this.preExecute('shape.append', function(context) {
var source = context.source,
shape = context.shape;
if (!context.position) {
if (is$1(shape, 'bpmn:TextAnnotation')) {
context.position = {
x: source.x + source.width / 2 + 75,
y: source.y - (50) - shape.height / 2
};
} else {
context.position = {
x: source.x + source.width + 80 + shape.width / 2,
y: source.y + source.height / 2
};
}
}
}, true);
}
e(AppendBehavior, CommandInterceptor);
AppendBehavior.$inject = [
'eventBus',
'elementFactory',
'bpmnRules'
];
function AssociationBehavior(injector, modeling) {
injector.invoke(CommandInterceptor, this);
this.postExecute('shape.move', function(context) {
var newParent = context.newParent,
shape = context.shape;
var associations = filter(shape.incoming.concat(shape.outgoing), function(connection) {
return is$1(connection, 'bpmn:Association');
});
forEach$1(associations, function(association) {
modeling.moveConnection(association, { x: 0, y: 0 }, newParent);
});
}, true);
}
e(AssociationBehavior, CommandInterceptor);
AssociationBehavior.$inject = [
'injector',
'modeling'
];
var LOW_PRIORITY$d = 500;
/**
* Replace intermediate event with boundary event when creating or moving results in attached event.
*/
function AttachEventBehavior(bpmnReplace, injector) {
injector.invoke(CommandInterceptor, this);
this._bpmnReplace = bpmnReplace;
var self = this;
this.postExecuted('elements.create', LOW_PRIORITY$d, function(context) {
var elements = context.elements;
elements = elements.filter(function(shape) {
var host = shape.host;
return shouldReplace$1(shape, host);
});
if (elements.length !== 1) {
return;
}
elements.map(function(element) {
return elements.indexOf(element);
}).forEach(function(index) {
var host = elements[ index ];
context.elements[ index ] = self.replaceShape(elements[ index ], host);
});
}, true);
this.preExecute('elements.move', LOW_PRIORITY$d, function(context) {
var shapes = context.shapes,
host = context.newHost;
if (shapes.length !== 1) {
return;
}
var shape = shapes[0];
if (shouldReplace$1(shape, host)) {
context.shapes = [ self.replaceShape(shape, host) ];
}
}, true);
}
AttachEventBehavior.$inject = [
'bpmnReplace',
'injector'
];
e(AttachEventBehavior, CommandInterceptor);
AttachEventBehavior.prototype.replaceShape = function(shape, host) {
var eventDefinition = getEventDefinition$1(shape);
var boundaryEvent = {
type: 'bpmn:BoundaryEvent',
host: host
};
if (eventDefinition) {
boundaryEvent.eventDefinitionType = eventDefinition.$type;
}
return this._bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false });
};
// helpers //////////
function getEventDefinition$1(element) {
var businessObject = getBusinessObject(element),
eventDefinitions = businessObject.eventDefinitions;
return eventDefinitions && eventDefinitions[0];
}
function shouldReplace$1(shape, host) {
return !isLabel$6(shape) &&
isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ]) && !!host;
}
/**
* BPMN specific boundary event behavior
*/
function BoundaryEventBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
function getBoundaryEvents(element) {
return filter(element.attachers, function(attacher) {
return is$1(attacher, 'bpmn:BoundaryEvent');
});
}
// remove after connecting to event-based gateway
this.postExecute('connection.create', function(event) {
var source = event.context.source,
target = event.context.target,
boundaryEvents = getBoundaryEvents(target);
if (
is$1(source, 'bpmn:EventBasedGateway') &&
is$1(target, 'bpmn:ReceiveTask') &&
boundaryEvents.length > 0
) {
modeling.removeElements(boundaryEvents);
}
});
// remove after replacing connected gateway with event-based gateway
this.postExecute('connection.reconnect', function(event) {
var oldSource = event.context.oldSource,
newSource = event.context.newSource;
if (is$1(oldSource, 'bpmn:Gateway') &&
is$1(newSource, 'bpmn:EventBasedGateway')) {
forEach$1(newSource.outgoing, function(connection) {
var target = connection.target,
attachedboundaryEvents = getBoundaryEvents(target);
if (is$1(target, 'bpmn:ReceiveTask') &&
attachedboundaryEvents.length > 0) {
modeling.removeElements(attachedboundaryEvents);
}
});
}
});
}
BoundaryEventBehavior.$inject = [
'eventBus',
'modeling'
];
e(BoundaryEventBehavior, CommandInterceptor);
function CreateBehavior(injector) {
injector.invoke(CommandInterceptor, this);
this.preExecute('shape.create', 1500, function(event) {
var context = event.context,
parent = context.parent,
shape = context.shape;
if (is$1(parent, 'bpmn:Lane') && !is$1(shape, 'bpmn:Lane')) {
context.parent = getParent(parent, 'bpmn:Participant');
}
});
}
CreateBehavior.$inject = [ 'injector' ];
e(CreateBehavior, CommandInterceptor);
/**
* BPMN specific create data object behavior
*/
function CreateDataObjectBehavior(eventBus, bpmnFactory, moddle) {
CommandInterceptor.call(this, eventBus);
this.preExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (is$1(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
// create a DataObject every time a DataObjectReference is created
var dataObject = bpmnFactory.create('bpmn:DataObject');
// set the reference to the DataObject
shape.businessObject.dataObjectRef = dataObject;
}
});
}
CreateDataObjectBehavior.$inject = [
'eventBus',
'bpmnFactory',
'moddle'
];
e(CreateDataObjectBehavior, CommandInterceptor);
var HORIZONTAL_PARTICIPANT_PADDING = 20,
VERTICAL_PARTICIPANT_PADDING = 20;
var PARTICIPANT_BORDER_WIDTH = 30;
var HIGH_PRIORITY$b = 2000;
/**
* BPMN-specific behavior for creating participants.
*/
function CreateParticipantBehavior(canvas, eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
// fit participant
eventBus.on([
'create.start',
'shape.move.start'
], HIGH_PRIORITY$b, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (!is$1(shape, 'bpmn:Participant') ||
!is$1(rootElement, 'bpmn:Process') ||
!rootElement.children.length) {
return;
}
// ignore connections, groups and labels
var children = rootElement.children.filter(function(element) {
return !is$1(element, 'bpmn:Group') &&
!isLabel$6(element) &&
!isConnection$9(element);
});
// ensure for available children to calculate bounds
if (!children.length) {
return;
}
var childrenBBox = getBBox(children);
var participantBounds = getParticipantBounds(shape, childrenBBox);
// assign width and height
assign(shape, participantBounds);
// assign create constraints
context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
});
// force hovering process when creating first participant
eventBus.on('create.start', HIGH_PRIORITY$b, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement(),
rootElementGfx = canvas.getGraphics(rootElement);
function ensureHoveringProcess(event) {
event.element = rootElement;
event.gfx = rootElementGfx;
}
if (is$1(shape, 'bpmn:Participant') && is$1(rootElement, 'bpmn:Process')) {
eventBus.on('element.hover', HIGH_PRIORITY$b, ensureHoveringProcess);
eventBus.once('create.cleanup', function() {
eventBus.off('element.hover', ensureHoveringProcess);
});
}
});
// turn process into collaboration when creating first participant
function getOrCreateCollaboration() {
var rootElement = canvas.getRootElement();
if (is$1(rootElement, 'bpmn:Collaboration')) {
return rootElement;
}
return modeling.makeCollaboration();
}
// when creating mutliple elements through `elements.create` parent must be set to collaboration
// and passed to `shape.create` as hint
this.preExecute('elements.create', HIGH_PRIORITY$b, function(context) {
var elements = context.elements,
parent = context.parent,
participant = findParticipant(elements),
hints;
if (participant && is$1(parent, 'bpmn:Process')) {
context.parent = getOrCreateCollaboration();
hints = context.hints = context.hints || {};
hints.participant = participant;
hints.process = parent;
hints.processRef = getBusinessObject(participant).get('processRef');
}
}, true);
// when creating single shape through `shape.create` parent must be set to collaboration
// unless it was already set through `elements.create`
this.preExecute('shape.create', function(context) {
var parent = context.parent,
shape = context.shape;
if (is$1(shape, 'bpmn:Participant') && is$1(parent, 'bpmn:Process')) {
context.parent = getOrCreateCollaboration();
context.process = parent;
context.processRef = getBusinessObject(shape).get('processRef');
}
}, true);
// #execute necessary because #preExecute not called on CommandStack#redo
this.execute('shape.create', function(context) {
var hints = context.hints || {},
process = context.process || hints.process,
shape = context.shape,
participant = hints.participant;
// both shape.create and elements.create must be handled
if (process && (!participant || shape === participant)) {
// monkey-patch process ref
getBusinessObject(shape).set('processRef', getBusinessObject(process));
}
}, true);
this.revert('shape.create', function(context) {
var hints = context.hints || {},
process = context.process || hints.process,
processRef = context.processRef || hints.processRef,
shape = context.shape,
participant = hints.participant;
// both shape.create and elements.create must be handled
if (process && (!participant || shape === participant)) {
// monkey-patch process ref
getBusinessObject(shape).set('processRef', processRef);
}
}, true);
this.postExecute('shape.create', function(context) {
var hints = context.hints || {},
process = context.process || context.hints.process,
shape = context.shape,
participant = hints.participant;
if (process) {
var children = process.children.slice();
// both shape.create and elements.create must be handled
if (!participant) {
modeling.moveElements(children, { x: 0, y: 0 }, shape);
} else if (shape === participant) {
modeling.moveElements(children, { x: 0, y: 0 }, participant);
}
}
}, true);
}
CreateParticipantBehavior.$inject = [
'canvas',
'eventBus',
'modeling'
];
e(CreateParticipantBehavior, CommandInterceptor);
// helpers //////////
function getParticipantBounds(shape, childrenBBox) {
childrenBBox = {
width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH,
height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2
};
var width = Math.max(shape.width, childrenBBox.width),
height = Math.max(shape.height, childrenBBox.height);
return {
x: -width / 2,
y: -height / 2,
width: width,
height: height
};
}
function getParticipantCreateConstraints(shape, childrenBBox) {
childrenBBox = asTRBL(childrenBBox);
return {
bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING,
left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING,
top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING,
right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH
};
}
function isConnection$9(element) {
return !!element.waypoints;
}
function findParticipant(elements) {
return find(elements, function(element) {
return is$1(element, 'bpmn:Participant');
});
}
var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder';
/**
* This behavior makes sure we always set a fake
* DataInputAssociation#targetRef as demanded by the BPMN 2.0
* XSD schema.
*
* The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' }
* which is created on the fly and cleaned up afterwards if not needed
* anymore.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
*/
function DataInputAssociationBehavior(eventBus, bpmnFactory) {
CommandInterceptor.call(this, eventBus);
this.executed([
'connection.create',
'connection.delete',
'connection.move',
'connection.reconnect'
], ifDataInputAssociation(fixTargetRef));
this.reverted([
'connection.create',
'connection.delete',
'connection.move',
'connection.reconnect'
], ifDataInputAssociation(fixTargetRef));
function usesTargetRef(element, targetRef, removedConnection) {
var inputAssociations = element.get('dataInputAssociations');
return find(inputAssociations, function(association) {
return association !== removedConnection &&
association.targetRef === targetRef;
});
}
function getTargetRef(element, create) {
var properties = element.get('properties');
var targetRefProp = find(properties, function(p) {
return p.name === TARGET_REF_PLACEHOLDER_NAME;
});
if (!targetRefProp && create) {
targetRefProp = bpmnFactory.create('bpmn:Property', {
name: TARGET_REF_PLACEHOLDER_NAME
});
add(properties, targetRefProp);
}
return targetRefProp;
}
function cleanupTargetRef(element, connection) {
var targetRefProp = getTargetRef(element);
if (!targetRefProp) {
return;
}
if (!usesTargetRef(element, targetRefProp, connection)) {
remove(element.get('properties'), targetRefProp);
}
}
/**
* Make sure targetRef is set to a valid property or
* `null` if the connection is detached.
*
* @param {Event} event
*/
function fixTargetRef(event) {
var context = event.context,
connection = context.connection,
connectionBo = connection.businessObject,
target = connection.target,
targetBo = target && target.businessObject,
newTarget = context.newTarget,
newTargetBo = newTarget && newTarget.businessObject,
oldTarget = context.oldTarget || context.target,
oldTargetBo = oldTarget && oldTarget.businessObject;
var dataAssociation = connection.businessObject,
targetRefProp;
if (oldTargetBo && oldTargetBo !== targetBo) {
cleanupTargetRef(oldTargetBo, connectionBo);
}
if (newTargetBo && newTargetBo !== targetBo) {
cleanupTargetRef(newTargetBo, connectionBo);
}
if (targetBo) {
targetRefProp = getTargetRef(targetBo, true);
dataAssociation.targetRef = targetRefProp;
} else {
dataAssociation.targetRef = null;
}
}
}
DataInputAssociationBehavior.$inject = [
'eventBus',
'bpmnFactory'
];
e(DataInputAssociationBehavior, CommandInterceptor);
/**
* Only call the given function when the event
* touches a bpmn:DataInputAssociation.
*
* @param {Function} fn
* @return {Function}
*/
function ifDataInputAssociation(fn) {
return function(event) {
var context = event.context,
connection = context.connection;
if (is$1(connection, 'bpmn:DataInputAssociation')) {
return fn(event);
}
};
}
function UpdateSemanticParentHandler(bpmnUpdater) {
this._bpmnUpdater = bpmnUpdater;
}
UpdateSemanticParentHandler.$inject = [ 'bpmnUpdater' ];
UpdateSemanticParentHandler.prototype.execute = function(context) {
var dataStoreBo = context.dataStoreBo,
dataStoreDi = context.dataStoreDi,
newSemanticParent = context.newSemanticParent,
newDiParent = context.newDiParent;
context.oldSemanticParent = dataStoreBo.$parent;
context.oldDiParent = dataStoreDi.$parent;
// update semantic parent
this._bpmnUpdater.updateSemanticParent(dataStoreBo, newSemanticParent);
// update DI parent
this._bpmnUpdater.updateDiParent(dataStoreDi, newDiParent);
};
UpdateSemanticParentHandler.prototype.revert = function(context) {
var dataStoreBo = context.dataStoreBo,
dataStoreDi = context.dataStoreDi,
oldSemanticParent = context.oldSemanticParent,
oldDiParent = context.oldDiParent;
// update semantic parent
this._bpmnUpdater.updateSemanticParent(dataStoreBo, oldSemanticParent);
// update DI parent
this._bpmnUpdater.updateDiParent(dataStoreDi, oldDiParent);
};
/**
* BPMN specific data store behavior
*/
function DataStoreBehavior(
canvas, commandStack, elementRegistry,
eventBus) {
CommandInterceptor.call(this, eventBus);
commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler);
function getFirstParticipantWithProcessRef() {
return elementRegistry.filter(function(element) {
return is$1(element, 'bpmn:Participant') && getBusinessObject(element).processRef;
})[0];
}
function getDataStores(element) {
return element.children.filter(function(child) {
return is$1(child, 'bpmn:DataStoreReference') && !child.labelTarget;
});
}
function updateDataStoreParent(dataStore, newDataStoreParent) {
var dataStoreBo = dataStore.businessObject || dataStore;
newDataStoreParent = newDataStoreParent || getFirstParticipantWithProcessRef();
if (newDataStoreParent) {
var newDataStoreParentBo = newDataStoreParent.businessObject || newDataStoreParent;
commandStack.execute('dataStore.updateContainment', {
dataStoreBo: dataStoreBo,
dataStoreDi: getDi(dataStore),
newSemanticParent: newDataStoreParentBo.processRef || newDataStoreParentBo,
newDiParent: getDi(newDataStoreParent)
});
}
}
// disable auto-resize for data stores
this.preExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (is$1(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label') {
if (!context.hints) {
context.hints = {};
}
// prevent auto resizing
context.hints.autoResize = false;
}
});
// disable auto-resize for data stores
this.preExecute('elements.move', function(event) {
var context = event.context,
shapes = context.shapes;
var dataStoreReferences = shapes.filter(function(shape) {
return is$1(shape, 'bpmn:DataStoreReference');
});
if (dataStoreReferences.length) {
if (!context.hints) {
context.hints = {};
}
// prevent auto resizing for data store references
context.hints.autoResize = shapes.filter(function(shape) {
return !is$1(shape, 'bpmn:DataStoreReference');
});
}
});
// update parent on data store created
this.postExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape,
parent = shape.parent;
if (is$1(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label' &&
is$1(parent, 'bpmn:Collaboration')) {
updateDataStoreParent(shape);
}
});
// update parent on data store moved
this.postExecute('shape.move', function(event) {
var context = event.context,
shape = context.shape,
oldParent = context.oldParent,
parent = shape.parent;
if (is$1(oldParent, 'bpmn:Collaboration')) {
// do nothing if not necessary
return;
}
if (is$1(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label' &&
is$1(parent, 'bpmn:Collaboration')) {
var participant = is$1(oldParent, 'bpmn:Participant') ?
oldParent :
getAncestor(oldParent, 'bpmn:Participant');
updateDataStoreParent(shape, participant);
}
});
// update data store parents on participant or subprocess deleted
this.postExecute('shape.delete', function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess' ])
&& is$1(rootElement, 'bpmn:Collaboration')) {
getDataStores(rootElement)
.filter(function(dataStore) {
return isDescendant(dataStore, shape);
})
.forEach(function(dataStore) {
updateDataStoreParent(dataStore);
});
}
});
// update data store parents on collaboration -> process
this.postExecute('canvas.updateRoot', function(event) {
var context = event.context,
oldRoot = context.oldRoot,
newRoot = context.newRoot;
var dataStores = getDataStores(oldRoot);
dataStores.forEach(function(dataStore) {
if (is$1(newRoot, 'bpmn:Process')) {
updateDataStoreParent(dataStore, newRoot);
}
});
});
}
DataStoreBehavior.$inject = [
'canvas',
'commandStack',
'elementRegistry',
'eventBus',
];
e(DataStoreBehavior, CommandInterceptor);
// helpers //////////
function isDescendant(descendant, ancestor) {
var descendantBo = descendant.businessObject || descendant,
ancestorBo = ancestor.businessObject || ancestor;
while (descendantBo.$parent) {
if (descendantBo.$parent === ancestorBo.processRef || ancestorBo) {
return true;
}
descendantBo = descendantBo.$parent;
}
return false;
}
function getAncestor(element, type) {
while (element.parent) {
if (is$1(element.parent, type)) {
return element.parent;
}
element = element.parent;
}
}
var LOW_PRIORITY$c = 500;
/**
* BPMN specific delete lane behavior
*/
function DeleteLaneBehavior(eventBus, modeling, spaceTool) {
CommandInterceptor.call(this, eventBus);
function compensateLaneDelete(shape, oldParent) {
var siblings = getChildLanes(oldParent);
var topAffected = [];
var bottomAffected = [];
eachElement(siblings, function(element) {
if (element.y > shape.y) {
bottomAffected.push(element);
} else {
topAffected.push(element);
}
return element.children;
});
if (!siblings.length) {
return;
}
var offset;
if (bottomAffected.length && topAffected.length) {
offset = shape.height / 2;
} else {
offset = shape.height;
}
var topAdjustments,
bottomAdjustments;
if (topAffected.length) {
topAdjustments = spaceTool.calculateAdjustments(
topAffected, 'y', offset, shape.y - 10);
spaceTool.makeSpace(
topAdjustments.movingShapes,
topAdjustments.resizingShapes,
{ x: 0, y: offset }, 's');
}
if (bottomAffected.length) {
bottomAdjustments = spaceTool.calculateAdjustments(
bottomAffected, 'y', -offset, shape.y + shape.height + 10);
spaceTool.makeSpace(
bottomAdjustments.movingShapes,
bottomAdjustments.resizingShapes,
{ x: 0, y: -offset }, 'n');
}
}
/**
* Adjust sizes of other lanes after lane deletion
*/
this.postExecuted('shape.delete', LOW_PRIORITY$c, function(event) {
var context = event.context,
hints = context.hints,
shape = context.shape,
oldParent = context.oldParent;
// only compensate lane deletes
if (!is$1(shape, 'bpmn:Lane')) {
return;
}
// compensate root deletes only
if (hints && hints.nested) {
return;
}
compensateLaneDelete(shape, oldParent);
});
}
DeleteLaneBehavior.$inject = [
'eventBus',
'modeling',
'spaceTool'
];
e(DeleteLaneBehavior, CommandInterceptor);
var LOW_PRIORITY$b = 500;
/**
* Replace boundary event with intermediate event when creating or moving results in detached event.
*/
function DetachEventBehavior(bpmnReplace, injector) {
injector.invoke(CommandInterceptor, this);
this._bpmnReplace = bpmnReplace;
var self = this;
this.postExecuted('elements.create', LOW_PRIORITY$b, function(context) {
var elements = context.elements;
elements.filter(function(shape) {
var host = shape.host;
return shouldReplace(shape, host);
}).map(function(shape) {
return elements.indexOf(shape);
}).forEach(function(index) {
context.elements[ index ] = self.replaceShape(elements[ index ]);
});
}, true);
this.preExecute('elements.move', LOW_PRIORITY$b, function(context) {
var shapes = context.shapes,
newHost = context.newHost;
shapes.forEach(function(shape, index) {
var host = shape.host;
if (shouldReplace(shape, includes$6(shapes, host) ? host : newHost)) {
shapes[ index ] = self.replaceShape(shape);
}
});
}, true);
}
DetachEventBehavior.$inject = [
'bpmnReplace',
'injector'
];
e(DetachEventBehavior, CommandInterceptor);
DetachEventBehavior.prototype.replaceShape = function(shape) {
var eventDefinition = getEventDefinition(shape),
intermediateEvent;
if (eventDefinition) {
intermediateEvent = {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: eventDefinition.$type
};
} else {
intermediateEvent = {
type: 'bpmn:IntermediateThrowEvent'
};
}
return this._bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false });
};
// helpers //////////
function getEventDefinition(element) {
var businessObject = getBusinessObject(element),
eventDefinitions = businessObject.eventDefinitions;
return eventDefinitions && eventDefinitions[0];
}
function shouldReplace(shape, host) {
return !isLabel$6(shape) && is$1(shape, 'bpmn:BoundaryEvent') && !host;
}
function includes$6(array, item) {
return array.indexOf(item) !== -1;
}
function DropOnFlowBehavior(eventBus, bpmnRules, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Reconnect start / end of a connection after
* dropping an element on a flow.
*/
function insertShape(shape, targetFlow, positionOrBounds) {
var waypoints = targetFlow.waypoints,
waypointsBefore,
waypointsAfter,
dockingPoint,
source,
target,
incomingConnection,
outgoingConnection,
oldOutgoing = shape.outgoing.slice(),
oldIncoming = shape.incoming.slice();
var mid;
if (isNumber(positionOrBounds.width)) {
mid = getMid(positionOrBounds);
} else {
mid = positionOrBounds;
}
var intersection = getApproxIntersection(waypoints, mid);
if (intersection) {
waypointsBefore = waypoints.slice(0, intersection.index);
waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0));
// due to inaccuracy intersection might have been found
if (!waypointsBefore.length || !waypointsAfter.length) {
return;
}
dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : mid;
// if last waypointBefore is inside shape's bounds, ignore docking point
if (waypointsBefore.length === 1 || !isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length - 1])) {
waypointsBefore.push(copy(dockingPoint));
}
// if first waypointAfter is inside shape's bounds, ignore docking point
if (waypointsAfter.length === 1 || !isPointInsideBBox(shape, waypointsAfter[0])) {
waypointsAfter.unshift(copy(dockingPoint));
}
}
source = targetFlow.source;
target = targetFlow.target;
if (bpmnRules.canConnect(source, shape, targetFlow)) {
// reconnect source -> inserted shape
modeling.reconnectEnd(targetFlow, shape, waypointsBefore || mid);
incomingConnection = targetFlow;
}
if (bpmnRules.canConnect(shape, target, targetFlow)) {
if (!incomingConnection) {
// reconnect inserted shape -> end
modeling.reconnectStart(targetFlow, shape, waypointsAfter || mid);
outgoingConnection = targetFlow;
} else {
outgoingConnection = modeling.connect(
shape, target, { type: targetFlow.type, waypoints: waypointsAfter }
);
}
}
var duplicateConnections = [].concat(
incomingConnection && filter(oldIncoming, function(connection) {
return connection.source === incomingConnection.source;
}) || [],
outgoingConnection && filter(oldOutgoing, function(connection) {
return connection.target === outgoingConnection.target;
}) || []
);
if (duplicateConnections.length) {
modeling.removeElements(duplicateConnections);
}
}
this.preExecute('elements.move', function(context) {
var newParent = context.newParent,
shapes = context.shapes,
delta = context.delta,
shape = shapes[0];
if (!shape || !newParent) {
return;
}
// if the new parent is a connection,
// change it to the new parent's parent
if (newParent && newParent.waypoints) {
context.newParent = newParent = newParent.parent;
}
var shapeMid = getMid(shape);
var newShapeMid = {
x: shapeMid.x + delta.x,
y: shapeMid.y + delta.y
};
// find a connection which intersects with the
// element's mid point
var connection = find(newParent.children, function(element) {
var canInsert = bpmnRules.canInsert(shapes, element);
return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
});
if (connection) {
context.targetFlow = connection;
context.position = newShapeMid;
}
}, true);
this.postExecuted('elements.move', function(context) {
var shapes = context.shapes,
targetFlow = context.targetFlow,
position = context.position;
if (targetFlow) {
insertShape(shapes[0], targetFlow, position);
}
}, true);
this.preExecute('shape.create', function(context) {
var parent = context.parent,
shape = context.shape;
if (bpmnRules.canInsert(shape, parent)) {
context.targetFlow = parent;
context.parent = parent.parent;
}
}, true);
this.postExecuted('shape.create', function(context) {
var shape = context.shape,
targetFlow = context.targetFlow,
positionOrBounds = context.position;
if (targetFlow) {
insertShape(shape, targetFlow, positionOrBounds);
}
}, true);
}
e(DropOnFlowBehavior, CommandInterceptor);
DropOnFlowBehavior.$inject = [
'eventBus',
'bpmnRules',
'modeling'
];
// helpers /////////////////////
function isPointInsideBBox(bbox, point) {
var x = point.x,
y = point.y;
return x >= bbox.x &&
x <= bbox.x + bbox.width &&
y >= bbox.y &&
y <= bbox.y + bbox.height;
}
function copy(obj) {
return assign({}, obj);
}
function EventBasedGatewayBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Remove existing sequence flows of event-based target before connecting
* from event-based gateway.
*/
this.preExecuted('connection.create', function(event) {
var context = event.context,
source = context.source,
target = context.target,
existingIncomingConnections = target.incoming.slice();
if (context.hints && context.hints.createElementsBehavior === false) {
return;
}
if (
is$1(source, 'bpmn:EventBasedGateway') &&
target.incoming.length
) {
existingIncomingConnections.filter(isSequenceFlow)
.forEach(function(sequenceFlow) {
modeling.removeConnection(sequenceFlow);
});
}
});
/**
* After replacing shape with event-based gateway, remove incoming sequence
* flows of event-based targets which do not belong to event-based gateway
* source.
*/
this.preExecuted('shape.replace', function(event) {
var newShape = event.context.newShape,
newShapeTargets,
newShapeTargetsIncomingSequenceFlows;
if (!is$1(newShape, 'bpmn:EventBasedGateway')) {
return;
}
newShapeTargets = newShape.outgoing.filter(isSequenceFlow)
.map(function(sequenceFlow) {
return sequenceFlow.target;
});
newShapeTargetsIncomingSequenceFlows = newShapeTargets.reduce(function(sequenceFlows, target) {
var incomingSequenceFlows = target.incoming.filter(isSequenceFlow);
return sequenceFlows.concat(incomingSequenceFlows);
}, []);
newShapeTargetsIncomingSequenceFlows.forEach(function(sequenceFlow) {
if (sequenceFlow.source !== newShape) {
modeling.removeConnection(sequenceFlow);
}
});
});
}
EventBasedGatewayBehavior.$inject = [
'eventBus',
'modeling'
];
e(EventBasedGatewayBehavior, CommandInterceptor);
// helpers //////////////////////
function isSequenceFlow(connection) {
return is$1(connection, 'bpmn:SequenceFlow');
}
var HIGH_PRIORITY$a = 1500;
var HIGHEST_PRIORITY = 2000;
/**
* Correct hover targets in certain situations to improve diagram interaction.
*
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function FixHoverBehavior(elementRegistry, eventBus, canvas) {
eventBus.on([
'create.hover',
'create.move',
'create.out',
'create.end',
'shape.move.hover',
'shape.move.move',
'shape.move.out',
'shape.move.end'
], HIGH_PRIORITY$a, function(event) {
var context = event.context,
shape = context.shape || event.shape,
hover = event.hover;
// ensure elements are not dropped onto a bpmn:Lane but onto
// the underlying bpmn:Participant
if (is$1(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
event.hover = getLanesRoot(hover);
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
var rootElement = canvas.getRootElement();
// ensure bpmn:Group and label elements are dropped
// always onto the root
if (hover !== rootElement && (shape.labelTarget || is$1(shape, 'bpmn:Group'))) {
event.hover = rootElement;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'connect.hover',
'connect.out',
'connect.end',
'connect.cleanup',
'global-connect.hover',
'global-connect.out',
'global-connect.end',
'global-connect.cleanup'
], HIGH_PRIORITY$a, function(event) {
var hover = event.hover;
// ensure connections start/end on bpmn:Participant,
// not the underlying bpmn:Lane
if (is$1(hover, 'bpmn:Lane')) {
event.hover = getLanesRoot(hover) || hover;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'bendpoint.move.hover'
], HIGH_PRIORITY$a, function(event) {
var context = event.context,
hover = event.hover,
type = context.type;
// ensure reconnect start/end on bpmn:Participant,
// not the underlying bpmn:Lane
if (is$1(hover, 'bpmn:Lane') && /reconnect/.test(type)) {
event.hover = getLanesRoot(hover) || hover;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'connect.start'
], HIGH_PRIORITY$a, function(event) {
var context = event.context,
start = context.start;
// ensure connect start on bpmn:Participant,
// not the underlying bpmn:Lane
if (is$1(start, 'bpmn:Lane')) {
context.start = getLanesRoot(start) || start;
}
});
// allow movement of participants from lanes
eventBus.on('shape.move.start', HIGHEST_PRIORITY, function(event) {
var shape = event.shape;
if (is$1(shape, 'bpmn:Lane')) {
event.shape = getLanesRoot(shape) || shape;
}
});
}
FixHoverBehavior.$inject = [
'elementRegistry',
'eventBus',
'canvas'
];
/**
* Creates a new bpmn:CategoryValue inside a new bpmn:Category
*
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement} categoryValue.
*/
function createCategory(bpmnFactory) {
return bpmnFactory.create('bpmn:Category');
}
/**
* Creates a new bpmn:CategoryValue inside a new bpmn:Category
*
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement} categoryValue.
*/
function createCategoryValue(bpmnFactory) {
return bpmnFactory.create('bpmn:CategoryValue');
}
/**
* Adds category value to definitions
*
* @param {ModdleElement} categoryValue
* @param {ModdleElement} category
* @param {ModdleElement} definitions
*
* @return {ModdleElement} categoryValue
*/
function linkCategoryValue(categoryValue, category, definitions) {
add(category.get('categoryValue'), categoryValue);
categoryValue.$parent = category;
add(definitions.get('rootElements'), category);
category.$parent = definitions;
return categoryValue;
}
/**
* Unlink category value from parent
*
* @param {ModdleElement} categoryValue
*
* @return {ModdleElement} categoryValue
*/
function unlinkCategoryValue(categoryValue) {
var category = categoryValue.$parent;
if (category) {
remove(category.get('categoryValue'), categoryValue);
categoryValue.$parent = null;
}
return categoryValue;
}
/**
* Unlink category from parent
*
* @param {ModdleElement} category
* @param {ModdleElement} definitions
*
* @return {ModdleElement} categoryValue
*/
function unlinkCategory(category) {
var definitions = category.$parent;
if (definitions) {
remove(definitions.get('rootElements'), category);
category.$parent = null;
}
return category;
}
var LOWER_PRIORITY = 770;
/**
* BPMN specific Group behavior
*/
function GroupBehavior(
bpmnFactory,
bpmnjs,
elementRegistry,
eventBus,
injector,
moddleCopy
) {
injector.invoke(CommandInterceptor, this);
/**
* Returns all group element in the current registry
*
* @return {Array<djs.model.shape>} a list of group shapes
*/
function getGroupElements() {
return elementRegistry.filter(function(e) {
return is$1(e, 'bpmn:Group');
});
}
/**
* Returns true if given category is referenced in one of the given elements
*
* @param { djs.model.Element[] } elements
* @param { ModdleElement } category
*
* @return { boolean }
*/
function isReferencedCategory(elements, category) {
return elements.some(function(element) {
var businessObject = getBusinessObject(element);
var _category = businessObject.categoryValueRef && businessObject.categoryValueRef.$parent;
return _category === category;
});
}
/**
* Returns true if given categoryValue is referenced in one of the given elements
*
* @param { djs.model.Element[] } elements
* @param { ModdleElement } categoryValue
*
* @return { boolean }
*/
function isReferencedCategoryValue(elements, categoryValue) {
return elements.some(function(element) {
var businessObject = getBusinessObject(element);
return businessObject.categoryValueRef === categoryValue;
});
}
/**
* Remove category value unless it is still referenced
*
* @param {ModdleElement} categoryValue
* @param {ModdleElement} category
* @param {ModdleElement} businessObject
*/
function removeCategoryValue(categoryValue, category, businessObject) {
var groups = getGroupElements().filter(function(element) {
return element.businessObject !== businessObject;
});
if (category && !isReferencedCategory(groups, category)) {
unlinkCategory(category);
}
if (categoryValue && !isReferencedCategoryValue(groups, categoryValue)) {
unlinkCategoryValue(categoryValue);
}
}
/**
* Add category value
*
* @param {ModdleElement} categoryValue
* @param {ModdleElement} category
*/
function addCategoryValue(categoryValue, category) {
return linkCategoryValue(categoryValue, category, bpmnjs.getDefinitions());
}
function setCategoryValue(element, context) {
var businessObject = getBusinessObject(element),
categoryValue = businessObject.categoryValueRef;
if (!categoryValue) {
categoryValue =
businessObject.categoryValueRef =
context.categoryValue = (
context.categoryValue || createCategoryValue(bpmnFactory)
);
}
var category = categoryValue.$parent;
if (!category) {
category =
categoryValue.$parent =
context.category = (
context.category || createCategory(bpmnFactory)
);
}
addCategoryValue(categoryValue, category, bpmnjs.getDefinitions());
}
function unsetCategoryValue(element, context) {
var category = context.category,
categoryValue = context.categoryValue,
businessObject = getBusinessObject(element);
if (categoryValue) {
businessObject.categoryValueRef = null;
removeCategoryValue(categoryValue, category, businessObject);
} else {
removeCategoryValue(null, businessObject.categoryValueRef.$parent, businessObject);
}
}
// ensure category + value exist before label editing
this.execute('label.create', function(event) {
var context = event.context,
labelTarget = context.labelTarget;
if (!is$1(labelTarget, 'bpmn:Group')) {
return;
}
setCategoryValue(labelTarget, context);
});
this.revert('label.create', function(event) {
var context = event.context,
labelTarget = context.labelTarget;
if (!is$1(labelTarget, 'bpmn:Group')) {
return;
}
unsetCategoryValue(labelTarget, context);
});
// remove referenced category + value when group was deleted
this.execute('shape.delete', function(event) {
var context = event.context,
shape = context.shape,
businessObject = getBusinessObject(shape);
if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
var categoryValue = context.categoryValue = businessObject.categoryValueRef,
category;
if (categoryValue) {
category = context.category = categoryValue.$parent;
removeCategoryValue(categoryValue, category, businessObject);
businessObject.categoryValueRef = null;
}
});
this.reverted('shape.delete', function(event) {
var context = event.context,
shape = context.shape;
if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
var category = context.category,
categoryValue = context.categoryValue,
businessObject = getBusinessObject(shape);
if (categoryValue) {
businessObject.categoryValueRef = categoryValue;
addCategoryValue(categoryValue, category);
}
});
// create new category + value when group was created
this.execute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
if (getBusinessObject(shape).categoryValueRef) {
setCategoryValue(shape, context);
}
});
this.reverted('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (!is$1(shape, 'bpmn:Group') || shape.labelTarget) {
return;
}
if (getBusinessObject(shape).categoryValueRef) {
unsetCategoryValue(shape, context);
}
});
// copy + paste categoryValueRef with group
function copy(bo, clone) {
var targetBo = bpmnFactory.create(bo.$type);
return moddleCopy.copyElement(bo, targetBo, null, clone);
}
eventBus.on('copyPaste.copyElement', LOWER_PRIORITY, function(context) {
var descriptor = context.descriptor,
element = context.element;
if (!is$1(element, 'bpmn:Group') || element.labelTarget) {
return;
}
var groupBo = getBusinessObject(element);
if (groupBo.categoryValueRef) {
var categoryValue = groupBo.categoryValueRef;
descriptor.categoryValue = copy(categoryValue, true);
if (categoryValue.$parent) {
descriptor.category = copy(categoryValue.$parent, true);
}
}
});
eventBus.on('copyPaste.pasteElement', LOWER_PRIORITY, function(context) {
var descriptor = context.descriptor,
businessObject = descriptor.businessObject,
categoryValue = descriptor.categoryValue,
category = descriptor.category;
if (categoryValue) {
categoryValue = businessObject.categoryValueRef = copy(categoryValue);
}
if (category) {
categoryValue.$parent = copy(category);
}
delete descriptor.category;
delete descriptor.categoryValue;
});
}
GroupBehavior.$inject = [
'bpmnFactory',
'bpmnjs',
'elementRegistry',
'eventBus',
'injector',
'moddleCopy'
];
e(GroupBehavior, CommandInterceptor);
/**
* Returns the intersection between two line segments a and b.
*
* @param {Point} l1s
* @param {Point} l1e
* @param {Point} l2s
* @param {Point} l2e
*
* @return {Point}
*/
function lineIntersect(l1s, l1e, l2s, l2e) {
// if the lines intersect, the result contains the x and y of the
// intersection (treating the lines as infinite) and booleans for
// whether line segment 1 or line segment 2 contain the point
var denominator, a, b, c, numerator;
denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y));
if (denominator == 0) {
return null;
}
a = l1s.y - l2s.y;
b = l1s.x - l2s.x;
numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b);
c = numerator / denominator;
// if we cast these lines infinitely in
// both directions, they intersect here
return {
x: Math.round(l1s.x + (c * (l1e.x - l1s.x))),
y: Math.round(l1s.y + (c * (l1e.y - l1s.y)))
};
}
/**
* Fix broken dockings after DI imports.
*
* @param {EventBus} eventBus
*/
function ImportDockingFix(eventBus) {
function adjustDocking(startPoint, nextPoint, elementMid) {
var elementTop = {
x: elementMid.x,
y: elementMid.y - 50
};
var elementLeft = {
x: elementMid.x - 50,
y: elementMid.y
};
var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop),
horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft);
// original is horizontal or vertical center cross intersection
var centerIntersect;
if (verticalIntersect && horizontalIntersect) {
if (getDistance$1(verticalIntersect, elementMid) > getDistance$1(horizontalIntersect, elementMid)) {
centerIntersect = horizontalIntersect;
} else {
centerIntersect = verticalIntersect;
}
} else {
centerIntersect = verticalIntersect || horizontalIntersect;
}
startPoint.original = centerIntersect;
}
function fixDockings(connection) {
var waypoints = connection.waypoints;
adjustDocking(
waypoints[0],
waypoints[1],
getMid(connection.source)
);
adjustDocking(
waypoints[waypoints.length - 1],
waypoints[waypoints.length - 2],
getMid(connection.target)
);
}
eventBus.on('bpmnElement.added', function(e) {
var element = e.element;
if (element.waypoints) {
fixDockings(element);
}
});
}
ImportDockingFix.$inject = [
'eventBus'
];
// helpers //////////////////////
function getDistance$1(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
/**
* A component that makes sure that each created or updated
* Pool and Lane is assigned an isHorizontal property set to true.
*
* @param {EventBus} eventBus
*/
function IsHorizontalFix(eventBus) {
CommandInterceptor.call(this, eventBus);
var elementTypesToUpdate = [
'bpmn:Participant',
'bpmn:Lane'
];
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], function(event) {
var shape = event.context.shape,
bo = getBusinessObject(shape),
di = getDi(shape);
if (isAny(bo, elementTypesToUpdate) && !di.get('isHorizontal')) {
// set attribute directly to avoid modeling#updateProperty side effects
di.set('isHorizontal', true);
}
});
}
IsHorizontalFix.$inject = [ 'eventBus' ];
e(IsHorizontalFix, CommandInterceptor);
var sqrt = Math.sqrt,
min$1 = Math.min,
max$3 = Math.max,
abs$3 = Math.abs;
/**
* Calculate the square (power to two) of a number.
*
* @param {number} n
*
* @return {number}
*/
function sq(n) {
return Math.pow(n, 2);
}
/**
* Get distance between two points.
*
* @param {Point} p1
* @param {Point} p2
*
* @return {number}
*/
function getDistance(p1, p2) {
return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
}
/**
* Return the attachment of the given point on the specified line.
*
* The attachment is either a bendpoint (attached to the given point)
* or segment (attached to a location on a line segment) attachment:
*
* ```javascript
* var pointAttachment = {
* type: 'bendpoint',
* bendpointIndex: 3,
* position: { x: 10, y: 10 } // the attach point on the line
* };
*
* var segmentAttachment = {
* type: 'segment',
* segmentIndex: 2,
* relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
* position: { x: 10, y: 10 } // the attach point on the line
* };
* ```
*
* @param {Point} point
* @param {Array<Point>} line
*
* @return {Object} attachment
*/
function getAttachment(point, line) {
var idx = 0,
segmentStart,
segmentEnd,
segmentStartDistance,
segmentEndDistance,
attachmentPosition,
minDistance,
intersections,
attachment,
attachmentDistance,
closestAttachmentDistance,
closestAttachment;
for (idx = 0; idx < line.length - 1; idx++) {
segmentStart = line[idx];
segmentEnd = line[idx + 1];
if (pointsEqual(segmentStart, segmentEnd)) {
intersections = [ segmentStart ];
} else {
segmentStartDistance = getDistance(point, segmentStart);
segmentEndDistance = getDistance(point, segmentEnd);
minDistance = min$1(segmentStartDistance, segmentEndDistance);
intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
}
if (intersections.length < 1) {
throw new Error('expected between [1, 2] circle -> line intersections');
}
// one intersection -> bendpoint attachment
if (intersections.length === 1) {
attachment = {
type: 'bendpoint',
position: intersections[0],
segmentIndex: idx,
bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
};
}
// two intersections -> segment attachment
if (intersections.length === 2) {
attachmentPosition = mid$1(intersections[0], intersections[1]);
attachment = {
type: 'segment',
position: attachmentPosition,
segmentIndex: idx,
relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd)
};
}
attachmentDistance = getDistance(attachment.position, point);
if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
closestAttachment = attachment;
closestAttachmentDistance = attachmentDistance;
}
}
return closestAttachment;
}
/**
* Gets the intersection between a circle and a line segment.
*
* @param {Point} s1 segment start
* @param {Point} s2 segment end
* @param {Point} cc circle center
* @param {number} cr circle radius
*
* @return {Array<Point>} intersections
*/
function getCircleSegmentIntersections(s1, s2, cc, cr) {
var baX = s2.x - s1.x;
var baY = s2.y - s1.y;
var caX = cc.x - s1.x;
var caY = cc.y - s1.y;
var a = baX * baX + baY * baY;
var bBy2 = baX * caX + baY * caY;
var c = caX * caX + caY * caY - cr * cr;
var pBy2 = bBy2 / a;
var q = c / a;
var disc = pBy2 * pBy2 - q;
// check against negative value to work around
// negative, very close to zero results (-4e-15)
// being produced in some environments
if (disc < 0 && disc > -0.000001) {
disc = 0;
}
if (disc < 0) {
return [];
}
// if disc == 0 ... dealt with later
var tmpSqrt = sqrt(disc);
var abScalingFactor1 = -pBy2 + tmpSqrt;
var abScalingFactor2 = -pBy2 - tmpSqrt;
var i1 = {
x: s1.x - baX * abScalingFactor1,
y: s1.y - baY * abScalingFactor1
};
if (disc === 0) { // abScalingFactor1 == abScalingFactor2
return [ i1 ];
}
var i2 = {
x: s1.x - baX * abScalingFactor2,
y: s1.y - baY * abScalingFactor2
};
// return only points on line segment
return [ i1, i2 ].filter(function(p) {
return isPointInSegment(p, s1, s2);
});
}
function isPointInSegment(p, segmentStart, segmentEnd) {
return (
fenced(p.x, segmentStart.x, segmentEnd.x) &&
fenced(p.y, segmentStart.y, segmentEnd.y)
);
}
function fenced(n, rangeStart, rangeEnd) {
// use matching threshold to work around
// precision errors in intersection computation
return (
n >= min$1(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
n <= max$3(rangeStart, rangeEnd) + EQUAL_THRESHOLD
);
}
/**
* Calculate mid of two points.
*
* @param {Point} p1
* @param {Point} p2
*
* @return {Point}
*/
function mid$1(p1, p2) {
return {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2
};
}
var EQUAL_THRESHOLD = 0.1;
function pointsEqual(p1, p2) {
return (
abs$3(p1.x - p2.x) <= EQUAL_THRESHOLD &&
abs$3(p1.y - p2.y) <= EQUAL_THRESHOLD
);
}
function findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
var index = attachment.segmentIndex;
var offset = newWaypoints.length - oldWaypoints.length;
// segmentMove happened
if (hints.segmentMove) {
var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;
// if point was on moved segment return new segment index
if (index === oldSegmentStartIndex) {
return newSegmentStartIndex;
}
// point is after new segment index
if (index >= newSegmentStartIndex) {
return (index + offset < newSegmentStartIndex) ? newSegmentStartIndex : index + offset;
}
// if point is before new segment index
return index;
}
// bendpointMove happened
if (hints.bendpointMove) {
var insert = hints.bendpointMove.insert,
bendpointIndex = hints.bendpointMove.bendpointIndex,
newIndex;
// waypoints length didnt change
if (offset === 0) {
return index;
}
// point behind new/removed bendpoint
if (index >= bendpointIndex) {
newIndex = insert ? index + 1 : index - 1;
}
// point before new/removed bendpoint
if (index < bendpointIndex) {
newIndex = index;
// decide point should take right or left segment
if (insert && attachment.type !== 'bendpoint' && bendpointIndex - 1 === index) {
var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);
if (rel < attachment.relativeLocation) {
newIndex++;
}
}
}
return newIndex;
}
// start/end changed
if (offset === 0) {
return index;
}
if (hints.connectionStart && index === 0) {
return 0;
}
if (hints.connectionEnd && index === oldWaypoints.length - 2) {
return newWaypoints.length - 2;
}
// if nothing fits, take the middle segment
return Math.floor((newWaypoints.length - 2) / 2);
}
/**
* Calculate the required adjustment (move delta) for the given point
* after the connection waypoints got updated.
*
* @param {Point} position
* @param {Array<Point>} newWaypoints
* @param {Array<Point>} oldWaypoints
* @param {Object} hints
*
* @return {Object} result
* @return {Point} result.point
* @return {Point} result.delta
*/
function getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints) {
var dx = 0,
dy = 0;
var oldPosition = {
point: position,
delta: { x: 0, y: 0 }
};
// get closest attachment
var attachment = getAttachment(position, oldWaypoints),
oldLabelLineIndex = attachment.segmentIndex,
newLabelLineIndex = findNewLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
// should never happen
// TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
if (newLabelLineIndex < 0 ||
newLabelLineIndex > newWaypoints.length - 2 ||
newLabelLineIndex === null) {
return oldPosition;
}
var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
newLabelLine = getLine(newWaypoints, newLabelLineIndex),
oldFoot = attachment.position;
var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
angleDelta = getAngleDelta(oldLabelLine, newLabelLine);
// special rule if label on bendpoint
if (attachment.type === 'bendpoint') {
var offset = newWaypoints.length - oldWaypoints.length,
oldBendpointIndex = attachment.bendpointIndex,
oldBendpoint = oldWaypoints[oldBendpointIndex];
// bendpoint position hasn't changed, return same position
if (newWaypoints.indexOf(oldBendpoint) !== -1) {
return oldPosition;
}
// new bendpoint and old bendpoint have same index, then just return the offset
if (offset === 0) {
var newBendpoint = newWaypoints[oldBendpointIndex];
dx = newBendpoint.x - attachment.position.x,
dy = newBendpoint.y - attachment.position.y;
return {
delta: {
x: dx,
y: dy
},
point: {
x: position.x + dx,
y: position.y + dy
}
};
}
// if bendpoints get removed
if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
}
}
var newFoot = {
x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
};
// the rotated vector to label
var newLabelVector = rotateVector({
x: position.x - oldFoot.x,
y: position.y - oldFoot.y
}, angleDelta);
// the new relative position
dx = newFoot.x + newLabelVector.x - position.x;
dy = newFoot.y + newLabelVector.y - position.y;
return {
point: roundPoint(newFoot),
delta: roundPoint({
x: dx,
y: dy
})
};
}
// HELPERS //////////////////////
function relativePositionMidWaypoint(waypoints, idx) {
var distanceSegment1 = getDistancePointPoint(waypoints[idx - 1], waypoints[idx]),
distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx + 1]);
var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2);
return relativePosition;
}
function getAngleDelta(l1, l2) {
var a1 = getAngle(l1),
a2 = getAngle(l2);
return a2 - a1;
}
function getLine(waypoints, idx) {
return [ waypoints[idx], waypoints[idx + 1] ];
}
function getRelativeFootPosition(line, foot) {
var length = getDistancePointPoint(line[0], line[1]),
lengthToFoot = getDistancePointPoint(line[0], foot);
return length === 0 ? 0 : lengthToFoot / length;
}
/**
* Calculate the required adjustment (move delta) for the given label
* after the connection waypoints got updated.
*
* @param {djs.model.Label} label
* @param {Array<Point>} newWaypoints
* @param {Array<Point>} oldWaypoints
* @param {Object} hints
*
* @return {Point} delta
*/
function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
var labelPosition = getLabelMid(label);
return getAnchorPointAdjustment(labelPosition, newWaypoints, oldWaypoints, hints).delta;
}
// HELPERS //////////////////////
function getLabelMid(label) {
return {
x: label.x + label.width / 2,
y: label.y + label.height / 2
};
}
/**
* Calculates the absolute point relative to the new element's position
*
* @param {point} point [absolute]
* @param {bounds} oldBounds
* @param {bounds} newBounds
*
* @return {point} point [absolute]
*/
function getNewAttachPoint(point, oldBounds, newBounds) {
var oldCenter = center(oldBounds),
newCenter = center(newBounds),
oldDelta = delta(point, oldCenter);
var newDelta = {
x: oldDelta.x * (newBounds.width / oldBounds.width),
y: oldDelta.y * (newBounds.height / oldBounds.height)
};
return roundPoint({
x: newCenter.x + newDelta.x,
y: newCenter.y + newDelta.y
});
}
/**
* Calculates the shape's delta relative to a new position
* of a certain element's bounds
*
* @param {djs.model.Shape} point [absolute]
* @param {bounds} oldBounds
* @param {bounds} newBounds
*
* @return {delta} delta
*/
function getNewAttachShapeDelta(shape, oldBounds, newBounds) {
var shapeCenter = center(shape),
oldCenter = center(oldBounds),
newCenter = center(newBounds),
shapeDelta = delta(shape, shapeCenter),
oldCenterDelta = delta(shapeCenter, oldCenter),
stickyPositionDelta = getStickyPositionDelta(shapeCenter, oldBounds, newBounds);
if (stickyPositionDelta) {
return stickyPositionDelta;
}
var newCenterDelta = {
x: oldCenterDelta.x * (newBounds.width / oldBounds.width),
y: oldCenterDelta.y * (newBounds.height / oldBounds.height)
};
var newShapeCenter = {
x: newCenter.x + newCenterDelta.x,
y: newCenter.y + newCenterDelta.y
};
return roundPoint({
x: newShapeCenter.x + shapeDelta.x - shape.x,
y: newShapeCenter.y + shapeDelta.y - shape.y
});
}
function getStickyPositionDelta(oldShapeCenter, oldBounds, newBounds) {
var oldTRBL = asTRBL(oldBounds),
newTRBL = asTRBL(newBounds);
if (isMoved(oldTRBL, newTRBL)) {
return null;
}
var oldOrientation = getOrientation(oldBounds, oldShapeCenter),
stickyPositionDelta,
newShapeCenter,
newOrientation;
if (oldOrientation === 'top') {
stickyPositionDelta = {
x: 0,
y: newTRBL.bottom - oldTRBL.bottom
};
} else if (oldOrientation === 'bottom') {
stickyPositionDelta = {
x: 0,
y: newTRBL.top - oldTRBL.top
};
} else if (oldOrientation === 'right') {
stickyPositionDelta = {
x: newTRBL.left - oldTRBL.left,
y: 0
};
} else if (oldOrientation === 'left') {
stickyPositionDelta = {
x: newTRBL.right - oldTRBL.right,
y: 0
};
} else {
// fallback to proportional movement for corner-placed attachments
return null;
}
newShapeCenter = {
x: oldShapeCenter.x + stickyPositionDelta.x,
y: oldShapeCenter.y + stickyPositionDelta.y
};
newOrientation = getOrientation(newBounds, newShapeCenter);
if (newOrientation !== oldOrientation) {
// fallback to proportional movement if orientation would otherwise change
return null;
}
return stickyPositionDelta;
}
function isMoved(oldTRBL, newTRBL) {
return isHorizontallyMoved(oldTRBL, newTRBL) || isVerticallyMoved(oldTRBL, newTRBL);
}
function isHorizontallyMoved(oldTRBL, newTRBL) {
return oldTRBL.right !== newTRBL.right && oldTRBL.left !== newTRBL.left;
}
function isVerticallyMoved(oldTRBL, newTRBL) {
return oldTRBL.top !== newTRBL.top && oldTRBL.bottom !== newTRBL.bottom;
}
var DEFAULT_LABEL_DIMENSIONS = {
width: 90,
height: 20
};
var NAME_PROPERTY = 'name';
var TEXT_PROPERTY = 'text';
/**
* A component that makes sure that external labels are added
* together with respective elements and properly updated (DI wise)
* during move.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {BpmnFactory} bpmnFactory
* @param {TextRenderer} textRenderer
*/
function LabelBehavior(
eventBus, modeling, bpmnFactory,
textRenderer) {
CommandInterceptor.call(this, eventBus);
// update label if name property was updated
this.postExecute('element.updateProperties', function(e) {
var context = e.context,
element = context.element,
properties = context.properties;
if (NAME_PROPERTY in properties) {
modeling.updateLabel(element, properties[NAME_PROPERTY]);
}
if (TEXT_PROPERTY in properties
&& is$1(element, 'bpmn:TextAnnotation')) {
var newBounds = textRenderer.getTextAnnotationBounds(
{
x: element.x,
y: element.y,
width: element.width,
height: element.height
},
properties[TEXT_PROPERTY] || ''
);
modeling.updateLabel(element, properties.text, newBounds);
}
});
// create label shape after shape/connection was created
this.postExecute([ 'shape.create', 'connection.create' ], function(e) {
var context = e.context,
hints = context.hints || {};
if (hints.createElementsBehavior === false) {
return;
}
var element = context.shape || context.connection,
businessObject = element.businessObject;
if (isLabel$6(element) || !isLabelExternal(element)) {
return;
}
// only create label if attribute available
if (!getLabel(element)) {
return;
}
var labelCenter = getExternalLabelMid(element);
// we don't care about x and y
var labelDimensions = textRenderer.getExternalLabelBounds(
DEFAULT_LABEL_DIMENSIONS,
getLabel(element)
);
modeling.createLabel(element, labelCenter, {
id: businessObject.id + '_label',
businessObject: businessObject,
width: labelDimensions.width,
height: labelDimensions.height
});
});
// update label after label shape was deleted
this.postExecute('shape.delete', function(event) {
var context = event.context,
labelTarget = context.labelTarget,
hints = context.hints || {};
// check if label
if (labelTarget && hints.unsetLabel !== false) {
modeling.updateLabel(labelTarget, null, null, { removeShape: false });
}
});
// update di information on label creation
this.postExecute([ 'label.create' ], function(event) {
var context = event.context,
element = context.shape,
labelTarget = context.labelTarget,
di;
// we want to trigger on real labels only
if (!labelTarget) {
return;
}
// we want to trigger on BPMN elements only
if (!is$1(labelTarget, 'bpmn:BaseElement')) {
return;
}
di = getDi(labelTarget);
if (!di.label) {
di.label = bpmnFactory.create('bpmndi:BPMNLabel', {
bounds: bpmnFactory.create('dc:Bounds')
});
element.di = di;
}
assign(di.label.bounds, {
x: element.x,
y: element.y,
width: element.width,
height: element.height
});
});
function getVisibleLabelAdjustment(event) {
var context = event.context,
connection = context.connection,
label = connection.label,
hints = assign({}, context.hints),
newWaypoints = context.newWaypoints || connection.waypoints,
oldWaypoints = context.oldWaypoints;
if (typeof hints.startChanged === 'undefined') {
hints.startChanged = !!hints.connectionStart;
}
if (typeof hints.endChanged === 'undefined') {
hints.endChanged = !!hints.connectionEnd;
}
return getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
}
this.postExecute([
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context,
hints = context.hints || {};
if (hints.labelBehavior === false) {
return;
}
var connection = context.connection,
label = connection.label,
labelAdjustment;
// handle missing label as well as the case
// that the label parent does not exist (yet),
// because it is being pasted / created via multi element create
//
// Cf. https://github.com/bpmn-io/bpmn-js/pull/1227
if (!label || !label.parent) {
return;
}
labelAdjustment = getVisibleLabelAdjustment(event);
modeling.moveShape(label, labelAdjustment);
});
// keep label position on shape replace
this.postExecute([ 'shape.replace' ], function(event) {
var context = event.context,
newShape = context.newShape,
oldShape = context.oldShape;
var businessObject = getBusinessObject(newShape);
if (businessObject
&& isLabelExternal(businessObject)
&& oldShape.label
&& newShape.label) {
newShape.label.x = oldShape.label.x;
newShape.label.y = oldShape.label.y;
}
});
// move external label after resizing
this.postExecute('shape.resize', function(event) {
var context = event.context,
shape = context.shape,
newBounds = context.newBounds,
oldBounds = context.oldBounds;
if (hasExternalLabel(shape)) {
var label = shape.label,
labelMid = getMid(label),
edges = asEdges(oldBounds);
// get nearest border point to label as reference point
var referencePoint = getReferencePoint(labelMid, edges);
var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);
modeling.moveShape(label, delta);
}
});
}
e(LabelBehavior, CommandInterceptor);
LabelBehavior.$inject = [
'eventBus',
'modeling',
'bpmnFactory',
'textRenderer'
];
// helpers //////////////////////
/**
* Calculates a reference point delta relative to a new position
* of a certain element's bounds
*
* @param {Point} point
* @param {Bounds} oldBounds
* @param {Bounds} newBounds
*
* @return {Delta} delta
*/
function getReferencePointDelta(referencePoint, oldBounds, newBounds) {
var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);
return roundPoint(delta(newReferencePoint, referencePoint));
}
/**
* Generates the nearest point (reference point) for a given point
* onto given set of lines
*
* @param {Array<Point, Point>} lines
* @param {Point} point
*
* @param {Point}
*/
function getReferencePoint(point, lines) {
if (!lines.length) {
return;
}
var nearestLine = getNearestLine(point, lines);
return perpendicularFoot(point, nearestLine);
}
/**
* Convert the given bounds to a lines array containing all edges
*
* @param {Bounds|Point} bounds
*
* @return Array<Point>
*/
function asEdges(bounds) {
return [
[ // top
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
}
],
[ // right
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // bottom
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // left
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
}
]
];
}
/**
* Returns the nearest line for a given point by distance
* @param {Point} point
* @param Array<Point> lines
*
* @return Array<Point>
*/
function getNearestLine(point, lines) {
var distances = lines.map(function(l) {
return {
line: l,
distance: getDistancePointLine(point, l)
};
});
var sorted = sortBy(distances, 'distance');
return sorted[0].line;
}
/**
* Calculate the new point after the connection waypoints got updated.
*
* @param {djs.model.Label} label
* @param {Array<Point>} newWaypoints
* @param {Array<Point>} oldWaypoints
* @param {Object} hints
*
* @return {Point} point
*/
function getConnectionAdjustment(position, newWaypoints, oldWaypoints, hints) {
return getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints).point;
}
/**
* A component that makes sure that Associations connected to Connections
* are updated together with the Connection.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
function LayoutConnectionBehavior(
eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
function getnewAnchorPoint(event, point) {
var context = event.context,
connection = context.connection,
hints = assign({}, context.hints),
newWaypoints = context.newWaypoints || connection.waypoints,
oldWaypoints = context.oldWaypoints;
if (typeof hints.startChanged === 'undefined') {
hints.startChanged = !!hints.connectionStart;
}
if (typeof hints.endChanged === 'undefined') {
hints.endChanged = !!hints.connectionEnd;
}
return getConnectionAdjustment(point, newWaypoints, oldWaypoints, hints);
}
this.postExecute([
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context;
var connection = context.connection,
outgoing = connection.outgoing,
incoming = connection.incoming;
incoming.forEach(function(connection) {
var endPoint = connection.waypoints[connection.waypoints.length - 1];
var newEndpoint = getnewAnchorPoint(event, endPoint);
var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);
modeling.updateWaypoints(connection, newWaypoints);
});
outgoing.forEach(function(connection) {
var startpoint = connection.waypoints[0];
var newStartpoint = getnewAnchorPoint(event, startpoint);
var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(1));
modeling.updateWaypoints(connection, newWaypoints);
});
});
this.postExecute([
'connection.move'
], function(event) {
var context = event.context;
var connection = context.connection,
outgoing = connection.outgoing,
incoming = connection.incoming,
delta = context.delta;
incoming.forEach(function(connection) {
var endPoint = connection.waypoints[connection.waypoints.length - 1];
var newEndpoint = {
x: endPoint.x + delta.x,
y: endPoint.y + delta.y
};
var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);
modeling.updateWaypoints(connection, newWaypoints);
});
outgoing.forEach(function(connection) {
var startpoint = connection.waypoints[0];
var newStartpoint = {
x: startpoint.x + delta.x,
y: startpoint.y + delta.y
};
var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(1));
modeling.updateWaypoints(connection, newWaypoints);
});
});
}
e(LayoutConnectionBehavior, CommandInterceptor);
LayoutConnectionBehavior.$inject = [
'eventBus',
'modeling'
];
function getResizedSourceAnchor(connection, shape, oldBounds) {
var waypoints = safeGetWaypoints(connection),
waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape),
oldAnchor = waypoints[0];
// new anchor is the last waypoint enclosed be resized source
if (waypointsInsideNewBounds.length) {
return waypointsInsideNewBounds[ waypointsInsideNewBounds.length - 1 ];
}
return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape);
}
function getResizedTargetAnchor(connection, shape, oldBounds) {
var waypoints = safeGetWaypoints(connection),
waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape),
oldAnchor = waypoints[waypoints.length - 1];
// new anchor is the first waypoint enclosed be resized target
if (waypointsInsideNewBounds.length) {
return waypointsInsideNewBounds[ 0 ];
}
return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape);
}
function getMovedSourceAnchor(connection, source, moveDelta) {
var waypoints = safeGetWaypoints(connection),
oldBounds = subtract(source, moveDelta),
oldAnchor = waypoints[ 0 ];
return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, source);
}
function getMovedTargetAnchor(connection, target, moveDelta) {
var waypoints = safeGetWaypoints(connection),
oldBounds = subtract(target, moveDelta),
oldAnchor = waypoints[ waypoints.length - 1 ];
return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, target);
}
// helpers //////////////////////
function subtract(bounds, delta) {
return {
x: bounds.x - delta.x,
y: bounds.y - delta.y,
width: bounds.width,
height: bounds.height
};
}
/**
* Return waypoints of given connection; throw if non exists (should not happen!!).
*
* @param {Connection} connection
*
* @return {Array<Point>}
*/
function safeGetWaypoints(connection) {
var waypoints = connection.waypoints;
if (!waypoints.length) {
throw new Error('connection#' + connection.id + ': no waypoints');
}
return waypoints;
}
function getWaypointsInsideBounds(waypoints, bounds) {
var originalWaypoints = map(waypoints, getOriginal);
return filter(originalWaypoints, function(waypoint) {
return isInsideBounds(waypoint, bounds);
});
}
/**
* Checks if point is inside bounds, incl. edges.
*
* @param {Point} point
* @param {Bounds} bounds
*/
function isInsideBounds(point, bounds) {
return getOrientation(bounds, point, 1) === 'intersect';
}
function getOriginal(point) {
return point.original || point;
}
/**
* BPMN-specific message flow behavior.
*/
function MessageFlowBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.postExecute('shape.replace', function(context) {
var oldShape = context.oldShape,
newShape = context.newShape;
if (!isParticipantCollapse(oldShape, newShape)) {
return;
}
var messageFlows = getMessageFlows(oldShape);
messageFlows.incoming.forEach(function(incoming) {
var anchor = getResizedTargetAnchor(incoming, newShape, oldShape);
modeling.reconnectEnd(incoming, newShape, anchor);
});
messageFlows.outgoing.forEach(function(outgoing) {
var anchor = getResizedSourceAnchor(outgoing, newShape, oldShape);
modeling.reconnectStart(outgoing, newShape, anchor);
});
}, true);
}
MessageFlowBehavior.$inject = [ 'eventBus', 'modeling' ];
e(MessageFlowBehavior, CommandInterceptor);
// helpers //////////
function isParticipantCollapse(oldShape, newShape) {
return is$1(oldShape, 'bpmn:Participant')
&& isExpanded(oldShape)
&& is$1(newShape, 'bpmn:Participant')
&& !isExpanded(newShape);
}
function getMessageFlows(parent) {
var elements = selfAndAllChildren([ parent ], false);
var incoming = [],
outgoing = [];
elements.forEach(function(element) {
if (element === parent) {
return;
}
element.incoming.forEach(function(connection) {
if (is$1(connection, 'bpmn:MessageFlow')) {
incoming.push(connection);
}
});
element.outgoing.forEach(function(connection) {
if (is$1(connection, 'bpmn:MessageFlow')) {
outgoing.push(connection);
}
});
}, []);
return {
incoming: incoming,
outgoing: outgoing
};
}
var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants';
function ModelingFeedback(eventBus, tooltips, translate) {
function showError(position, message, timeout) {
tooltips.add({
position: {
x: position.x + 5,
y: position.y + 5
},
type: 'error',
timeout: timeout || 2000,
html: '<div>' + message + '</div>'
});
}
eventBus.on([ 'shape.move.rejected', 'create.rejected' ], function(event) {
var context = event.context,
shape = context.shape,
target = context.target;
if (is$1(target, 'bpmn:Collaboration') && is$1(shape, 'bpmn:FlowNode')) {
showError(event, translate(COLLAB_ERR_MSG));
}
});
}
ModelingFeedback.$inject = [
'eventBus',
'tooltips',
'translate'
];
/**
* BPMN specific behavior ensuring that bpmndi:Label's dc:Bounds are removed
* when shape is resized.
*/
function RemoveEmbeddedLabelBoundsBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.preExecute('shape.resize', function(context) {
var shape = context.shape;
var di = getDi(shape),
label = di && di.get('label'),
bounds = label && label.get('bounds');
if (bounds) {
modeling.updateModdleProperties(shape, label, {
bounds: undefined
});
}
}, true);
}
e(RemoveEmbeddedLabelBoundsBehavior, CommandInterceptor);
RemoveEmbeddedLabelBoundsBehavior.$inject = [
'eventBus',
'modeling'
];
function RemoveElementBehavior(eventBus, bpmnRules, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Combine sequence flows when deleting an element
* if there is one incoming and one outgoing
* sequence flow
*/
this.preExecute('shape.delete', function(e) {
var shape = e.context.shape;
// only handle [a] -> [shape] -> [b] patterns
if (shape.incoming.length !== 1 || shape.outgoing.length !== 1) {
return;
}
var inConnection = shape.incoming[0],
outConnection = shape.outgoing[0];
// only handle sequence flows
if (!is$1(inConnection, 'bpmn:SequenceFlow') || !is$1(outConnection, 'bpmn:SequenceFlow')) {
return;
}
if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) {
// compute new, combined waypoints
var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints);
modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints);
}
});
}
e(RemoveElementBehavior, CommandInterceptor);
RemoveElementBehavior.$inject = [
'eventBus',
'bpmnRules',
'modeling'
];
// helpers //////////////////////
function getDocking$1(point) {
return point.original || point;
}
function getNewWaypoints(inWaypoints, outWaypoints) {
var intersection = lineIntersect(
getDocking$1(inWaypoints[inWaypoints.length - 2]),
getDocking$1(inWaypoints[inWaypoints.length - 1]),
getDocking$1(outWaypoints[1]),
getDocking$1(outWaypoints[0]));
if (intersection) {
return [].concat(
inWaypoints.slice(0, inWaypoints.length - 1),
[ intersection ],
outWaypoints.slice(1));
} else {
return [
getDocking$1(inWaypoints[0]),
getDocking$1(outWaypoints[outWaypoints.length - 1])
];
}
}
/**
* BPMN specific remove behavior
*/
function RemoveParticipantBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* morph collaboration diagram into process diagram
* after the last participant has been removed
*/
this.preExecute('shape.delete', function(context) {
var shape = context.shape,
parent = shape.parent;
// activate the behavior if the shape to be removed
// is a participant
if (is$1(shape, 'bpmn:Participant')) {
context.collaborationRoot = parent;
}
}, true);
this.postExecute('shape.delete', function(context) {
var collaborationRoot = context.collaborationRoot;
if (collaborationRoot && !collaborationRoot.businessObject.participants.length) {
// replace empty collaboration with process diagram
modeling.makeProcess();
}
}, true);
}
RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ];
e(RemoveParticipantBehavior, CommandInterceptor);
function ReplaceConnectionBehavior(eventBus, modeling, bpmnRules, injector) {
CommandInterceptor.call(this, eventBus);
var dragging = injector.get('dragging', false);
function fixConnection(connection) {
var source = connection.source,
target = connection.target,
parent = connection.parent;
// do not do anything if connection
// is already deleted (may happen due to other
// behaviors plugged-in before)
if (!parent) {
return;
}
var replacementType,
remove;
/**
* Check if incoming or outgoing connections
* can stay or could be substituted with an
* appropriate replacement.
*
* This holds true for SequenceFlow <> MessageFlow.
*/
if (is$1(connection, 'bpmn:SequenceFlow')) {
if (!bpmnRules.canConnectSequenceFlow(source, target)) {
remove = true;
}
if (bpmnRules.canConnectMessageFlow(source, target)) {
replacementType = 'bpmn:MessageFlow';
}
}
// transform message flows into sequence flows, if possible
if (is$1(connection, 'bpmn:MessageFlow')) {
if (!bpmnRules.canConnectMessageFlow(source, target)) {
remove = true;
}
if (bpmnRules.canConnectSequenceFlow(source, target)) {
replacementType = 'bpmn:SequenceFlow';
}
}
if (is$1(connection, 'bpmn:Association') && !bpmnRules.canConnectAssociation(source, target)) {
remove = true;
}
// remove invalid connection,
// unless it has been removed already
if (remove) {
modeling.removeConnection(connection);
}
// replace SequenceFlow <> MessageFlow
if (replacementType) {
modeling.connect(source, target, {
type: replacementType,
waypoints: connection.waypoints.slice()
});
}
}
function replaceReconnectedConnection(event) {
var context = event.context,
connection = context.connection,
source = context.newSource || connection.source,
target = context.newTarget || connection.target,
allowed,
replacement;
allowed = bpmnRules.canConnect(source, target);
if (!allowed || allowed.type === connection.type) {
return;
}
replacement = modeling.connect(source, target, {
type: allowed.type,
waypoints: connection.waypoints.slice()
});
// remove old connection
modeling.removeConnection(connection);
// replace connection in context to reconnect end/start
context.connection = replacement;
if (dragging) {
cleanDraggingSelection(connection, replacement);
}
}
// monkey-patch selection saved in dragging in order to re-select it when operation is finished
function cleanDraggingSelection(oldConnection, newConnection) {
var context = dragging.context(),
previousSelection = context && context.payload.previousSelection,
index;
// do nothing if not dragging or no selection was present
if (!previousSelection || !previousSelection.length) {
return;
}
index = previousSelection.indexOf(oldConnection);
if (index === -1) {
return;
}
previousSelection.splice(index, 1, newConnection);
}
// lifecycle hooks
this.postExecuted('elements.move', function(context) {
var closure = context.closure,
allConnections = closure.allConnections;
forEach$1(allConnections, fixConnection);
}, true);
this.preExecute('connection.reconnect', replaceReconnectedConnection);
this.postExecuted('element.updateProperties', function(event) {
var context = event.context,
properties = context.properties,
element = context.element,
businessObject = element.businessObject,
connection;
// remove condition on change to default
if (properties.default) {
connection = find(
element.outgoing,
matchPattern({ id: element.businessObject.default.id })
);
if (connection) {
modeling.updateProperties(connection, { conditionExpression: undefined });
}
}
// remove default from source on change to conditional
if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) {
modeling.updateProperties(element.source, { default: undefined });
}
});
}
e(ReplaceConnectionBehavior, CommandInterceptor);
ReplaceConnectionBehavior.$inject = [
'eventBus',
'modeling',
'bpmnRules',
'injector'
];
/**
* BPMN-specific replace behavior.
*/
function ReplaceElementBehaviour(
bpmnReplace,
bpmnRules,
elementRegistry,
injector,
modeling,
selection
) {
injector.invoke(CommandInterceptor, this);
this._bpmnReplace = bpmnReplace;
this._elementRegistry = elementRegistry;
this._selection = selection;
// replace elements on create, e.g. during copy-paste
this.postExecuted([ 'elements.create' ], 500, function(event) {
var context = event.context,
target = context.parent,
elements = context.elements;
var elementReplacements = reduce(elements, function(replacements, element) {
var canReplace = bpmnRules.canReplace([ element ], element.host || element.parent || target);
return canReplace ? replacements.concat(canReplace.replacements) : replacements;
}, []);
if (elementReplacements.length) {
this.replaceElements(elements, elementReplacements);
}
}, this);
// replace elements on move
this.postExecuted([ 'elements.move' ], 500, function(event) {
var context = event.context,
target = context.newParent,
newHost = context.newHost,
elements = [];
forEach$1(context.closure.topLevel, function(topLevelElements) {
if (isEventSubProcess(topLevelElements)) {
elements = elements.concat(topLevelElements.children);
} else {
elements = elements.concat(topLevelElements);
}
});
// set target to host if attaching
if (elements.length === 1 && newHost) {
target = newHost;
}
var canReplace = bpmnRules.canReplace(elements, target);
if (canReplace) {
this.replaceElements(elements, canReplace.replacements, newHost);
}
}, this);
// update attachments on host replace
this.postExecute([ 'shape.replace' ], 1500, function(e) {
var context = e.context,
oldShape = context.oldShape,
newShape = context.newShape,
attachers = oldShape.attachers,
canReplace;
if (attachers && attachers.length) {
canReplace = bpmnRules.canReplace(attachers, newShape);
this.replaceElements(attachers, canReplace.replacements);
}
}, this);
// keep ID on shape replace
this.postExecuted([ 'shape.replace' ], 1500, function(e) {
var context = e.context,
oldShape = context.oldShape,
newShape = context.newShape;
modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject);
modeling.updateProperties(newShape, { id: oldShape.id });
});
}
e(ReplaceElementBehaviour, CommandInterceptor);
ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements) {
var elementRegistry = this._elementRegistry,
bpmnReplace = this._bpmnReplace,
selection = this._selection;
forEach$1(newElements, function(replacement) {
var newElement = {
type: replacement.newElementType
};
var oldElement = elementRegistry.get(replacement.oldElementId);
var idx = elements.indexOf(oldElement);
elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false });
});
if (newElements) {
selection.select(elements);
}
};
ReplaceElementBehaviour.$inject = [
'bpmnReplace',
'bpmnRules',
'elementRegistry',
'injector',
'modeling',
'selection'
];
var HIGH_PRIORITY$9 = 1500;
var GROUP_MIN_DIMENSIONS = { width: 140, height: 120 };
var LANE_MIN_DIMENSIONS = { width: 300, height: 60 };
var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 };
var SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 };
var TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 };
/**
* Set minimum bounds/resize constraints on resize.
*
* @param {EventBus} eventBus
*/
function ResizeBehavior(eventBus) {
eventBus.on('resize.start', HIGH_PRIORITY$9, function(event) {
var context = event.context,
shape = context.shape,
direction = context.direction,
balanced = context.balanced;
if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {
context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced);
}
if (is$1(shape, 'bpmn:Participant')) {
context.minDimensions = PARTICIPANT_MIN_DIMENSIONS;
}
if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS;
}
if (is$1(shape, 'bpmn:TextAnnotation')) {
context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS;
}
});
}
ResizeBehavior.$inject = [ 'eventBus' ];
var abs$2 = Math.abs,
min = Math.min,
max$2 = Math.max;
function addToTrbl(trbl, attr, value, choice) {
var current = trbl[attr];
// make sure to set the value if it does not exist
// or apply the correct value by comparing against
// choice(value, currentValue)
trbl[attr] = current === undefined ? value : choice(value, current);
}
function addMin(trbl, attr, value) {
return addToTrbl(trbl, attr, value, min);
}
function addMax(trbl, attr, value) {
return addToTrbl(trbl, attr, value, max$2);
}
var LANE_RIGHT_PADDING = 20,
LANE_LEFT_PADDING = 50,
LANE_TOP_PADDING = 20,
LANE_BOTTOM_PADDING = 20;
function getParticipantResizeConstraints(laneShape, resizeDirection, balanced) {
var lanesRoot = getLanesRoot(laneShape);
var isFirst = true,
isLast = true;
// max top/bottom size for lanes
var allLanes = collectLanes(lanesRoot, [ lanesRoot ]);
var laneTrbl = asTRBL(laneShape);
var maxTrbl = {},
minTrbl = {};
if (/e/.test(resizeDirection)) {
minTrbl.right = laneTrbl.left + LANE_MIN_DIMENSIONS.width;
} else
if (/w/.test(resizeDirection)) {
minTrbl.left = laneTrbl.right - LANE_MIN_DIMENSIONS.width;
}
allLanes.forEach(function(other) {
var otherTrbl = asTRBL(other);
if (/n/.test(resizeDirection)) {
if (otherTrbl.top < (laneTrbl.top - 10)) {
isFirst = false;
}
// max top size (based on next element)
if (balanced && abs$2(laneTrbl.top - otherTrbl.bottom) < 10) {
addMax(maxTrbl, 'top', otherTrbl.top + LANE_MIN_DIMENSIONS.height);
}
// min top size (based on self or nested element)
if (abs$2(laneTrbl.top - otherTrbl.top) < 5) {
addMin(minTrbl, 'top', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height);
}
}
if (/s/.test(resizeDirection)) {
if (otherTrbl.bottom > (laneTrbl.bottom + 10)) {
isLast = false;
}
// max bottom size (based on previous element)
if (balanced && abs$2(laneTrbl.bottom - otherTrbl.top) < 10) {
addMin(maxTrbl, 'bottom', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height);
}
// min bottom size (based on self or nested element)
if (abs$2(laneTrbl.bottom - otherTrbl.bottom) < 5) {
addMax(minTrbl, 'bottom', otherTrbl.top + LANE_MIN_DIMENSIONS.height);
}
}
});
// max top/bottom/left/right size based on flow nodes
var flowElements = lanesRoot.children.filter(function(s) {
return !s.hidden && !s.waypoints && (is$1(s, 'bpmn:FlowElement') || is$1(s, 'bpmn:Artifact'));
});
flowElements.forEach(function(flowElement) {
var flowElementTrbl = asTRBL(flowElement);
if (isFirst && /n/.test(resizeDirection)) {
addMin(minTrbl, 'top', flowElementTrbl.top - LANE_TOP_PADDING);
}
if (/e/.test(resizeDirection)) {
addMax(minTrbl, 'right', flowElementTrbl.right + LANE_RIGHT_PADDING);
}
if (isLast && /s/.test(resizeDirection)) {
addMax(minTrbl, 'bottom', flowElementTrbl.bottom + LANE_BOTTOM_PADDING);
}
if (/w/.test(resizeDirection)) {
addMin(minTrbl, 'left', flowElementTrbl.left - LANE_LEFT_PADDING);
}
});
return {
min: minTrbl,
max: maxTrbl
};
}
var SLIGHTLY_HIGHER_PRIORITY = 1001;
/**
* Invoke {@link Modeling#resizeLane} instead of
* {@link Modeling#resizeShape} when resizing a Lane
* or Participant shape.
*/
function ResizeLaneBehavior(eventBus, modeling) {
eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) {
var context = event.context,
shape = context.shape;
if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {
// should we resize the opposite lane(s) in
// order to compensate for the resize operation?
context.balanced = !hasPrimaryModifier(event);
}
});
/**
* Intercept resize end and call resize lane function instead.
*/
eventBus.on('resize.end', SLIGHTLY_HIGHER_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
canExecute = context.canExecute,
newBounds = context.newBounds;
if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) {
if (canExecute) {
// ensure we have actual pixel values for new bounds
// (important when zoom level was > 1 during move)
newBounds = roundBounds(newBounds);
// perform the actual resize
modeling.resizeLane(shape, newBounds, context.balanced);
}
// stop propagation
return false;
}
});
}
ResizeLaneBehavior.$inject = [
'eventBus',
'modeling'
];
var LOW_PRIORITY$a = 500;
/**
* Add referenced root elements (error, escalation, message, signal) if they don't exist.
* Copy referenced root elements on copy & paste.
*/
function RootElementReferenceBehavior(
bpmnjs, eventBus, injector, moddleCopy, bpmnFactory
) {
injector.invoke(CommandInterceptor, this);
function canHaveRootElementReference(element) {
return isAny(element, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ]) ||
hasAnyEventDefinition(element, [
'bpmn:ErrorEventDefinition',
'bpmn:EscalationEventDefinition',
'bpmn:MessageEventDefinition',
'bpmn:SignalEventDefinition'
]);
}
function hasRootElement(rootElement) {
var definitions = bpmnjs.getDefinitions(),
rootElements = definitions.get('rootElements');
return !!find(rootElements, matchPattern({ id: rootElement.id }));
}
function getRootElementReferencePropertyName(eventDefinition) {
if (is$1(eventDefinition, 'bpmn:ErrorEventDefinition')) {
return 'errorRef';
} else if (is$1(eventDefinition, 'bpmn:EscalationEventDefinition')) {
return 'escalationRef';
} else if (is$1(eventDefinition, 'bpmn:MessageEventDefinition')) {
return 'messageRef';
} else if (is$1(eventDefinition, 'bpmn:SignalEventDefinition')) {
return 'signalRef';
}
}
function getRootElement(businessObject) {
if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
return businessObject.get('messageRef');
}
var eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ];
return eventDefinition.get(getRootElementReferencePropertyName(eventDefinition));
}
function setRootElement(businessObject, rootElement) {
if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) {
return businessObject.set('messageRef', rootElement);
}
var eventDefinitions = businessObject.get('eventDefinitions'),
eventDefinition = eventDefinitions[ 0 ];
return eventDefinition.set(getRootElementReferencePropertyName(eventDefinition), rootElement);
}
// create shape
this.executed('shape.create', function(context) {
var shape = context.shape;
if (!canHaveRootElementReference(shape)) {
return;
}
var businessObject = getBusinessObject(shape),
rootElement = getRootElement(businessObject),
rootElements;
if (rootElement && !hasRootElement(rootElement)) {
rootElements = bpmnjs.getDefinitions().get('rootElements');
// add root element
add(rootElements, rootElement);
context.addedRootElement = rootElement;
}
}, true);
this.reverted('shape.create', function(context) {
var addedRootElement = context.addedRootElement;
if (!addedRootElement) {
return;
}
var rootElements = bpmnjs.getDefinitions().get('rootElements');
// remove root element
remove(rootElements, addedRootElement);
}, true);
eventBus.on('copyPaste.copyElement', function(context) {
var descriptor = context.descriptor,
element = context.element;
if (element.labelTarget || !canHaveRootElementReference(element)) {
return;
}
var businessObject = getBusinessObject(element),
rootElement = getRootElement(businessObject);
if (rootElement) {
// TODO(nikku): clone on copy
descriptor.referencedRootElement = rootElement;
}
});
eventBus.on('copyPaste.pasteElement', LOW_PRIORITY$a, function(context) {
var descriptor = context.descriptor,
businessObject = descriptor.businessObject,
referencedRootElement = descriptor.referencedRootElement;
if (!referencedRootElement) {
return;
}
if (!hasRootElement(referencedRootElement)) {
referencedRootElement = moddleCopy.copyElement(
referencedRootElement,
bpmnFactory.create(referencedRootElement.$type)
);
}
setRootElement(businessObject, referencedRootElement);
delete descriptor.referencedRootElement;
});
}
RootElementReferenceBehavior.$inject = [
'bpmnjs',
'eventBus',
'injector',
'moddleCopy',
'bpmnFactory'
];
e(RootElementReferenceBehavior, CommandInterceptor);
// helpers //////////
function hasAnyEventDefinition(element, types) {
if (!isArray$3(types)) {
types = [ types ];
}
return some(types, function(type) {
return hasEventDefinition$2(element, type);
});
}
var max$1 = Math.max;
function SpaceToolBehavior(eventBus) {
eventBus.on('spaceTool.getMinDimensions', function(context) {
var shapes = context.shapes,
axis = context.axis,
start = context.start,
minDimensions = {};
forEach$1(shapes, function(shape) {
var id = shape.id;
if (is$1(shape, 'bpmn:Participant')) {
if (isHorizontal$1(axis)) {
minDimensions[ id ] = PARTICIPANT_MIN_DIMENSIONS;
} else {
minDimensions[ id ] = {
width: PARTICIPANT_MIN_DIMENSIONS.width,
height: getParticipantMinHeight(shape, start)
};
}
}
if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
minDimensions[ id ] = SUB_PROCESS_MIN_DIMENSIONS;
}
if (is$1(shape, 'bpmn:TextAnnotation')) {
minDimensions[ id ] = TEXT_ANNOTATION_MIN_DIMENSIONS;
}
if (is$1(shape, 'bpmn:Group')) {
minDimensions[ id ] = GROUP_MIN_DIMENSIONS;
}
});
return minDimensions;
});
}
SpaceToolBehavior.$inject = [ 'eventBus' ];
// helpers //////////
function isHorizontal$1(axis) {
return axis === 'x';
}
/**
* Get minimum height for participant taking lanes into account.
*
* @param {<djs.model.Shape>} participant
* @param {number} start
*
* @returns {Object}
*/
function getParticipantMinHeight(participant, start) {
var lanesMinHeight;
if (!hasChildLanes(participant)) {
return PARTICIPANT_MIN_DIMENSIONS.height;
}
lanesMinHeight = getLanesMinHeight(participant, start);
return max$1(PARTICIPANT_MIN_DIMENSIONS.height, lanesMinHeight);
}
function hasChildLanes(element) {
return !!getChildLanes(element).length;
}
function getLanesMinHeight(participant, resizeStart) {
var lanes = getChildLanes(participant),
resizedLane;
// find the nested lane which is currently resized
resizedLane = findResizedLane(lanes, resizeStart);
// resized lane cannot shrink below the minimum height
// but remaining lanes' dimensions are kept intact
return participant.height - resizedLane.height + LANE_MIN_DIMENSIONS.height;
}
/**
* Find nested lane which is currently resized.
*
* @param {Array<djs.model.Shape>} lanes
* @param {number} resizeStart
*/
function findResizedLane(lanes, resizeStart) {
var i, lane, childLanes;
for (i = 0; i < lanes.length; i++) {
lane = lanes[i];
// resizing current lane or a lane nested
if (resizeStart >= lane.y && resizeStart <= lane.y + lane.height) {
childLanes = getChildLanes(lane);
// a nested lane is resized
if (childLanes.length) {
return findResizedLane(childLanes, resizeStart);
}
// current lane is the resized one
return lane;
}
}
}
var LOW_PRIORITY$9 = 400;
var HIGH_PRIORITY$8 = 600;
var DEFAULT_POSITION = {
x: 180,
y: 160
};
/**
* Creates bpmndi:BPMNPlane elements and canvas planes when collapsed subprocesses are created.
*
*
* @param {Canvas} canvas
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {ElementFactory} elementFactory
* @param {BpmnFactory} bpmnFactory
* @param {Bpmnjs} bpmnjs
* @param {ElementRegistry} elementRegistry
*/
function SubProcessPlaneBehavior(
canvas, eventBus, modeling,
elementFactory, bpmnFactory, bpmnjs, elementRegistry) {
CommandInterceptor.call(this, eventBus);
this._canvas = canvas;
this._eventBus = eventBus;
this._modeling = modeling;
this._elementFactory = elementFactory;
this._bpmnFactory = bpmnFactory;
this._bpmnjs = bpmnjs;
this._elementRegistry = elementRegistry;
var self = this;
function isCollapsedSubProcess(element) {
return is$1(element, 'bpmn:SubProcess') && !isExpanded(element);
}
function createRoot(context) {
var shape = context.shape,
rootElement = context.newRootElement;
var businessObject = getBusinessObject(shape);
rootElement = self._addDiagram(rootElement || businessObject);
context.newRootElement = canvas.addRootElement(rootElement);
}
function removeRoot(context) {
var shape = context.shape;
var businessObject = getBusinessObject(shape);
self._removeDiagram(businessObject);
var rootElement = context.newRootElement = elementRegistry.get(getPlaneIdFromShape(businessObject));
canvas.removeRootElement(rootElement);
}
// add plane elements for newly created sub-processes
// this ensures we can actually drill down into the element
this.executed('shape.create', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
createRoot(context);
}, true);
this.postExecuted('shape.create', function(context) {
var shape = context.shape,
rootElement = context.newRootElement;
if (!rootElement || !shape.children) {
return;
}
self._showRecursively(shape.children);
self._moveChildrenToShape(shape, rootElement);
}, true);
this.reverted('shape.create', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
removeRoot(context);
}, true);
this.preExecuted('shape.delete', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
var attachedRoot = elementRegistry.get(getPlaneIdFromShape(shape));
if (!attachedRoot) {
return;
}
modeling.removeElements(attachedRoot.children.slice());
}, true);
this.executed('shape.delete', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
removeRoot(context);
}, true);
this.reverted('shape.delete', function(context) {
var shape = context.shape;
if (!isCollapsedSubProcess(shape)) {
return;
}
createRoot(context);
}, true);
this.preExecuted('shape.replace', function(context) {
var oldShape = context.oldShape;
var newShape = context.newShape;
if (!isCollapsedSubProcess(oldShape) || !isCollapsedSubProcess(newShape)) {
return;
}
// old plane could have content,
// we remove it so it is not recursively deleted from 'shape.delete'
context.oldRoot = canvas.removeRootElement(getPlaneIdFromShape(oldShape));
}, true);
this.postExecuted('shape.replace', function(context) {
var newShape = context.newShape,
source = context.oldRoot,
target = canvas.findRoot(getPlaneIdFromShape(newShape));
if (!source || !target) {
return;
}
var elements = source.children;
modeling.moveElements(elements, { x: 0, y: 0 }, target);
}, true);
// rename primary elements when the secondary element changes
// this ensures rootElement.id = element.id + '_plane'
this.executed('element.updateProperties', function(context) {
var shape = context.element;
if (!is$1(shape, 'bpmn:SubProcess')) {
return;
}
var properties = context.properties;
var oldProperties = context.oldProperties;
var oldId = oldProperties.id,
newId = properties.id;
if (oldId === newId) {
return;
}
if (isPlane(shape)) {
elementRegistry.updateId(shape, toPlaneId(newId));
elementRegistry.updateId(oldId, newId);
return;
}
var planeElement = elementRegistry.get(toPlaneId(oldId));
if (!planeElement) {
return;
}
elementRegistry.updateId(toPlaneId(oldId), toPlaneId(newId));
}, true);
this.reverted('element.updateProperties', function(context) {
var shape = context.element;
if (!is$1(shape, 'bpmn:SubProcess')) {
return;
}
var properties = context.properties;
var oldProperties = context.oldProperties;
var oldId = oldProperties.id,
newId = properties.id;
if (oldId === newId) {
return;
}
if (isPlane(shape)) {
elementRegistry.updateId(shape, toPlaneId(oldId));
elementRegistry.updateId(newId, oldId);
return;
}
var planeElement = elementRegistry.get(toPlaneId(newId));
if (!planeElement) {
return;
}
elementRegistry.updateId(planeElement, toPlaneId(oldId));
}, true);
// re-throw element.changed to re-render primary shape if associated plane has
// changed (e.g. bpmn:name property has changed)
eventBus.on('element.changed', function(context) {
var element = context.element;
if (!isPlane(element)) {
return;
}
var plane = element;
var primaryShape = elementRegistry.get(getShapeIdFromPlane(plane));
// do not re-throw if no associated primary shape (e.g. bpmn:Process)
if (!primaryShape || primaryShape === plane) {
return;
}
eventBus.fire('element.changed', { element: primaryShape });
});
// create/remove plane for the subprocess
this.executed('shape.toggleCollapse', LOW_PRIORITY$9, function(context) {
var shape = context.shape;
if (!is$1(shape, 'bpmn:SubProcess')) {
return;
}
if (!isExpanded(shape)) {
createRoot(context);
self._showRecursively(shape.children);
} else {
removeRoot(context);
}
}, true);
// create/remove plane for the subprocess
this.reverted('shape.toggleCollapse', LOW_PRIORITY$9, function(context) {
var shape = context.shape;
if (!is$1(shape, 'bpmn:SubProcess')) {
return;
}
if (!isExpanded(shape)) {
createRoot(context);
self._showRecursively(shape.children);
} else {
removeRoot(context);
}
}, true);
// move elements between planes
this.postExecuted('shape.toggleCollapse', HIGH_PRIORITY$8, function(context) {
var shape = context.shape;
if (!is$1(shape, 'bpmn:SubProcess')) {
return;
}
var rootElement = context.newRootElement;
if (!rootElement) {
return;
}
if (!isExpanded(shape)) {
// collapsed
self._moveChildrenToShape(shape, rootElement);
} else {
self._moveChildrenToShape(rootElement, shape);
}
}, true);
// copy-paste ///////////
// add elements in plane to tree
eventBus.on('copyPaste.createTree', function(context) {
var element = context.element,
children = context.children;
if (!isCollapsedSubProcess(element)) {
return;
}
var id = getPlaneIdFromShape(element);
var parent = elementRegistry.get(id);
if (parent) {
// do not copy invisible root element
children.push.apply(children, parent.children);
}
});
// set plane children as direct children of collapsed shape
eventBus.on('copyPaste.copyElement', function(context) {
var descriptor = context.descriptor,
element = context.element,
elements = context.elements;
var parent = element.parent;
var isPlane = is$1(getDi(parent), 'bpmndi:BPMNPlane');
if (!isPlane) {
return;
}
var parentId = getShapeIdFromPlane(parent);
var referencedShape = find(elements, function(element) {
return element.id === parentId;
});
if (!referencedShape) {
return;
}
descriptor.parent = referencedShape.id;
});
// hide children during pasting
eventBus.on('copyPaste.pasteElement', function(context) {
var descriptor = context.descriptor;
if (!descriptor.parent) {
return;
}
if (isCollapsedSubProcess(descriptor.parent) || descriptor.parent.hidden) {
descriptor.hidden = true;
}
});
}
e(SubProcessPlaneBehavior, CommandInterceptor);
/**
* Moves the child elements from source to target.
*
* If the target is a plane, the children are moved to the top left corner.
* Otherwise, the center of the target is used.
*
* @param {Object|djs.model.Base} source
* @param {Object|djs.model.Base} target
*/
SubProcessPlaneBehavior.prototype._moveChildrenToShape = function(source, target) {
var modeling = this._modeling;
var children = source.children;
var offset;
if (!children) {
return;
}
// add external labels that weren't children of sub process
children = children.concat(children.reduce(function(labels, child) {
if (child.label && child.label.parent !== source) {
return labels.concat(child.label);
}
return labels;
}, []));
// only change plane if there are no visible children, but don't move them
var visibleChildren = children.filter(function(child) {
return !child.hidden;
});
if (!visibleChildren.length) {
modeling.moveElements(children, { x: 0, y: 0 }, target, { autoResize: false });
return;
}
var childrenBounds = getBBox(visibleChildren);
// target is a plane
if (!target.x) {
offset = {
x: DEFAULT_POSITION.x - childrenBounds.x,
y: DEFAULT_POSITION.y - childrenBounds.y
};
}
// source is a plane
else {
// move relative to the center of the shape
var targetMid = getMid(target);
var childrenMid = getMid(childrenBounds);
offset = {
x: targetMid.x - childrenMid.x,
y: targetMid.y - childrenMid.y
};
}
modeling.moveElements(children, offset, target, { autoResize: false });
};
/**
* Sets `hidden` property on all children of the given shape.
*
* @param {Array} elements
* @param {Boolean} [hidden]
* @returns {Array} all child elements
*/
SubProcessPlaneBehavior.prototype._showRecursively = function(elements, hidden) {
var self = this;
var result = [];
elements.forEach(function(element) {
element.hidden = !!hidden;
result = result.concat(element);
if (element.children) {
result = result.concat(
self._showRecursively(element.children, element.collapsed || hidden)
);
}
});
return result;
};
/**
* Adds a given rootElement to the bpmnDi diagrams.
*
* @param {Object} rootElement
* @returns {Object} planeElement
*/
SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) {
var bpmnjs = this._bpmnjs;
var diagrams = bpmnjs.getDefinitions().diagrams;
if (!planeElement.businessObject) {
planeElement = this._createNewDiagram(planeElement);
}
diagrams.push(planeElement.di.$parent);
return planeElement;
};
/**
* Creates a new plane element for the given sub process.
*
* @param {Object} bpmnElement
*
* @return {Object} new diagram element
*/
SubProcessPlaneBehavior.prototype._createNewDiagram = function(bpmnElement) {
var bpmnFactory = this._bpmnFactory;
var elementFactory = this._elementFactory;
var diPlane = bpmnFactory.create('bpmndi:BPMNPlane', {
bpmnElement: bpmnElement
});
var diDiagram = bpmnFactory.create('bpmndi:BPMNDiagram', {
plane: diPlane
});
diPlane.$parent = diDiagram;
// add a virtual element (not being drawn),
// a copy cat of our BpmnImporter code
var planeElement = elementFactory.createRoot({
id: getPlaneIdFromShape(bpmnElement),
type: bpmnElement.$type,
di: diPlane,
businessObject: bpmnElement,
collapsed: true
});
return planeElement;
};
/**
* Removes the diagram for a given root element
*
* @param {Object} rootElement
* @returns {Object} removed bpmndi:BPMNDiagram
*/
SubProcessPlaneBehavior.prototype._removeDiagram = function(rootElement) {
var bpmnjs = this._bpmnjs;
var diagrams = bpmnjs.getDefinitions().diagrams;
var removedDiagram = find(diagrams, function(diagram) {
return diagram.plane.bpmnElement.id === rootElement.id;
});
diagrams.splice(diagrams.indexOf(removedDiagram), 1);
return removedDiagram;
};
SubProcessPlaneBehavior.$inject = [
'canvas',
'eventBus',
'modeling',
'elementFactory',
'bpmnFactory',
'bpmnjs',
'elementRegistry'
];
/**
* Add start event replacing element with expanded sub process.
*
* @param {Injector} injector
* @param {Modeling} modeling
*/
function SubProcessStartEventBehavior(injector, modeling) {
injector.invoke(CommandInterceptor, this);
this.postExecuted('shape.replace', function(event) {
var oldShape = event.context.oldShape,
newShape = event.context.newShape;
if (
!is$1(newShape, 'bpmn:SubProcess') ||
! (is$1(oldShape, 'bpmn:Task') || is$1(oldShape, 'bpmn:CallActivity')) ||
!isExpanded(newShape)
) {
return;
}
var position = getStartEventPosition(newShape);
modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape);
});
}
SubProcessStartEventBehavior.$inject = [
'injector',
'modeling'
];
e(SubProcessStartEventBehavior, CommandInterceptor);
// helpers //////////
function getStartEventPosition(shape) {
return {
x: shape.x + shape.width / 6,
y: shape.y + shape.height / 2
};
}
function ToggleCollapseConnectionBehaviour(
eventBus, modeling
) {
CommandInterceptor.call(this, eventBus);
this.postExecuted('shape.toggleCollapse', 1500, function(context) {
// var shape = context.shape;
var shape = context.shape;
// only change connections when collapsing
if (isExpanded(shape)) {
return;
}
var allChildren = selfAndAllChildren(shape);
allChildren.forEach(function(child) {
// Ensure that the connection array is not modified during iteration
var incomingConnections = child.incoming.slice(),
outgoingConnections = child.outgoing.slice();
forEach$1(incomingConnections, function(c) {
handleConnection(c, true);
});
forEach$1(outgoingConnections, function(c) {
handleConnection(c, false);
});
});
function handleConnection(c, incoming) {
if (allChildren.indexOf(c.source) !== -1 && allChildren.indexOf(c.target) !== -1) {
return;
}
if (incoming) {
modeling.reconnectEnd(c, shape, getMid(shape));
} else {
modeling.reconnectStart(c, shape, getMid(shape));
}
}
}, true);
}
e(ToggleCollapseConnectionBehaviour, CommandInterceptor);
ToggleCollapseConnectionBehaviour.$inject = [
'eventBus',
'modeling',
];
var LOW_PRIORITY$8 = 500;
function ToggleElementCollapseBehaviour(
eventBus, elementFactory, modeling,
resize) {
CommandInterceptor.call(this, eventBus);
function hideEmptyLabels(children) {
if (children.length) {
children.forEach(function(child) {
if (child.type === 'label' && !child.businessObject.name) {
child.hidden = true;
}
});
}
}
function expandedBounds(shape, defaultSize) {
var children = shape.children,
newBounds = defaultSize,
visibleElements,
visibleBBox;
visibleElements = filterVisible(children).concat([ shape ]);
visibleBBox = computeChildrenBBox(visibleElements);
if (visibleBBox) {
// center to visibleBBox with max(defaultSize, childrenBounds)
newBounds.width = Math.max(visibleBBox.width, newBounds.width);
newBounds.height = Math.max(visibleBBox.height, newBounds.height);
newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2;
newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2;
} else {
// center to collapsed shape with defaultSize
newBounds.x = shape.x + (shape.width - newBounds.width) / 2;
newBounds.y = shape.y + (shape.height - newBounds.height) / 2;
}
return newBounds;
}
function collapsedBounds(shape, defaultSize) {
return {
x: shape.x + (shape.width - defaultSize.width) / 2,
y: shape.y + (shape.height - defaultSize.height) / 2,
width: defaultSize.width,
height: defaultSize.height
};
}
this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) {
var context = e.context,
shape = context.shape;
if (!is$1(shape, 'bpmn:SubProcess')) {
return;
}
if (!shape.collapsed) {
// all children got made visible through djs, hide empty labels
hideEmptyLabels(shape.children);
// remove collapsed marker
getDi(shape).isExpanded = true;
} else {
// place collapsed marker
getDi(shape).isExpanded = false;
}
});
this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) {
var context = e.context;
var shape = context.shape;
// revert removing/placing collapsed marker
if (!shape.collapsed) {
getDi(shape).isExpanded = true;
} else {
getDi(shape).isExpanded = false;
}
});
this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) {
var shape = e.context.shape,
defaultSize = elementFactory.getDefaultSize(shape),
newBounds;
if (shape.collapsed) {
// resize to default size of collapsed shapes
newBounds = collapsedBounds(shape, defaultSize);
} else {
// resize to bounds of max(visible children, defaultSize)
newBounds = expandedBounds(shape, defaultSize);
}
modeling.resizeShape(shape, newBounds, null, {
autoResize: shape.collapsed ? false : 'nwse'
});
});
}
e(ToggleElementCollapseBehaviour, CommandInterceptor);
ToggleElementCollapseBehaviour.$inject = [
'eventBus',
'elementFactory',
'modeling'
];
// helpers //////////////////////
function filterVisible(elements) {
return elements.filter(function(e) {
return !e.hidden;
});
}
/**
* Unclaims model IDs on element deletion.
*
* @param {Canvas} canvas
* @param {Injector} injector
* @param {Moddle} moddle
* @param {Modeling} modeling
*/
function UnclaimIdBehavior(canvas, injector, moddle, modeling) {
injector.invoke(CommandInterceptor, this);
this.preExecute('shape.delete', function(event) {
var context = event.context,
shape = context.shape,
shapeBo = shape.businessObject;
if (isLabel$6(shape)) {
return;
}
if (is$1(shape, 'bpmn:Participant') && isExpanded(shape)) {
moddle.ids.unclaim(shapeBo.processRef.id);
}
modeling.unclaimId(shapeBo.id, shapeBo);
});
this.preExecute('connection.delete', function(event) {
var context = event.context,
connection = context.connection,
connectionBo = connection.businessObject;
modeling.unclaimId(connectionBo.id, connectionBo);
});
this.preExecute('canvas.updateRoot', function() {
var rootElement = canvas.getRootElement(),
rootElementBo = rootElement.businessObject;
if (is$1(rootElement, 'bpmn:Collaboration')) {
moddle.ids.unclaim(rootElementBo.id);
}
});
}
e(UnclaimIdBehavior, CommandInterceptor);
UnclaimIdBehavior.$inject = [ 'canvas', 'injector', 'moddle', 'modeling' ];
/**
* A behavior that unsets the Default property of
* sequence flow source on element delete, if the
* removed element is the Gateway or Task's default flow.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
function DeleteSequenceFlowBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.preExecute('connection.delete', function(event) {
var context = event.context,
connection = context.connection,
source = connection.source;
if (isDefaultFlow(connection, source)) {
modeling.updateProperties(source, {
'default': null
});
}
});
}
e(DeleteSequenceFlowBehavior, CommandInterceptor);
DeleteSequenceFlowBehavior.$inject = [
'eventBus',
'modeling'
];
// helpers //////////////////////
function isDefaultFlow(connection, source) {
if (!is$1(connection, 'bpmn:SequenceFlow')) {
return false;
}
var sourceBo = getBusinessObject(source),
sequenceFlow = getBusinessObject(connection);
return sourceBo.get('default') === sequenceFlow;
}
var LOW_PRIORITY$7 = 500,
HIGH_PRIORITY$7 = 5000;
/**
* BPMN specific delete lane behavior
*/
function UpdateFlowNodeRefsBehavior(eventBus, modeling, translate) {
CommandInterceptor.call(this, eventBus);
/**
* Ok, this is it:
*
* We have to update the Lane#flowNodeRefs _and_
* FlowNode#lanes with every FlowNode move/resize and
* Lane move/resize.
*
* We want to group that stuff to recompute containments
* as efficient as possible.
*
* Yea!
*/
// the update context
var context;
function initContext() {
context = context || new UpdateContext();
context.enter();
return context;
}
function getContext() {
if (!context) {
throw new Error(translate('out of bounds release'));
}
return context;
}
function releaseContext() {
if (!context) {
throw new Error(translate('out of bounds release'));
}
var triggerUpdate = context.leave();
if (triggerUpdate) {
modeling.updateLaneRefs(context.flowNodes, context.lanes);
context = null;
}
return triggerUpdate;
}
var laneRefUpdateEvents = [
'spaceTool',
'lane.add',
'lane.resize',
'lane.split',
'elements.create',
'elements.delete',
'elements.move',
'shape.create',
'shape.delete',
'shape.move',
'shape.resize'
];
// listen to a lot of stuff to group lane updates
this.preExecute(laneRefUpdateEvents, HIGH_PRIORITY$7, function(event) {
initContext();
});
this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY$7, function(event) {
releaseContext();
});
// Mark flow nodes + lanes that need an update
this.preExecute([
'shape.create',
'shape.move',
'shape.delete',
'shape.resize'
], function(event) {
var context = event.context,
shape = context.shape;
var updateContext = getContext();
// no need to update labels
if (shape.labelTarget) {
return;
}
if (is$1(shape, 'bpmn:Lane')) {
updateContext.addLane(shape);
}
if (is$1(shape, 'bpmn:FlowNode')) {
updateContext.addFlowNode(shape);
}
});
}
UpdateFlowNodeRefsBehavior.$inject = [
'eventBus',
'modeling' ,
'translate'
];
e(UpdateFlowNodeRefsBehavior, CommandInterceptor);
function UpdateContext() {
this.flowNodes = [];
this.lanes = [];
this.counter = 0;
this.addLane = function(lane) {
this.lanes.push(lane);
};
this.addFlowNode = function(flowNode) {
this.flowNodes.push(flowNode);
};
this.enter = function() {
this.counter++;
};
this.leave = function() {
this.counter--;
return !this.counter;
};
}
var BehaviorModule = {
__init__: [
'adaptiveLabelPositioningBehavior',
'appendBehavior',
'associationBehavior',
'attachEventBehavior',
'boundaryEventBehavior',
'createBehavior',
'createDataObjectBehavior',
'createParticipantBehavior',
'dataInputAssociationBehavior',
'dataStoreBehavior',
'deleteLaneBehavior',
'detachEventBehavior',
'dropOnFlowBehavior',
'eventBasedGatewayBehavior',
'fixHoverBehavior',
'groupBehavior',
'importDockingFix',
'isHorizontalFix',
'labelBehavior',
'layoutConnectionBehavior',
'messageFlowBehavior',
'modelingFeedback',
'removeElementBehavior',
'removeEmbeddedLabelBoundsBehavior',
'removeParticipantBehavior',
'replaceConnectionBehavior',
'replaceElementBehaviour',
'resizeBehavior',
'resizeLaneBehavior',
'rootElementReferenceBehavior',
'spaceToolBehavior',
'subProcessPlaneBehavior',
'subProcessStartEventBehavior',
'toggleCollapseConnectionBehaviour',
'toggleElementCollapseBehaviour',
'unclaimIdBehavior',
'updateFlowNodeRefsBehavior',
'unsetDefaultFlowBehavior'
],
adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
appendBehavior: [ 'type', AppendBehavior ],
associationBehavior: [ 'type', AssociationBehavior ],
attachEventBehavior: [ 'type', AttachEventBehavior ],
boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
createBehavior: [ 'type', CreateBehavior ],
createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
createParticipantBehavior: [ 'type', CreateParticipantBehavior ],
dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ],
dataStoreBehavior: [ 'type', DataStoreBehavior ],
deleteLaneBehavior: [ 'type', DeleteLaneBehavior ],
detachEventBehavior: [ 'type', DetachEventBehavior ],
dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
fixHoverBehavior: [ 'type', FixHoverBehavior ],
groupBehavior: [ 'type', GroupBehavior ],
importDockingFix: [ 'type', ImportDockingFix ],
isHorizontalFix: [ 'type', IsHorizontalFix ],
labelBehavior: [ 'type', LabelBehavior ],
layoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ],
messageFlowBehavior: [ 'type', MessageFlowBehavior ],
modelingFeedback: [ 'type', ModelingFeedback ],
removeElementBehavior: [ 'type', RemoveElementBehavior ],
removeEmbeddedLabelBoundsBehavior: [ 'type', RemoveEmbeddedLabelBoundsBehavior ],
removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
resizeBehavior: [ 'type', ResizeBehavior ],
resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
spaceToolBehavior: [ 'type', SpaceToolBehavior ],
subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
toggleCollapseConnectionBehaviour: [ 'type', ToggleCollapseConnectionBehaviour ],
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
unsetDefaultFlowBehavior: [ 'type', DeleteSequenceFlowBehavior ],
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ]
};
function getBoundaryAttachment(position, targetBounds) {
var orientation = getOrientation(position, targetBounds, -15);
if (orientation !== 'intersect') {
return orientation;
} else {
return null;
}
}
/**
* BPMN specific modeling rule
*/
function BpmnRules(eventBus) {
RuleProvider.call(this, eventBus);
}
e(BpmnRules, RuleProvider);
BpmnRules.$inject = [ 'eventBus' ];
BpmnRules.prototype.init = function() {
this.addRule('connection.start', function(context) {
var source = context.source;
return canStartConnection(source);
});
this.addRule('connection.create', function(context) {
var source = context.source,
target = context.target,
hints = context.hints || {},
targetParent = hints.targetParent,
targetAttach = hints.targetAttach;
// don't allow incoming connections on
// newly created boundary events
// to boundary events
if (targetAttach) {
return false;
}
// temporarily set target parent for scoping
// checks to work
if (targetParent) {
target.parent = targetParent;
}
try {
return canConnect(source, target);
} finally {
// unset temporary target parent
if (targetParent) {
target.parent = null;
}
}
});
this.addRule('connection.reconnect', function(context) {
var connection = context.connection,
source = context.source,
target = context.target;
return canConnect(source, target, connection);
});
this.addRule('connection.updateWaypoints', function(context) {
return {
type: context.connection.type
};
});
this.addRule('shape.resize', function(context) {
var shape = context.shape,
newBounds = context.newBounds;
return canResize(shape, newBounds);
});
this.addRule('elements.create', function(context) {
var elements = context.elements,
position = context.position,
target = context.target;
if (isConnection$8(target) && !canInsert(elements, target)) {
return false;
}
return every(elements, function(element) {
if (isConnection$8(element)) {
return canConnect(element.source, element.target, element);
}
if (element.host) {
return canAttach(element, element.host, null, position);
}
return canCreate(element, target, null);
});
});
this.addRule('elements.move', function(context) {
var target = context.target,
shapes = context.shapes,
position = context.position;
return canAttach(shapes, target, null, position) ||
canReplace(shapes, target, position) ||
canMove(shapes, target) ||
canInsert(shapes, target);
});
this.addRule('shape.create', function(context) {
return canCreate(
context.shape,
context.target,
context.source,
context.position
);
});
this.addRule('shape.attach', function(context) {
return canAttach(
context.shape,
context.target,
null,
context.position
);
});
this.addRule('element.copy', function(context) {
var element = context.element,
elements = context.elements;
return canCopy(elements, element);
});
};
BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;
BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;
BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
BpmnRules.prototype.canMove = canMove;
BpmnRules.prototype.canAttach = canAttach;
BpmnRules.prototype.canReplace = canReplace;
BpmnRules.prototype.canDrop = canDrop;
BpmnRules.prototype.canInsert = canInsert;
BpmnRules.prototype.canCreate = canCreate;
BpmnRules.prototype.canConnect = canConnect;
BpmnRules.prototype.canResize = canResize;
BpmnRules.prototype.canCopy = canCopy;
/**
* Utility functions for rule checking
*/
/**
* Checks if given element can be used for starting connection.
*
* @param {Element} source
* @return {boolean}
*/
function canStartConnection(element) {
if (nonExistingOrLabel(element)) {
return null;
}
return isAny(element, [
'bpmn:FlowNode',
'bpmn:InteractionNode',
'bpmn:DataObjectReference',
'bpmn:DataStoreReference',
'bpmn:Group',
'bpmn:TextAnnotation'
]);
}
function nonExistingOrLabel(element) {
return !element || isLabel$6(element);
}
function isSame$1(a, b) {
return a === b;
}
function getOrganizationalParent(element) {
do {
if (is$1(element, 'bpmn:Process')) {
return getBusinessObject(element);
}
if (is$1(element, 'bpmn:Participant')) {
return (
getBusinessObject(element).processRef ||
getBusinessObject(element)
);
}
} while ((element = element.parent));
}
function isTextAnnotation(element) {
return is$1(element, 'bpmn:TextAnnotation');
}
function isGroup(element) {
return is$1(element, 'bpmn:Group') && !element.labelTarget;
}
function isCompensationBoundary(element) {
return is$1(element, 'bpmn:BoundaryEvent') &&
hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
}
function isForCompensation(e) {
return getBusinessObject(e).isForCompensation;
}
function isSameOrganization(a, b) {
var parentA = getOrganizationalParent(a),
parentB = getOrganizationalParent(b);
return parentA === parentB;
}
function isMessageFlowSource(element) {
return (
is$1(element, 'bpmn:InteractionNode') &&
!is$1(element, 'bpmn:BoundaryEvent') && (
!is$1(element, 'bpmn:Event') || (
is$1(element, 'bpmn:ThrowEvent') &&
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
)
)
);
}
function isMessageFlowTarget(element) {
return (
is$1(element, 'bpmn:InteractionNode') &&
!isForCompensation(element) && (
!is$1(element, 'bpmn:Event') || (
is$1(element, 'bpmn:CatchEvent') &&
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
)
) && !(
is$1(element, 'bpmn:BoundaryEvent') &&
!hasEventDefinition(element, 'bpmn:MessageEventDefinition')
)
);
}
function getScopeParent(element) {
var parent = element;
while ((parent = parent.parent)) {
if (is$1(parent, 'bpmn:FlowElementsContainer')) {
return getBusinessObject(parent);
}
if (is$1(parent, 'bpmn:Participant')) {
return getBusinessObject(parent).processRef;
}
}
return null;
}
function isSameScope(a, b) {
var scopeParentA = getScopeParent(a),
scopeParentB = getScopeParent(b);
return scopeParentA === scopeParentB;
}
function hasEventDefinition(element, eventDefinition) {
var bo = getBusinessObject(element);
return !!find(bo.eventDefinitions || [], function(definition) {
return is$1(definition, eventDefinition);
});
}
function hasEventDefinitionOrNone(element, eventDefinition) {
var bo = getBusinessObject(element);
return (bo.eventDefinitions || []).every(function(definition) {
return is$1(definition, eventDefinition);
});
}
function isSequenceFlowSource(element) {
return (
is$1(element, 'bpmn:FlowNode') &&
!is$1(element, 'bpmn:EndEvent') &&
!isEventSubProcess(element) &&
!(is$1(element, 'bpmn:IntermediateThrowEvent') &&
hasEventDefinition(element, 'bpmn:LinkEventDefinition')
) &&
!isCompensationBoundary(element) &&
!isForCompensation(element)
);
}
function isSequenceFlowTarget(element) {
return (
is$1(element, 'bpmn:FlowNode') &&
!is$1(element, 'bpmn:StartEvent') &&
!is$1(element, 'bpmn:BoundaryEvent') &&
!isEventSubProcess(element) &&
!(is$1(element, 'bpmn:IntermediateCatchEvent') &&
hasEventDefinition(element, 'bpmn:LinkEventDefinition')
) &&
!isForCompensation(element)
);
}
function isEventBasedTarget(element) {
return (
is$1(element, 'bpmn:ReceiveTask') || (
is$1(element, 'bpmn:IntermediateCatchEvent') && (
hasEventDefinition(element, 'bpmn:MessageEventDefinition') ||
hasEventDefinition(element, 'bpmn:TimerEventDefinition') ||
hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') ||
hasEventDefinition(element, 'bpmn:SignalEventDefinition')
)
)
);
}
function isConnection$8(element) {
return element.waypoints;
}
function getParents(element) {
var parents = [];
while (element) {
element = element.parent;
if (element) {
parents.push(element);
}
}
return parents;
}
function isParent(possibleParent, element) {
var allParents = getParents(element);
return allParents.indexOf(possibleParent) !== -1;
}
function canConnect(source, target, connection) {
if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
return null;
}
if (!is$1(connection, 'bpmn:DataAssociation')) {
if (canConnectMessageFlow(source, target)) {
return { type: 'bpmn:MessageFlow' };
}
if (canConnectSequenceFlow(source, target)) {
return { type: 'bpmn:SequenceFlow' };
}
}
var connectDataAssociation = canConnectDataAssociation(source, target);
if (connectDataAssociation) {
return connectDataAssociation;
}
if (isCompensationBoundary(source) && isForCompensation(target)) {
return {
type: 'bpmn:Association',
associationDirection: 'One'
};
}
if (canConnectAssociation(source, target)) {
return {
type: 'bpmn:Association'
};
}
return false;
}
/**
* Can an element be dropped into the target element
*
* @return {boolean}
*/
function canDrop(element, target, position) {
// can move labels and groups everywhere
if (isLabel$6(element) || isGroup(element)) {
return true;
}
// disallow to create elements on collapsed pools
if (is$1(target, 'bpmn:Participant') && !isExpanded(target)) {
return false;
}
// allow to create new participants on
// existing collaboration and process diagrams
if (is$1(element, 'bpmn:Participant')) {
return is$1(target, 'bpmn:Process') || is$1(target, 'bpmn:Collaboration');
}
// allow moving DataInput / DataOutput within its original container only
if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {
if (element.parent) {
return target === element.parent;
}
}
// allow creating lanes on participants and other lanes only
if (is$1(element, 'bpmn:Lane')) {
return is$1(target, 'bpmn:Participant') || is$1(target, 'bpmn:Lane');
}
// disallow dropping boundary events which cannot replace with intermediate event
if (is$1(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
return false;
}
// drop flow elements onto flow element containers
// and participants
if (is$1(element, 'bpmn:FlowElement') && !is$1(element, 'bpmn:DataStoreReference')) {
if (is$1(target, 'bpmn:FlowElementsContainer')) {
return isExpanded(target);
}
return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
}
// disallow dropping data store reference if there is no process to append to
if (is$1(element, 'bpmn:DataStoreReference') && is$1(target, 'bpmn:Collaboration')) {
return some(getBusinessObject(target).get('participants'), function(participant) {
return !!participant.get('processRef');
});
}
// account for the fact that data associations are always
// rendered and moved to top (Process or Collaboration level)
//
// artifacts may be placed wherever, too
if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
return isAny(target, [
'bpmn:Collaboration',
'bpmn:Lane',
'bpmn:Participant',
'bpmn:Process',
'bpmn:SubProcess' ]);
}
if (is$1(element, 'bpmn:MessageFlow')) {
return is$1(target, 'bpmn:Collaboration')
|| element.source.parent == target
|| element.target.parent == target;
}
return false;
}
function isDroppableBoundaryEvent(event) {
return getBusinessObject(event).cancelActivity && (
hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
);
}
function isBoundaryEvent(element) {
return !isLabel$6(element) && is$1(element, 'bpmn:BoundaryEvent');
}
function isLane(element) {
return is$1(element, 'bpmn:Lane');
}
/**
* We treat IntermediateThrowEvents as boundary events during create,
* this must be reflected in the rules.
*/
function isBoundaryCandidate(element) {
if (isBoundaryEvent(element)) {
return true;
}
if (is$1(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
return true;
}
return (
is$1(element, 'bpmn:IntermediateCatchEvent') &&
hasCommonBoundaryIntermediateEventDefinition(element)
);
}
function hasNoEventDefinition(element) {
var bo = getBusinessObject(element);
return bo && !(bo.eventDefinitions && bo.eventDefinitions.length);
}
function hasCommonBoundaryIntermediateEventDefinition(element) {
return hasOneOfEventDefinitions(element, [
'bpmn:MessageEventDefinition',
'bpmn:TimerEventDefinition',
'bpmn:SignalEventDefinition',
'bpmn:ConditionalEventDefinition'
]);
}
function hasOneOfEventDefinitions(element, eventDefinitions) {
return eventDefinitions.some(function(definition) {
return hasEventDefinition(element, definition);
});
}
function isReceiveTaskAfterEventBasedGateway(element) {
return (
is$1(element, 'bpmn:ReceiveTask') &&
find(element.incoming, function(incoming) {
return is$1(incoming.source, 'bpmn:EventBasedGateway');
})
);
}
function canAttach(elements, target, source, position) {
if (!Array.isArray(elements)) {
elements = [ elements ];
}
// only (re-)attach one element at a time
if (elements.length !== 1) {
return false;
}
var element = elements[0];
// do not attach labels
if (isLabel$6(element)) {
return false;
}
// only handle boundary events
if (!isBoundaryCandidate(element)) {
return false;
}
// disallow drop on event sub processes
if (isEventSubProcess(target)) {
return false;
}
// only allow drop on non compensation activities
if (!is$1(target, 'bpmn:Activity') || isForCompensation(target)) {
return false;
}
// only attach to subprocess border
if (position && !getBoundaryAttachment(position, target)) {
return false;
}
// do not attach on receive tasks after event based gateways
if (isReceiveTaskAfterEventBasedGateway(target)) {
return false;
}
return 'attach';
}
/**
* Defines how to replace elements for a given target.
*
* Returns an array containing all elements which will be replaced.
*
* @example
*
* [{ id: 'IntermediateEvent_2',
* type: 'bpmn:StartEvent'
* },
* { id: 'IntermediateEvent_5',
* type: 'bpmn:EndEvent'
* }]
*
* @param {Array} elements
* @param {Object} target
*
* @return {Object} an object containing all elements which have to be replaced
*/
function canReplace(elements, target, position) {
if (!target) {
return false;
}
var canExecute = {
replacements: []
};
forEach$1(elements, function(element) {
if (!isEventSubProcess(target)) {
if (is$1(element, 'bpmn:StartEvent') &&
element.type !== 'label' &&
canDrop(element, target)) {
// replace a non-interrupting start event by a blank interrupting start event
// when the target is not an event sub process
if (!isInterrupting(element)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:StartEvent'
});
}
// replace an error/escalation/compensate start event by a blank interrupting start event
// when the target is not an event sub process
if (hasErrorEventDefinition(element) ||
hasEscalationEventDefinition(element) ||
hasCompensateEventDefinition(element)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:StartEvent'
});
}
// replace a typed start event by a blank interrupting start event
// when the target is a sub process but not an event sub process
if (hasOneOfEventDefinitions(element,
[
'bpmn:MessageEventDefinition',
'bpmn:TimerEventDefinition',
'bpmn:SignalEventDefinition',
'bpmn:ConditionalEventDefinition'
]) &&
is$1(target, 'bpmn:SubProcess')) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:StartEvent'
});
}
}
}
if (!is$1(target, 'bpmn:Transaction')) {
if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') &&
element.type !== 'label') {
if (is$1(element, 'bpmn:EndEvent') && canDrop(element, target)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:EndEvent'
});
}
if (is$1(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
canExecute.replacements.push({
oldElementId: element.id,
newElementType: 'bpmn:BoundaryEvent'
});
}
}
}
});
return canExecute.replacements.length ? canExecute : false;
}
function canMove(elements, target) {
// do not move selection containing lanes
if (some(elements, isLane)) {
return false;
}
// allow default move check to start move operation
if (!target) {
return true;
}
return elements.every(function(element) {
return canDrop(element, target);
});
}
function canCreate(shape, target, source, position) {
if (!target) {
return false;
}
if (isLabel$6(shape) || isGroup(shape)) {
return true;
}
if (isSame$1(source, target)) {
return false;
}
// ensure we do not drop the element
// into source
if (source && isParent(source, target)) {
return false;
}
return canDrop(shape, target) || canInsert(shape, target);
}
function canResize(shape, newBounds) {
if (is$1(shape, 'bpmn:SubProcess')) {
return (
isExpanded(shape) && (
!newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
)
);
}
if (is$1(shape, 'bpmn:Lane')) {
return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60);
}
if (is$1(shape, 'bpmn:Participant')) {
return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50);
}
if (isTextAnnotation(shape)) {
return true;
}
if (isGroup(shape)) {
return true;
}
return false;
}
/**
* Check, whether one side of the relationship
* is a text annotation.
*/
function isOneTextAnnotation(source, target) {
var sourceTextAnnotation = isTextAnnotation(source),
targetTextAnnotation = isTextAnnotation(target);
return (
(sourceTextAnnotation || targetTextAnnotation) &&
(sourceTextAnnotation !== targetTextAnnotation)
);
}
function canConnectAssociation(source, target) {
// compensation boundary events are exception
if (isCompensationBoundary(source) && isForCompensation(target)) {
return true;
}
// don't connect parent <-> child
if (isParent(target, source) || isParent(source, target)) {
return false;
}
// allow connection of associations between <!TextAnnotation> and <TextAnnotation>
if (isOneTextAnnotation(source, target)) {
return true;
}
// can connect associations where we can connect
// data associations, too (!)
return !!canConnectDataAssociation(source, target);
}
function canConnectMessageFlow(source, target) {
// during connect user might move mouse out of canvas
// https://github.com/bpmn-io/bpmn-js/issues/1033
if (getRootElement(source) && !getRootElement(target)) {
return false;
}
return (
isMessageFlowSource(source) &&
isMessageFlowTarget(target) &&
!isSameOrganization(source, target)
);
}
function canConnectSequenceFlow(source, target) {
if (
isEventBasedTarget(target) &&
target.incoming.length > 0 &&
areOutgoingEventBasedGatewayConnections(target.incoming) &&
!is$1(source, 'bpmn:EventBasedGateway')
) {
return false;
}
return isSequenceFlowSource(source) &&
isSequenceFlowTarget(target) &&
isSameScope(source, target) &&
!(is$1(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
}
function canConnectDataAssociation(source, target) {
if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
return { type: 'bpmn:DataInputAssociation' };
}
if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
return { type: 'bpmn:DataOutputAssociation' };
}
return false;
}
function canInsert(shape, flow, position) {
if (!flow) {
return false;
}
if (Array.isArray(shape)) {
if (shape.length !== 1) {
return false;
}
shape = shape[0];
}
if (flow.source === shape ||
flow.target === shape) {
return false;
}
// return true if we can drop on the
// underlying flow parent
//
// at this point we are not really able to talk
// about connection rules (yet)
return (
isAny(flow, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
!isLabel$6(flow) &&
is$1(shape, 'bpmn:FlowNode') &&
!is$1(shape, 'bpmn:BoundaryEvent') &&
canDrop(shape, flow.parent));
}
function includes$5(elements, element) {
return (elements && element) && elements.indexOf(element) !== -1;
}
function canCopy(elements, element) {
if (isLabel$6(element)) {
return true;
}
if (is$1(element, 'bpmn:Lane') && !includes$5(elements, element.parent)) {
return false;
}
return true;
}
function isOutgoingEventBasedGatewayConnection(connection) {
if (connection && connection.source) {
return is$1(connection.source, 'bpmn:EventBasedGateway');
}
}
function areOutgoingEventBasedGatewayConnections(connections) {
connections = connections || [];
return connections.some(isOutgoingEventBasedGatewayConnection);
}
function getRootElement(element) {
return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration');
}
var RulesModule = {
__depends__: [
RulesModule$1
],
__init__: [ 'bpmnRules' ],
bpmnRules: [ 'type', BpmnRules ]
};
var HIGH_PRIORITY$6 = 2000;
function BpmnDiOrdering(eventBus, canvas) {
eventBus.on('saveXML.start', HIGH_PRIORITY$6, orderDi);
function orderDi() {
var rootElements = canvas.getRootElements();
forEach$1(rootElements, function(root) {
var rootDi = getDi(root),
elements,
diElements;
elements = selfAndAllChildren([ root ], false);
// only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane
elements = filter(elements, function(element) {
return element !== root && !element.labelTarget;
});
diElements = map(elements, getDi);
rootDi.set('planeElement', diElements);
});
}
}
BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ];
var DiOrderingModule = {
__init__: [
'bpmnDiOrdering'
],
bpmnDiOrdering: [ 'type', BpmnDiOrdering ]
};
/**
* An abstract provider that allows modelers to implement a custom
* ordering of diagram elements on the canvas.
*
* It makes sure that the order is always preserved during element
* creation and move operations.
*
* In order to use this behavior, inherit from it and override
* the method {@link OrderingProvider#getOrdering}.
*
* @example
*
* ```javascript
* function CustomOrderingProvider(eventBus) {
* OrderingProvider.call(this, eventBus);
*
* this.getOrdering = function(element, newParent) {
* // always insert elements at the front
* // when moving
* return {
* index: 0,
* parent: newParent
* };
* };
* }
* ```
*
* @param {EventBus} eventBus
*/
function OrderingProvider(eventBus) {
CommandInterceptor.call(this, eventBus);
var self = this;
this.preExecute([ 'shape.create', 'connection.create' ], function(event) {
var context = event.context,
element = context.shape || context.connection,
parent = context.parent;
var ordering = self.getOrdering(element, parent);
if (ordering) {
if (ordering.parent !== undefined) {
context.parent = ordering.parent;
}
context.parentIndex = ordering.index;
}
});
this.preExecute([ 'shape.move', 'connection.move' ], function(event) {
var context = event.context,
element = context.shape || context.connection,
parent = context.newParent || element.parent;
var ordering = self.getOrdering(element, parent);
if (ordering) {
if (ordering.parent !== undefined) {
context.newParent = ordering.parent;
}
context.newParentIndex = ordering.index;
}
});
}
/**
* Return a custom ordering of the element, both in terms
* of parent element and index in the new parent.
*
* Implementors of this method must return an object with
* `parent` _and_ `index` in it.
*
* @param {djs.model.Base} element
* @param {djs.model.Shape} newParent
*
* @return {Object} ordering descriptor
*/
OrderingProvider.prototype.getOrdering = function(element, newParent) {
return null;
};
e(OrderingProvider, CommandInterceptor);
/**
* a simple ordering provider that makes sure:
*
* (0) labels and groups are rendered always on top
* (1) elements are ordered by a {level} property
*/
function BpmnOrderingProvider(eventBus, canvas, translate) {
OrderingProvider.call(this, eventBus);
var orders = [
{ type: 'bpmn:SubProcess', order: { level: 6 } },
// handle SequenceFlow(s) like message flows and render them always on top
{
type: 'bpmn:SequenceFlow',
order: {
level: 9,
containers: [
'bpmn:Participant',
'bpmn:FlowElementsContainer'
]
}
},
// handle DataAssociation(s) like message flows and render them always on top
{
type: 'bpmn:DataAssociation',
order: {
level: 9,
containers: [
'bpmn:Collaboration',
'bpmn:FlowElementsContainer'
]
}
},
{
type: 'bpmn:MessageFlow', order: {
level: 9,
containers: [ 'bpmn:Collaboration' ]
}
},
{
type: 'bpmn:Association',
order: {
level: 6,
containers: [
'bpmn:Participant',
'bpmn:FlowElementsContainer',
'bpmn:Collaboration'
]
}
},
{ type: 'bpmn:BoundaryEvent', order: { level: 8 } },
{
type: 'bpmn:Group',
order: {
level: 10,
containers: [
'bpmn:Collaboration',
'bpmn:FlowElementsContainer'
]
}
},
{ type: 'bpmn:FlowElement', order: { level: 5 } },
{ type: 'bpmn:Participant', order: { level: -2 } },
{ type: 'bpmn:Lane', order: { level: -1 } }
];
function computeOrder(element) {
if (element.labelTarget) {
return { level: 10 };
}
var entry = find(orders, function(o) {
return isAny(element, [ o.type ]);
});
return entry && entry.order || { level: 1 };
}
function getOrder(element) {
var order = element.order;
if (!order) {
element.order = order = computeOrder(element);
}
if (!order) {
throw new Error('no order for <' + element.id + '>');
}
return order;
}
function findActualParent(element, newParent, containers) {
var actualParent = newParent;
while (actualParent) {
if (isAny(actualParent, containers)) {
break;
}
actualParent = actualParent.parent;
}
if (!actualParent) {
throw new Error('no parent for <' + element.id + '> in <' + (newParent && newParent.id) + '>');
}
return actualParent;
}
this.getOrdering = function(element, newParent) {
// render labels always on top
if (element.labelTarget) {
return {
parent: canvas.findRoot(newParent) || canvas.getRootElement(),
index: -1
};
}
var elementOrder = getOrder(element);
if (elementOrder.containers) {
newParent = findActualParent(element, newParent, elementOrder.containers);
}
var currentIndex = newParent.children.indexOf(element);
var insertIndex = findIndex(newParent.children, function(child) {
// do not compare with labels, they are created
// in the wrong order (right after elements) during import and
// mess up the positioning.
if (!element.labelTarget && child.labelTarget) {
return false;
}
return elementOrder.level < getOrder(child).level;
});
// if the element is already in the child list at
// a smaller index, we need to adjust the insert index.
// this takes into account that the element is being removed
// before being re-inserted
if (insertIndex !== -1) {
if (currentIndex !== -1 && currentIndex < insertIndex) {
insertIndex -= 1;
}
}
return {
index: insertIndex,
parent: newParent
};
};
}
BpmnOrderingProvider.$inject = [ 'eventBus', 'canvas', 'translate' ];
e(BpmnOrderingProvider, OrderingProvider);
var OrderingModule = {
__depends__: [
translate
],
__init__: [ 'bpmnOrderingProvider' ],
bpmnOrderingProvider: [ 'type', BpmnOrderingProvider ]
};
/**
* A service that offers un- and redoable execution of commands.
*
* The command stack is responsible for executing modeling actions
* in a un- and redoable manner. To do this it delegates the actual
* command execution to {@link CommandHandler}s.
*
* Command handlers provide {@link CommandHandler#execute(ctx)} and
* {@link CommandHandler#revert(ctx)} methods to un- and redo a command
* identified by a command context.
*
*
* ## Life-Cycle events
*
* In the process the command stack fires a number of life-cycle events
* that other components to participate in the command execution.
*
* * preExecute
* * preExecuted
* * execute
* * executed
* * postExecute
* * postExecuted
* * revert
* * reverted
*
* A special event is used for validating, whether a command can be
* performed prior to its execution.
*
* * canExecute
*
* Each of the events is fired as `commandStack.{eventName}` and
* `commandStack.{commandName}.{eventName}`, respectively. This gives
* components fine grained control on where to hook into.
*
* The event object fired transports `command`, the name of the
* command and `context`, the command context.
*
*
* ## Creating Command Handlers
*
* Command handlers should provide the {@link CommandHandler#execute(ctx)}
* and {@link CommandHandler#revert(ctx)} methods to implement
* redoing and undoing of a command.
*
* A command handler _must_ ensure undo is performed properly in order
* not to break the undo chain. It must also return the shapes that
* got changed during the `execute` and `revert` operations.
*
* Command handlers may execute other modeling operations (and thus
* commands) in their `preExecute` and `postExecute` phases. The command
* stack will properly group all commands together into a logical unit
* that may be re- and undone atomically.
*
* Command handlers must not execute other commands from within their
* core implementation (`execute`, `revert`).
*
*
* ## Change Tracking
*
* During the execution of the CommandStack it will keep track of all
* elements that have been touched during the command's execution.
*
* At the end of the CommandStack execution it will notify interested
* components via an 'elements.changed' event with all the dirty
* elements.
*
* The event can be picked up by components that are interested in the fact
* that elements have been changed. One use case for this is updating
* their graphical representation after moving / resizing or deletion.
*
* @see CommandHandler
*
* @param {EventBus} eventBus
* @param {Injector} injector
*/
function CommandStack(eventBus, injector) {
/**
* A map of all registered command handlers.
*
* @type {Object}
*/
this._handlerMap = {};
/**
* A stack containing all re/undoable actions on the diagram
*
* @type {Array<Object>}
*/
this._stack = [];
/**
* The current index on the stack
*
* @type {number}
*/
this._stackIdx = -1;
/**
* Current active commandStack execution
*
* @type {Object}
* @property {Object[]} actions
* @property {Object[]} dirty
* @property { 'undo' | 'redo' | 'clear' | 'execute' | null } trigger the cause of the current excecution
*/
this._currentExecution = {
actions: [],
dirty: [],
trigger: null
};
this._injector = injector;
this._eventBus = eventBus;
this._uid = 1;
eventBus.on([
'diagram.destroy',
'diagram.clear'
], function() {
this.clear(false);
}, this);
}
CommandStack.$inject = [ 'eventBus', 'injector' ];
/**
* Execute a command
*
* @param {string} command the command to execute
* @param {Object} context the environment to execute the command in
*/
CommandStack.prototype.execute = function(command, context) {
if (!command) {
throw new Error('command required');
}
this._currentExecution.trigger = 'execute';
var action = { command: command, context: context };
this._pushAction(action);
this._internalExecute(action);
this._popAction(action);
};
/**
* Ask whether a given command can be executed.
*
* Implementors may hook into the mechanism on two ways:
*
* * in event listeners:
*
* Users may prevent the execution via an event listener.
* It must prevent the default action for `commandStack.(<command>.)canExecute` events.
*
* * in command handlers:
*
* If the method {@link CommandHandler#canExecute} is implemented in a handler
* it will be called to figure out whether the execution is allowed.
*
* @param {string} command the command to execute
* @param {Object} context the environment to execute the command in
*
* @return {boolean} true if the command can be executed
*/
CommandStack.prototype.canExecute = function(command, context) {
var action = { command: command, context: context };
var handler = this._getHandler(command);
var result = this._fire(command, 'canExecute', action);
// handler#canExecute will only be called if no listener
// decided on a result already
if (result === undefined) {
if (!handler) {
return false;
}
if (handler.canExecute) {
result = handler.canExecute(context);
}
}
return result;
};
/**
* Clear the command stack, erasing all undo / redo history
*/
CommandStack.prototype.clear = function(emit) {
this._stack.length = 0;
this._stackIdx = -1;
if (emit !== false) {
this._fire('changed', { trigger: 'clear' });
}
};
/**
* Undo last command(s)
*/
CommandStack.prototype.undo = function() {
var action = this._getUndoAction(),
next;
if (action) {
this._currentExecution.trigger = 'undo';
this._pushAction(action);
while (action) {
this._internalUndo(action);
next = this._getUndoAction();
if (!next || next.id !== action.id) {
break;
}
action = next;
}
this._popAction();
}
};
/**
* Redo last command(s)
*/
CommandStack.prototype.redo = function() {
var action = this._getRedoAction(),
next;
if (action) {
this._currentExecution.trigger = 'redo';
this._pushAction(action);
while (action) {
this._internalExecute(action, true);
next = this._getRedoAction();
if (!next || next.id !== action.id) {
break;
}
action = next;
}
this._popAction();
}
};
/**
* Register a handler instance with the command stack
*
* @param {string} command
* @param {CommandHandler} handler
*/
CommandStack.prototype.register = function(command, handler) {
this._setHandler(command, handler);
};
/**
* Register a handler type with the command stack
* by instantiating it and injecting its dependencies.
*
* @param {string} command
* @param {Function} a constructor for a {@link CommandHandler}
*/
CommandStack.prototype.registerHandler = function(command, handlerCls) {
if (!command || !handlerCls) {
throw new Error('command and handlerCls must be defined');
}
var handler = this._injector.instantiate(handlerCls);
this.register(command, handler);
};
CommandStack.prototype.canUndo = function() {
return !!this._getUndoAction();
};
CommandStack.prototype.canRedo = function() {
return !!this._getRedoAction();
};
// stack access //////////////////////
CommandStack.prototype._getRedoAction = function() {
return this._stack[this._stackIdx + 1];
};
CommandStack.prototype._getUndoAction = function() {
return this._stack[this._stackIdx];
};
// internal functionality //////////////////////
CommandStack.prototype._internalUndo = function(action) {
var self = this;
var command = action.command,
context = action.context;
var handler = this._getHandler(command);
// guard against illegal nested command stack invocations
this._atomicDo(function() {
self._fire(command, 'revert', action);
if (handler.revert) {
self._markDirty(handler.revert(context));
}
self._revertedAction(action);
self._fire(command, 'reverted', action);
});
};
CommandStack.prototype._fire = function(command, qualifier, event) {
if (arguments.length < 3) {
event = qualifier;
qualifier = null;
}
var names = qualifier ? [ command + '.' + qualifier, qualifier ] : [ command ],
i, name, result;
event = this._eventBus.createEvent(event);
for (i = 0; (name = names[i]); i++) {
result = this._eventBus.fire('commandStack.' + name, event);
if (event.cancelBubble) {
break;
}
}
return result;
};
CommandStack.prototype._createId = function() {
return this._uid++;
};
CommandStack.prototype._atomicDo = function(fn) {
var execution = this._currentExecution;
execution.atomic = true;
try {
fn();
} finally {
execution.atomic = false;
}
};
CommandStack.prototype._internalExecute = function(action, redo) {
var self = this;
var command = action.command,
context = action.context;
var handler = this._getHandler(command);
if (!handler) {
throw new Error('no command handler registered for <' + command + '>');
}
this._pushAction(action);
if (!redo) {
this._fire(command, 'preExecute', action);
if (handler.preExecute) {
handler.preExecute(context);
}
this._fire(command, 'preExecuted', action);
}
// guard against illegal nested command stack invocations
this._atomicDo(function() {
self._fire(command, 'execute', action);
if (handler.execute) {
// actual execute + mark return results as dirty
self._markDirty(handler.execute(context));
}
// log to stack
self._executedAction(action, redo);
self._fire(command, 'executed', action);
});
if (!redo) {
this._fire(command, 'postExecute', action);
if (handler.postExecute) {
handler.postExecute(context);
}
this._fire(command, 'postExecuted', action);
}
this._popAction(action);
};
CommandStack.prototype._pushAction = function(action) {
var execution = this._currentExecution,
actions = execution.actions;
var baseAction = actions[0];
if (execution.atomic) {
throw new Error('illegal invocation in <execute> or <revert> phase (action: ' + action.command + ')');
}
if (!action.id) {
action.id = (baseAction && baseAction.id) || this._createId();
}
actions.push(action);
};
CommandStack.prototype._popAction = function() {
var execution = this._currentExecution,
trigger = execution.trigger,
actions = execution.actions,
dirty = execution.dirty;
actions.pop();
if (!actions.length) {
this._eventBus.fire('elements.changed', { elements: uniqueBy('id', dirty.reverse()) });
dirty.length = 0;
this._fire('changed', { trigger: trigger });
execution.trigger = null;
}
};
CommandStack.prototype._markDirty = function(elements) {
var execution = this._currentExecution;
if (!elements) {
return;
}
elements = isArray$3(elements) ? elements : [ elements ];
execution.dirty = execution.dirty.concat(elements);
};
CommandStack.prototype._executedAction = function(action, redo) {
var stackIdx = ++this._stackIdx;
if (!redo) {
this._stack.splice(stackIdx, this._stack.length, action);
}
};
CommandStack.prototype._revertedAction = function(action) {
this._stackIdx--;
};
CommandStack.prototype._getHandler = function(command) {
return this._handlerMap[command];
};
CommandStack.prototype._setHandler = function(command, handler) {
if (!command || !handler) {
throw new Error('command and handler required');
}
if (this._handlerMap[command]) {
throw new Error('overriding handler for command <' + command + '>');
}
this._handlerMap[command] = handler;
};
var CommandModule = {
commandStack: [ 'type', CommandStack ]
};
// document wide unique tooltip ids
var ids = new IdGenerator('tt');
function createRoot(parentNode) {
var root = domify(
'<div class="djs-tooltip-container" />'
);
assign$1(root, {
position: 'absolute',
width: '0',
height: '0'
});
parentNode.insertBefore(root, parentNode.firstChild);
return root;
}
function setPosition(el, x, y) {
assign$1(el, { left: x + 'px', top: y + 'px' });
}
function setVisible(el, visible) {
el.style.display = visible === false ? 'none' : '';
}
var tooltipClass = 'djs-tooltip',
tooltipSelector = '.' + tooltipClass;
/**
* A service that allows users to render tool tips on the diagram.
*
* The tooltip service will take care of updating the tooltip positioning
* during navigation + zooming.
*
* @example
*
* ```javascript
*
* // add a pink badge on the top left of the shape
* tooltips.add({
* position: {
* x: 50,
* y: 100
* },
* html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
* });
*
* // or with optional life span
* tooltips.add({
* position: {
* top: -5,
* left: -5
* },
* html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>',
* ttl: 2000
* });
*
* // remove a tool tip
* var id = tooltips.add(...);
* tooltips.remove(id);
* ```
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function Tooltips(eventBus, canvas) {
this._eventBus = eventBus;
this._canvas = canvas;
this._ids = ids;
this._tooltipDefaults = {
show: {
minZoom: 0.7,
maxZoom: 5.0
}
};
/**
* Mapping tooltipId -> tooltip
*/
this._tooltips = {};
// root html element for all tooltips
this._tooltipRoot = createRoot(canvas.getContainer());
var self = this;
delegate.bind(this._tooltipRoot, tooltipSelector, 'mousedown', function(event) {
event.stopPropagation();
});
delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseover', function(event) {
self.trigger('mouseover', event);
});
delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseout', function(event) {
self.trigger('mouseout', event);
});
this._init();
}
Tooltips.$inject = [ 'eventBus', 'canvas' ];
/**
* Adds a HTML tooltip to the diagram
*
* @param {Object} tooltip the tooltip configuration
*
* @param {string|DOMElement} tooltip.html html element to use as an tooltip
* @param {Object} [tooltip.show] show configuration
* @param {number} [tooltip.show.minZoom] minimal zoom level to show the tooltip
* @param {number} [tooltip.show.maxZoom] maximum zoom level to show the tooltip
* @param {Object} tooltip.position where to attach the tooltip
* @param {number} [tooltip.position.left] relative to element bbox left attachment
* @param {number} [tooltip.position.top] relative to element bbox top attachment
* @param {number} [tooltip.position.bottom] relative to element bbox bottom attachment
* @param {number} [tooltip.position.right] relative to element bbox right attachment
* @param {number} [tooltip.timeout=-1]
*
* @return {string} id that may be used to reference the tooltip for update or removal
*/
Tooltips.prototype.add = function(tooltip) {
if (!tooltip.position) {
throw new Error('must specifiy tooltip position');
}
if (!tooltip.html) {
throw new Error('must specifiy tooltip html');
}
var id = this._ids.next();
tooltip = assign({}, this._tooltipDefaults, tooltip, {
id: id
});
this._addTooltip(tooltip);
if (tooltip.timeout) {
this.setTimeout(tooltip);
}
return id;
};
Tooltips.prototype.trigger = function(action, event) {
var node = event.delegateTarget || event.target;
var tooltip = this.get(attr$1(node, 'data-tooltip-id'));
if (!tooltip) {
return;
}
if (action === 'mouseover' && tooltip.timeout) {
this.clearTimeout(tooltip);
}
if (action === 'mouseout' && tooltip.timeout) {
// cut timeout after mouse out
tooltip.timeout = 1000;
this.setTimeout(tooltip);
}
};
/**
* Get a tooltip with the given id
*
* @param {string} id
*/
Tooltips.prototype.get = function(id) {
if (typeof id !== 'string') {
id = id.id;
}
return this._tooltips[id];
};
Tooltips.prototype.clearTimeout = function(tooltip) {
tooltip = this.get(tooltip);
if (!tooltip) {
return;
}
var removeTimer = tooltip.removeTimer;
if (removeTimer) {
clearTimeout(removeTimer);
tooltip.removeTimer = null;
}
};
Tooltips.prototype.setTimeout = function(tooltip) {
tooltip = this.get(tooltip);
if (!tooltip) {
return;
}
this.clearTimeout(tooltip);
var self = this;
tooltip.removeTimer = setTimeout(function() {
self.remove(tooltip);
}, tooltip.timeout);
};
/**
* Remove an tooltip with the given id
*
* @param {string} id
*/
Tooltips.prototype.remove = function(id) {
var tooltip = this.get(id);
if (tooltip) {
remove$2(tooltip.html);
remove$2(tooltip.htmlContainer);
delete tooltip.htmlContainer;
delete this._tooltips[tooltip.id];
}
};
Tooltips.prototype.show = function() {
setVisible(this._tooltipRoot);
};
Tooltips.prototype.hide = function() {
setVisible(this._tooltipRoot, false);
};
Tooltips.prototype._updateRoot = function(viewbox) {
var a = viewbox.scale || 1;
var d = viewbox.scale || 1;
var matrix = 'matrix(' + a + ',0,0,' + d + ',' + (-1 * viewbox.x * a) + ',' + (-1 * viewbox.y * d) + ')';
this._tooltipRoot.style.transform = matrix;
this._tooltipRoot.style['-ms-transform'] = matrix;
};
Tooltips.prototype._addTooltip = function(tooltip) {
var id = tooltip.id,
html = tooltip.html,
htmlContainer,
tooltipRoot = this._tooltipRoot;
// unwrap jquery (for those who need it)
if (html.get && html.constructor.prototype.jquery) {
html = html.get(0);
}
// create proper html elements from
// tooltip HTML strings
if (isString(html)) {
html = domify(html);
}
htmlContainer = domify('<div data-tooltip-id="' + id + '" class="' + tooltipClass + '">');
assign$1(htmlContainer, { position: 'absolute' });
htmlContainer.appendChild(html);
if (tooltip.type) {
classes$1(htmlContainer).add('djs-tooltip-' + tooltip.type);
}
if (tooltip.className) {
classes$1(htmlContainer).add(tooltip.className);
}
tooltip.htmlContainer = htmlContainer;
tooltipRoot.appendChild(htmlContainer);
this._tooltips[id] = tooltip;
this._updateTooltip(tooltip);
};
Tooltips.prototype._updateTooltip = function(tooltip) {
var position = tooltip.position,
htmlContainer = tooltip.htmlContainer;
// update overlay html based on tooltip x, y
setPosition(htmlContainer, position.x, position.y);
};
Tooltips.prototype._updateTooltipVisibilty = function(viewbox) {
forEach$1(this._tooltips, function(tooltip) {
var show = tooltip.show,
htmlContainer = tooltip.htmlContainer,
visible = true;
if (show) {
if (show.minZoom > viewbox.scale ||
show.maxZoom < viewbox.scale) {
visible = false;
}
setVisible(htmlContainer, visible);
}
});
};
Tooltips.prototype._init = function() {
var self = this;
// scroll/zoom integration
function updateViewbox(viewbox) {
self._updateRoot(viewbox);
self._updateTooltipVisibilty(viewbox);
self.show();
}
this._eventBus.on('canvas.viewbox.changing', function(event) {
self.hide();
});
this._eventBus.on('canvas.viewbox.changed', function(event) {
updateViewbox(event.viewbox);
});
};
var TooltipsModule = {
__init__: [ 'tooltips' ],
tooltips: [ 'type', Tooltips ]
};
/**
* Remove from the beginning of a collection until it is empty.
*
* This is a null-safe operation that ensures elements
* are being removed from the given collection until the
* collection is empty.
*
* The implementation deals with the fact that a remove operation
* may touch, i.e. remove multiple elements in the collection
* at a time.
*
* @param {Array<Object>} [collection]
* @param {Function} removeFn
*
* @return {Array<Object>} the cleared collection
*/
function saveClear(collection, removeFn) {
if (typeof removeFn !== 'function') {
throw new Error('removeFn iterator must be a function');
}
if (!collection) {
return;
}
var e;
while ((e = collection[0])) {
removeFn(e);
}
return collection;
}
var LOW_PRIORITY$6 = 250,
HIGH_PRIORITY$5 = 1400;
/**
* A handler that makes sure labels are properly moved with
* their label targets.
*
* @param {didi.Injector} injector
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
function LabelSupport(injector, eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
var movePreview = injector.get('movePreview', false);
// remove labels from the collection that are being
// moved with other elements anyway
eventBus.on('shape.move.start', HIGH_PRIORITY$5, function(e) {
var context = e.context,
shapes = context.shapes,
validatedShapes = context.validatedShapes;
context.shapes = removeLabels(shapes);
context.validatedShapes = removeLabels(validatedShapes);
});
// add labels to visual's group
movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$6, function(e) {
var context = e.context,
shapes = context.shapes;
var labels = [];
forEach$1(shapes, function(element) {
forEach$1(element.labels, function(label) {
if (!label.hidden && context.shapes.indexOf(label) === -1) {
labels.push(label);
}
if (element.labelTarget) {
labels.push(element);
}
});
});
forEach$1(labels, function(label) {
movePreview.makeDraggable(context, label, true);
});
});
// add all labels to move closure
this.preExecuted('elements.move', HIGH_PRIORITY$5, function(e) {
var context = e.context,
closure = context.closure,
enclosedElements = closure.enclosedElements;
var enclosedLabels = [];
// find labels that are not part of
// move closure yet and add them
forEach$1(enclosedElements, function(element) {
forEach$1(element.labels, function(label) {
if (!enclosedElements[label.id]) {
enclosedLabels.push(label);
}
});
});
closure.addAll(enclosedLabels);
});
this.preExecute([
'connection.delete',
'shape.delete'
], function(e) {
var context = e.context,
element = context.connection || context.shape;
saveClear(element.labels, function(label) {
modeling.removeShape(label, { nested: true });
});
});
this.execute('shape.delete', function(e) {
var context = e.context,
shape = context.shape,
labelTarget = shape.labelTarget;
// unset labelTarget
if (labelTarget) {
context.labelTargetIndex = indexOf(labelTarget.labels, shape);
context.labelTarget = labelTarget;
shape.labelTarget = null;
}
});
this.revert('shape.delete', function(e) {
var context = e.context,
shape = context.shape,
labelTarget = context.labelTarget,
labelTargetIndex = context.labelTargetIndex;
// restore labelTarget
if (labelTarget) {
add(labelTarget.labels, shape, labelTargetIndex);
shape.labelTarget = labelTarget;
}
});
}
e(LabelSupport, CommandInterceptor);
LabelSupport.$inject = [
'injector',
'eventBus',
'modeling'
];
/**
* Return a filtered list of elements that do not
* contain attached elements with hosts being part
* of the selection.
*
* @param {Array<djs.model.Base>} elements
*
* @return {Array<djs.model.Base>} filtered
*/
function removeLabels(elements) {
return filter(elements, function(element) {
// filter out labels that are move together
// with their label targets
return elements.indexOf(element.labelTarget) === -1;
});
}
var LabelSupportModule = {
__init__: [ 'labelSupport' ],
labelSupport: [ 'type', LabelSupport ]
};
var LOW_PRIORITY$5 = 251,
HIGH_PRIORITY$4 = 1401;
var MARKER_ATTACH$1 = 'attach-ok';
/**
* Adds the notion of attached elements to the modeler.
*
* Optionally depends on `diagram-js/lib/features/move` to render
* the attached elements during move preview.
*
* Optionally depends on `diagram-js/lib/features/label-support`
* to render attached labels during move preview.
*
* @param {didi.Injector} injector
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {Rules} rules
* @param {Modeling} modeling
*/
function AttachSupport(injector, eventBus, canvas, rules, modeling) {
CommandInterceptor.call(this, eventBus);
var movePreview = injector.get('movePreview', false);
// remove all the attached elements from the shapes to be validated
// add all the attached shapes to the overall list of moved shapes
eventBus.on('shape.move.start', HIGH_PRIORITY$4, function(e) {
var context = e.context,
shapes = context.shapes,
validatedShapes = context.validatedShapes;
context.shapes = addAttached(shapes);
context.validatedShapes = removeAttached(validatedShapes);
});
// add attachers to the visual's group
movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$5, function(e) {
var context = e.context,
shapes = context.shapes,
attachers = getAttachers(shapes);
forEach$1(attachers, function(attacher) {
movePreview.makeDraggable(context, attacher, true);
forEach$1(attacher.labels, function(label) {
movePreview.makeDraggable(context, label, true);
});
});
});
// add attach-ok marker to current host
movePreview && eventBus.on('shape.move.start', function(event) {
var context = event.context,
shapes = context.shapes;
if (shapes.length !== 1) {
return;
}
var shape = shapes[0];
var host = shape.host;
if (host) {
canvas.addMarker(host, MARKER_ATTACH$1);
eventBus.once([
'shape.move.out',
'shape.move.cleanup'
], function() {
canvas.removeMarker(host, MARKER_ATTACH$1);
});
}
});
// add all attachers to move closure
this.preExecuted('elements.move', HIGH_PRIORITY$4, function(e) {
var context = e.context,
closure = context.closure,
shapes = context.shapes,
attachers = getAttachers(shapes);
forEach$1(attachers, function(attacher) {
closure.add(attacher, closure.topLevel[attacher.host.id]);
});
});
// perform the attaching after shapes are done moving
this.postExecuted('elements.move', function(e) {
var context = e.context,
shapes = context.shapes,
newHost = context.newHost,
attachers;
// only single elements can be attached
// multiply elements can be detached
if (newHost && shapes.length !== 1) {
return;
}
if (newHost) {
attachers = shapes;
} else {
// find attachers moved without host
attachers = filter(shapes, function(shape) {
var host = shape.host;
return isAttacher(shape) && !includes$4(shapes, host);
});
}
forEach$1(attachers, function(attacher) {
modeling.updateAttachment(attacher, newHost);
});
});
// ensure invalid attachment connections are removed
this.postExecuted('elements.move', function(e) {
var shapes = e.context.shapes;
forEach$1(shapes, function(shape) {
forEach$1(shape.attachers, function(attacher) {
// remove invalid outgoing connections
forEach$1(attacher.outgoing.slice(), function(connection) {
var allowed = rules.allowed('connection.reconnect', {
connection: connection,
source: connection.source,
target: connection.target
});
if (!allowed) {
modeling.removeConnection(connection);
}
});
// remove invalid incoming connections
forEach$1(attacher.incoming.slice(), function(connection) {
var allowed = rules.allowed('connection.reconnect', {
connection: connection,
source: connection.source,
target: connection.target
});
if (!allowed) {
modeling.removeConnection(connection);
}
});
});
});
});
this.postExecute('shape.create', function(e) {
var context = e.context,
shape = context.shape,
host = context.host;
if (host) {
modeling.updateAttachment(shape, host);
}
});
// update attachments if the host is replaced
this.postExecute('shape.replace', function(e) {
var context = e.context,
oldShape = context.oldShape,
newShape = context.newShape;
// move the attachers to the new host
saveClear(oldShape.attachers, function(attacher) {
var allowed = rules.allowed('elements.move', {
target: newShape,
shapes: [ attacher ]
});
if (allowed === 'attach') {
modeling.updateAttachment(attacher, newShape);
} else {
modeling.removeShape(attacher);
}
});
// move attachers if new host has different size
if (newShape.attachers.length) {
forEach$1(newShape.attachers, function(attacher) {
var delta = getNewAttachShapeDelta(attacher, oldShape, newShape);
modeling.moveShape(attacher, delta, attacher.parent);
});
}
});
// move shape on host resize
this.postExecute('shape.resize', function(event) {
var context = event.context,
shape = context.shape,
oldBounds = context.oldBounds,
newBounds = context.newBounds,
attachers = shape.attachers,
hints = context.hints || {};
if (hints.attachSupport === false) {
return;
}
forEach$1(attachers, function(attacher) {
var delta = getNewAttachShapeDelta(attacher, oldBounds, newBounds);
modeling.moveShape(attacher, delta, attacher.parent);
forEach$1(attacher.labels, function(label) {
modeling.moveShape(label, delta, label.parent);
});
});
});
// remove attachments
this.preExecute('shape.delete', function(event) {
var shape = event.context.shape;
saveClear(shape.attachers, function(attacher) {
modeling.removeShape(attacher);
});
if (shape.host) {
modeling.updateAttachment(shape, null);
}
});
}
e(AttachSupport, CommandInterceptor);
AttachSupport.$inject = [
'injector',
'eventBus',
'canvas',
'rules',
'modeling'
];
/**
* Return attachers of the given shapes
*
* @param {Array<djs.model.Base>} shapes
* @return {Array<djs.model.Base>}
*/
function getAttachers(shapes) {
return flatten(map(shapes, function(s) {
return s.attachers || [];
}));
}
/**
* Return a combined list of elements and
* attachers.
*
* @param {Array<djs.model.Base>} elements
* @return {Array<djs.model.Base>} filtered
*/
function addAttached(elements) {
var attachers = getAttachers(elements);
return unionBy('id', elements, attachers);
}
/**
* Return a filtered list of elements that do not
* contain attached elements with hosts being part
* of the selection.
*
* @param {Array<djs.model.Base>} elements
*
* @return {Array<djs.model.Base>} filtered
*/
function removeAttached(elements) {
var ids = groupBy(elements, 'id');
return filter(elements, function(element) {
while (element) {
// host in selection
if (element.host && ids[element.host.id]) {
return false;
}
element = element.parent;
}
return true;
});
}
function isAttacher(shape) {
return !!shape.host;
}
function includes$4(array, item) {
return array.indexOf(item) !== -1;
}
var AttachSupportModule = {
__depends__: [
RulesModule$1
],
__init__: [ 'attachSupport' ],
attachSupport: [ 'type', AttachSupport ]
};
var LOW_PRIORITY$4 = 250;
/**
* The tool manager acts as middle-man between the available tool's and the Palette,
* it takes care of making sure that the correct active state is set.
*
* @param {Object} eventBus
* @param {Object} dragging
*/
function ToolManager(eventBus, dragging) {
this._eventBus = eventBus;
this._dragging = dragging;
this._tools = [];
this._active = null;
}
ToolManager.$inject = [ 'eventBus', 'dragging' ];
ToolManager.prototype.registerTool = function(name, events) {
var tools = this._tools;
if (!events) {
throw new Error('A tool has to be registered with it\'s "events"');
}
tools.push(name);
this.bindEvents(name, events);
};
ToolManager.prototype.isActive = function(tool) {
return tool && this._active === tool;
};
ToolManager.prototype.length = function(tool) {
return this._tools.length;
};
ToolManager.prototype.setActive = function(tool) {
var eventBus = this._eventBus;
if (this._active !== tool) {
this._active = tool;
eventBus.fire('tool-manager.update', { tool: tool });
}
};
ToolManager.prototype.bindEvents = function(name, events) {
var eventBus = this._eventBus,
dragging = this._dragging;
var eventsToRegister = [];
eventBus.on(events.tool + '.init', function(event) {
var context = event.context;
// Active tools that want to reactivate themselves must do this explicitly
if (!context.reactivate && this.isActive(name)) {
this.setActive(null);
dragging.cancel();
return;
}
this.setActive(name);
}, this);
// Todo[ricardo]: add test cases
forEach$1(events, function(event) {
eventsToRegister.push(event + '.ended');
eventsToRegister.push(event + '.canceled');
});
eventBus.on(eventsToRegister, LOW_PRIORITY$4, function(event) {
// We defer the de-activation of the tool to the .activate phase,
// so we're able to check if we want to toggle off the current
// active tool or switch to a new one
if (!this._active) {
return;
}
if (isPaletteClick(event)) {
return;
}
this.setActive(null);
}, this);
};
// helpers ///////////////
/**
* Check if a given event is a palette click event.
*
* @param {EventBus.Event} event
*
* @return {boolean}
*/
function isPaletteClick(event) {
var target = event.originalEvent && event.originalEvent.target;
return target && closest(target, '.group[data-group="tools"]');
}
var ToolManagerModule = {
__depends__: [
DraggingModule
],
__init__: [ 'toolManager' ],
toolManager: [ 'type', ToolManager ]
};
/**
* Return direction given axis and delta.
*
* @param {string} axis
* @param {number} delta
*
* @return {string}
*/
function getDirection(axis, delta) {
if (axis === 'x') {
if (delta > 0) {
return 'e';
}
if (delta < 0) {
return 'w';
}
}
if (axis === 'y') {
if (delta > 0) {
return 's';
}
if (delta < 0) {
return 'n';
}
}
return null;
}
/**
* Returns connections whose waypoints are to be updated. Waypoints are to be updated if start
* or end is to be moved or resized.
*
* @param {Array<djs.model.Shape} movingShapes
* @param {Array<djs.model.Shape} resizingShapes
*
* @returns {Array<djs.model.Connection>}
*/
function getWaypointsUpdatingConnections(movingShapes, resizingShapes) {
var waypointsUpdatingConnections = [];
forEach$1(movingShapes.concat(resizingShapes), function(shape) {
var incoming = shape.incoming,
outgoing = shape.outgoing;
forEach$1(incoming.concat(outgoing), function(connection) {
var source = connection.source,
target = connection.target;
if (includes$3(movingShapes, source) ||
includes$3(movingShapes, target) ||
includes$3(resizingShapes, source) ||
includes$3(resizingShapes, target)) {
if (!includes$3(waypointsUpdatingConnections, connection)) {
waypointsUpdatingConnections.push(connection);
}
}
});
});
return waypointsUpdatingConnections;
}
function includes$3(array, item) {
return array.indexOf(item) !== -1;
}
/**
* Resize bounds.
*
* @param {Object} bounds
* @param {number} bounds.x
* @param {number} bounds.y
* @param {number} bounds.width
* @param {number} bounds.height
* @param {string} direction
* @param {Object} delta
* @param {number} delta.x
* @param {number} delta.y
*
* @return {Object}
*/
function resizeBounds(bounds, direction, delta) {
var x = bounds.x,
y = bounds.y,
width = bounds.width,
height = bounds.height,
dx = delta.x,
dy = delta.y;
switch (direction) {
case 'n':
return {
x: x,
y: y + dy,
width: width,
height: height - dy
};
case 's':
return {
x: x,
y: y,
width: width,
height: height + dy
};
case 'w':
return {
x: x + dx,
y: y,
width: width - dx,
height: height
};
case 'e':
return {
x: x,
y: y,
width: width + dx,
height: height
};
default:
throw new Error('unknown direction: ' + direction);
}
}
var abs$1 = Math.abs,
round$4 = Math.round;
var AXIS_TO_DIMENSION = {
x: 'width',
y: 'height'
};
var CURSOR_CROSSHAIR = 'crosshair';
var DIRECTION_TO_TRBL = {
n: 'top',
w: 'left',
s: 'bottom',
e: 'right'
};
var HIGH_PRIORITY$3 = 1500;
var DIRECTION_TO_OPPOSITE = {
n: 's',
w: 'e',
s: 'n',
e: 'w'
};
var PADDING = 20;
/**
* Add or remove space by moving and resizing elements.
*
* @param {Canvas} canvas
* @param {Dragging} dragging
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {Rules} rules
* @param {ToolManager} toolManager
* @param {Mouse} mouse
*/
function SpaceTool(
canvas, dragging, eventBus,
modeling, rules, toolManager,
mouse) {
this._canvas = canvas;
this._dragging = dragging;
this._eventBus = eventBus;
this._modeling = modeling;
this._rules = rules;
this._toolManager = toolManager;
this._mouse = mouse;
var self = this;
toolManager.registerTool('space', {
tool: 'spaceTool.selection',
dragging: 'spaceTool'
});
eventBus.on('spaceTool.selection.end', function(event) {
eventBus.once('spaceTool.selection.ended', function() {
self.activateMakeSpace(event.originalEvent);
});
});
eventBus.on('spaceTool.move', HIGH_PRIORITY$3 , function(event) {
var context = event.context,
initialized = context.initialized;
if (!initialized) {
initialized = context.initialized = self.init(event, context);
}
if (initialized) {
ensureConstraints(event);
}
});
eventBus.on('spaceTool.end', function(event) {
var context = event.context,
axis = context.axis,
direction = context.direction,
movingShapes = context.movingShapes,
resizingShapes = context.resizingShapes,
start = context.start;
if (!context.initialized) {
return;
}
ensureConstraints(event);
var delta = {
x: 0,
y: 0
};
delta[ axis ] = round$4(event[ 'd' + axis ]);
self.makeSpace(movingShapes, resizingShapes, delta, direction, start);
eventBus.once('spaceTool.ended', function(event) {
// activate space tool selection after make space
self.activateSelection(event.originalEvent, true, true);
});
});
}
SpaceTool.$inject = [
'canvas',
'dragging',
'eventBus',
'modeling',
'rules',
'toolManager',
'mouse'
];
/**
* Activate space tool selection.
*
* @param {Object} event
* @param {boolean} autoActivate
*/
SpaceTool.prototype.activateSelection = function(event, autoActivate, reactivate) {
this._dragging.init(event, 'spaceTool.selection', {
autoActivate: autoActivate,
cursor: CURSOR_CROSSHAIR,
data: {
context: {
reactivate: reactivate
}
},
trapClick: false
});
};
/**
* Activate space tool make space.
*
* @param {MouseEvent} event
*/
SpaceTool.prototype.activateMakeSpace = function(event) {
this._dragging.init(event, 'spaceTool', {
autoActivate: true,
cursor: CURSOR_CROSSHAIR,
data: {
context: {}
}
});
};
/**
* Make space.
*
* @param {Array<djs.model.Shape>} movingShapes
* @param {Array<djs.model.Shape>} resizingShapes
* @param {Object} delta
* @param {number} delta.x
* @param {number} delta.y
* @param {string} direction
* @param {number} start
*/
SpaceTool.prototype.makeSpace = function(movingShapes, resizingShapes, delta, direction, start) {
return this._modeling.createSpace(movingShapes, resizingShapes, delta, direction, start);
};
/**
* Initialize make space and return true if that was successful.
*
* @param {Object} event
* @param {Object} context
*
* @return {boolean}
*/
SpaceTool.prototype.init = function(event, context) {
var axis = abs$1(event.dx) > abs$1(event.dy) ? 'x' : 'y',
delta = event[ 'd' + axis ],
start = event[ axis ] - delta;
if (abs$1(delta) < 5) {
return false;
}
// invert delta to remove space when moving left
if (delta < 0) {
delta *= -1;
}
// invert delta to add/remove space when removing/adding space if modifier key is pressed
if (hasPrimaryModifier(event)) {
delta *= -1;
}
var direction = getDirection(axis, delta);
var root = this._canvas.getRootElement();
var children = selfAndAllChildren(root, true);
var elements = this.calculateAdjustments(children, axis, delta, start);
var minDimensions = this._eventBus.fire('spaceTool.getMinDimensions', {
axis: axis,
direction: direction,
shapes: elements.resizingShapes,
start: start
});
var spaceToolConstraints = getSpaceToolConstraints(elements, axis, direction, start, minDimensions);
assign(
context,
elements,
{
axis: axis,
direction: direction,
spaceToolConstraints: spaceToolConstraints,
start: start
}
);
set('resize-' + (axis === 'x' ? 'ew' : 'ns'));
return true;
};
/**
* Get elements to be moved and resized.
*
* @param {Array<djs.model.Shape>} elements
* @param {string} axis
* @param {number} delta
* @param {number} start
*
* @return {Object}
*/
SpaceTool.prototype.calculateAdjustments = function(elements, axis, delta, start) {
var rules = this._rules;
var movingShapes = [],
resizingShapes = [];
forEach$1(elements, function(element) {
if (!element.parent || isConnection$7(element)) {
return;
}
var shapeStart = element[ axis ],
shapeEnd = shapeStart + element[ AXIS_TO_DIMENSION[ axis ] ];
// shape to be moved
if ((delta > 0 && shapeStart > start) || (delta < 0 && shapeEnd < start)) {
return movingShapes.push(element);
}
// shape to be resized
if (shapeStart < start &&
shapeEnd > start &&
rules.allowed('shape.resize', { shape: element })
) {
return resizingShapes.push(element);
}
});
return {
movingShapes: movingShapes,
resizingShapes: resizingShapes
};
};
SpaceTool.prototype.toggle = function() {
if (this.isActive()) {
return this._dragging.cancel();
}
var mouseEvent = this._mouse.getLastMoveEvent();
this.activateSelection(mouseEvent, !!mouseEvent);
};
SpaceTool.prototype.isActive = function() {
var context = this._dragging.context();
return context && /^spaceTool/.test(context.prefix);
};
// helpers //////////
function addPadding(trbl) {
return {
top: trbl.top - PADDING,
right: trbl.right + PADDING,
bottom: trbl.bottom + PADDING,
left: trbl.left - PADDING
};
}
function ensureConstraints(event) {
var context = event.context,
spaceToolConstraints = context.spaceToolConstraints;
if (!spaceToolConstraints) {
return;
}
var x, y;
if (isNumber(spaceToolConstraints.left)) {
x = Math.max(event.x, spaceToolConstraints.left);
event.dx = event.dx + x - event.x;
event.x = x;
}
if (isNumber(spaceToolConstraints.right)) {
x = Math.min(event.x, spaceToolConstraints.right);
event.dx = event.dx + x - event.x;
event.x = x;
}
if (isNumber(spaceToolConstraints.top)) {
y = Math.max(event.y, spaceToolConstraints.top);
event.dy = event.dy + y - event.y;
event.y = y;
}
if (isNumber(spaceToolConstraints.bottom)) {
y = Math.min(event.y, spaceToolConstraints.bottom);
event.dy = event.dy + y - event.y;
event.y = y;
}
}
function getSpaceToolConstraints(elements, axis, direction, start, minDimensions) {
var movingShapes = elements.movingShapes,
resizingShapes = elements.resizingShapes;
if (!resizingShapes.length) {
return;
}
var spaceToolConstraints = {},
min,
max;
forEach$1(resizingShapes, function(resizingShape) {
var resizingShapeBBox = asTRBL(resizingShape);
// find children that are not moving or resizing
var nonMovingResizingChildren = filter(resizingShape.children, function(child) {
return !isConnection$7(child) &&
!isLabel$2(child) &&
!includes$2(movingShapes, child) &&
!includes$2(resizingShapes, child);
});
// find children that are moving
var movingChildren = filter(resizingShape.children, function(child) {
return !isConnection$7(child) && !isLabel$2(child) && includes$2(movingShapes, child);
});
var minOrMax,
nonMovingResizingChildrenBBox,
movingChildrenBBox;
if (nonMovingResizingChildren.length) {
nonMovingResizingChildrenBBox = addPadding(asTRBL(getBBox(nonMovingResizingChildren)));
minOrMax = start -
resizingShapeBBox[ DIRECTION_TO_TRBL[ direction ] ] +
nonMovingResizingChildrenBBox[ DIRECTION_TO_TRBL[ direction ] ];
if (direction === 'n') {
spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
} else if (direction === 'w') {
spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
} else if (direction === 's') {
spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
} else if (direction === 'e') {
spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
}
}
if (movingChildren.length) {
movingChildrenBBox = addPadding(asTRBL(getBBox(movingChildren)));
minOrMax = start -
movingChildrenBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ] +
resizingShapeBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ];
if (direction === 'n') {
spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
} else if (direction === 'w') {
spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
} else if (direction === 's') {
spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
} else if (direction === 'e') {
spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
}
}
var resizingShapeMinDimensions = minDimensions && minDimensions[ resizingShape.id ];
if (resizingShapeMinDimensions) {
if (direction === 'n') {
minOrMax = start +
resizingShape[ AXIS_TO_DIMENSION [ axis ] ] -
resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
} else if (direction === 'w') {
minOrMax = start +
resizingShape[ AXIS_TO_DIMENSION [ axis ] ] -
resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax;
} else if (direction === 's') {
minOrMax = start -
resizingShape[ AXIS_TO_DIMENSION [ axis ] ] +
resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
} else if (direction === 'e') {
minOrMax = start -
resizingShape[ AXIS_TO_DIMENSION [ axis ] ] +
resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ];
spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax;
}
}
});
return spaceToolConstraints;
}
function includes$2(array, item) {
return array.indexOf(item) !== -1;
}
function isConnection$7(element) {
return !!element.waypoints;
}
function isLabel$2(element) {
return !!element.labelTarget;
}
var MARKER_DRAGGING$1 = 'djs-dragging',
MARKER_RESIZING = 'djs-resizing';
var LOW_PRIORITY$3 = 250;
var max = Math.max;
/**
* Provides previews for selecting/moving/resizing shapes when creating/removing space.
*
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
* @param {Canvas} canvas
* @param {Styles} styles
*/
function SpaceToolPreview(
eventBus, elementRegistry, canvas,
styles, previewSupport) {
function addPreviewGfx(collection, dragGroup) {
forEach$1(collection, function(element) {
previewSupport.addDragger(element, dragGroup);
canvas.addMarker(element, MARKER_DRAGGING$1);
});
}
// add crosshair
eventBus.on('spaceTool.selection.start', function(event) {
var space = canvas.getLayer('space'),
context = event.context;
var orientation = {
x: 'M 0,-10000 L 0,10000',
y: 'M -10000,0 L 10000,0'
};
var crosshairGroup = create$1('g');
attr(crosshairGroup, styles.cls('djs-crosshair-group', [ 'no-events' ]));
append(space, crosshairGroup);
// horizontal path
var pathX = create$1('path');
attr(pathX, 'd', orientation.x);
classes(pathX).add('djs-crosshair');
append(crosshairGroup, pathX);
// vertical path
var pathY = create$1('path');
attr(pathY, 'd', orientation.y);
classes(pathY).add('djs-crosshair');
append(crosshairGroup, pathY);
context.crosshairGroup = crosshairGroup;
});
// update crosshair
eventBus.on('spaceTool.selection.move', function(event) {
var crosshairGroup = event.context.crosshairGroup;
translate$2(crosshairGroup, event.x, event.y);
});
// remove crosshair
eventBus.on('spaceTool.selection.cleanup', function(event) {
var context = event.context,
crosshairGroup = context.crosshairGroup;
if (crosshairGroup) {
remove$1(crosshairGroup);
}
});
// add and update move/resize previews
eventBus.on('spaceTool.move', LOW_PRIORITY$3, function(event) {
var context = event.context,
line = context.line,
axis = context.axis,
movingShapes = context.movingShapes,
resizingShapes = context.resizingShapes;
if (!context.initialized) {
return;
}
if (!context.dragGroup) {
var spaceLayer = canvas.getLayer('space');
line = create$1('path');
attr(line, 'd', 'M0,0 L0,0');
classes(line).add('djs-crosshair');
append(spaceLayer, line);
context.line = line;
var dragGroup = create$1('g');
attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));
append(canvas.getActiveLayer(), dragGroup);
// shapes
addPreviewGfx(movingShapes, dragGroup);
// connections
var movingConnections = context.movingConnections = elementRegistry.filter(function(element) {
var sourceIsMoving = false;
forEach$1(movingShapes, function(shape) {
forEach$1(shape.outgoing, function(connection) {
if (element === connection) {
sourceIsMoving = true;
}
});
});
var targetIsMoving = false;
forEach$1(movingShapes, function(shape) {
forEach$1(shape.incoming, function(connection) {
if (element === connection) {
targetIsMoving = true;
}
});
});
var sourceIsResizing = false;
forEach$1(resizingShapes, function(shape) {
forEach$1(shape.outgoing, function(connection) {
if (element === connection) {
sourceIsResizing = true;
}
});
});
var targetIsResizing = false;
forEach$1(resizingShapes, function(shape) {
forEach$1(shape.incoming, function(connection) {
if (element === connection) {
targetIsResizing = true;
}
});
});
return isConnection$6(element)
&& (sourceIsMoving || sourceIsResizing)
&& (targetIsMoving || targetIsResizing);
});
addPreviewGfx(movingConnections, dragGroup);
context.dragGroup = dragGroup;
}
if (!context.frameGroup) {
var frameGroup = create$1('g');
attr(frameGroup, styles.cls('djs-frame-group', [ 'no-events' ]));
append(canvas.getActiveLayer(), frameGroup);
var frames = [];
forEach$1(resizingShapes, function(shape) {
var frame = previewSupport.addFrame(shape, frameGroup);
var initialBounds = frame.getBBox();
frames.push({
element: frame,
initialBounds: initialBounds
});
canvas.addMarker(shape, MARKER_RESIZING);
});
context.frameGroup = frameGroup;
context.frames = frames;
}
var orientation = {
x: 'M' + event.x + ', -10000 L' + event.x + ', 10000',
y: 'M -10000, ' + event.y + ' L 10000, ' + event.y
};
attr(line, { d: orientation[ axis ] });
var opposite = { x: 'y', y: 'x' };
var delta = { x: event.dx, y: event.dy };
delta[ opposite[ context.axis ] ] = 0;
// update move previews
translate$2(context.dragGroup, delta.x, delta.y);
// update resize previews
forEach$1(context.frames, function(frame) {
var element = frame.element,
initialBounds = frame.initialBounds,
width,
height;
if (context.direction === 'e') {
attr(element, {
width: max(initialBounds.width + delta.x, 5)
});
} else {
width = max(initialBounds.width - delta.x, 5);
attr(element, {
width: width,
x: initialBounds.x + initialBounds.width - width
});
}
if (context.direction === 's') {
attr(element, {
height: max(initialBounds.height + delta.y, 5)
});
} else {
height = max(initialBounds.height - delta.y, 5);
attr(element, {
height: height,
y: initialBounds.y + initialBounds.height - height
});
}
});
});
// remove move/resize previews
eventBus.on('spaceTool.cleanup', function(event) {
var context = event.context,
movingShapes = context.movingShapes,
movingConnections = context.movingConnections,
resizingShapes = context.resizingShapes,
line = context.line,
dragGroup = context.dragGroup,
frameGroup = context.frameGroup;
// moving shapes
forEach$1(movingShapes, function(shape) {
canvas.removeMarker(shape, MARKER_DRAGGING$1);
});
// moving connections
forEach$1(movingConnections, function(connection) {
canvas.removeMarker(connection, MARKER_DRAGGING$1);
});
if (dragGroup) {
remove$1(line);
remove$1(dragGroup);
}
forEach$1(resizingShapes, function(shape) {
canvas.removeMarker(shape, MARKER_RESIZING);
});
if (frameGroup) {
remove$1(frameGroup);
}
});
}
SpaceToolPreview.$inject = [
'eventBus',
'elementRegistry',
'canvas',
'styles',
'previewSupport'
];
// helpers //////////////////////
/**
* Checks if an element is a connection.
*/
function isConnection$6(element) {
return element.waypoints;
}
var SpaceToolModule = {
__init__: [ 'spaceToolPreview' ],
__depends__: [
DraggingModule,
RulesModule$1,
ToolManagerModule,
PreviewSupportModule,
MouseModule
],
spaceTool: [ 'type', SpaceTool ],
spaceToolPreview: [ 'type', SpaceToolPreview ]
};
function BpmnFactory(moddle) {
this._model = moddle;
}
BpmnFactory.$inject = [ 'moddle' ];
BpmnFactory.prototype._needsId = function(element) {
return isAny(element, [
'bpmn:RootElement',
'bpmn:FlowElement',
'bpmn:MessageFlow',
'bpmn:DataAssociation',
'bpmn:Artifact',
'bpmn:Participant',
'bpmn:Lane',
'bpmn:LaneSet',
'bpmn:Process',
'bpmn:Collaboration',
'bpmndi:BPMNShape',
'bpmndi:BPMNEdge',
'bpmndi:BPMNDiagram',
'bpmndi:BPMNPlane',
'bpmn:Property',
'bpmn:CategoryValue'
]);
};
BpmnFactory.prototype._ensureId = function(element) {
if (element.id) {
this._model.ids.claim(element.id, element);
return;
}
// generate semantic ids for elements
// bpmn:SequenceFlow -> SequenceFlow_ID
var prefix;
if (is$1(element, 'bpmn:Activity')) {
prefix = 'Activity';
} else if (is$1(element, 'bpmn:Event')) {
prefix = 'Event';
} else if (is$1(element, 'bpmn:Gateway')) {
prefix = 'Gateway';
} else if (isAny(element, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ])) {
prefix = 'Flow';
} else {
prefix = (element.$type || '').replace(/^[^:]*:/g, '');
}
prefix += '_';
if (!element.id && this._needsId(element)) {
element.id = this._model.ids.nextPrefixed(prefix, element);
}
};
BpmnFactory.prototype.create = function(type, attrs) {
var element = this._model.create(type, attrs || {});
this._ensureId(element);
return element;
};
BpmnFactory.prototype.createDiLabel = function() {
return this.create('bpmndi:BPMNLabel', {
bounds: this.createDiBounds()
});
};
BpmnFactory.prototype.createDiShape = function(semantic, attrs) {
return this.create('bpmndi:BPMNShape', assign({
bpmnElement: semantic,
bounds: this.createDiBounds()
}, attrs));
};
BpmnFactory.prototype.createDiBounds = function(bounds) {
return this.create('dc:Bounds', bounds);
};
BpmnFactory.prototype.createDiWaypoints = function(waypoints) {
var self = this;
return map(waypoints, function(pos) {
return self.createDiWaypoint(pos);
});
};
BpmnFactory.prototype.createDiWaypoint = function(point) {
return this.create('dc:Point', pick(point, [ 'x', 'y' ]));
};
BpmnFactory.prototype.createDiEdge = function(semantic, attrs) {
return this.create('bpmndi:BPMNEdge', assign({
bpmnElement: semantic,
waypoint: this.createDiWaypoints([])
}, attrs));
};
BpmnFactory.prototype.createDiPlane = function(semantic, attrs) {
return this.create('bpmndi:BPMNPlane', assign({
bpmnElement: semantic
}, attrs));
};
/**
* A handler responsible for updating the underlying BPMN 2.0 XML + DI
* once changes on the diagram happen
*/
function BpmnUpdater(
eventBus, bpmnFactory, connectionDocking,
translate) {
CommandInterceptor.call(this, eventBus);
this._bpmnFactory = bpmnFactory;
this._translate = translate;
var self = this;
// connection cropping //////////////////////
// crop connection ends during create/update
function cropConnection(e) {
var context = e.context,
hints = context.hints || {},
connection;
if (!context.cropped && hints.createElementsBehavior !== false) {
connection = context.connection;
connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
context.cropped = true;
}
}
this.executed([
'connection.layout',
'connection.create'
], cropConnection);
this.reverted([ 'connection.layout' ], function(e) {
delete e.context.cropped;
});
// BPMN + DI update //////////////////////
// update parent
function updateParent(e) {
var context = e.context;
self.updateParent(context.shape || context.connection, context.oldParent);
}
function reverseUpdateParent(e) {
var context = e.context;
var element = context.shape || context.connection,
// oldParent is the (old) new parent, because we are undoing
oldParent = context.parent || context.newParent;
self.updateParent(element, oldParent);
}
this.executed([
'shape.move',
'shape.create',
'shape.delete',
'connection.create',
'connection.move',
'connection.delete'
], ifBpmn(updateParent));
this.reverted([
'shape.move',
'shape.create',
'shape.delete',
'connection.create',
'connection.move',
'connection.delete'
], ifBpmn(reverseUpdateParent));
/*
* ## Updating Parent
*
* When morphing a Process into a Collaboration or vice-versa,
* make sure that both the *semantic* and *di* parent of each element
* is updated.
*
*/
function updateRoot(event) {
var context = event.context,
oldRoot = context.oldRoot,
children = oldRoot.children;
forEach$1(children, function(child) {
if (is$1(child, 'bpmn:BaseElement')) {
self.updateParent(child);
}
});
}
this.executed([ 'canvas.updateRoot' ], updateRoot);
this.reverted([ 'canvas.updateRoot' ], updateRoot);
// update bounds
function updateBounds(e) {
var shape = e.context.shape;
if (!is$1(shape, 'bpmn:BaseElement')) {
return;
}
self.updateBounds(shape);
}
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
// exclude labels because they're handled separately during shape.changed
if (event.context.shape.type === 'label') {
return;
}
updateBounds(event);
}));
this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
// exclude labels because they're handled separately during shape.changed
if (event.context.shape.type === 'label') {
return;
}
updateBounds(event);
}));
// Handle labels separately. This is necessary, because the label bounds have to be updated
// every time its shape changes, not only on move, create and resize.
eventBus.on('shape.changed', function(event) {
if (event.element.type === 'label') {
updateBounds({ context: { shape: event.element } });
}
});
// attach / detach connection
function updateConnection(e) {
self.updateConnection(e.context);
}
this.executed([
'connection.create',
'connection.move',
'connection.delete',
'connection.reconnect'
], ifBpmn(updateConnection));
this.reverted([
'connection.create',
'connection.move',
'connection.delete',
'connection.reconnect'
], ifBpmn(updateConnection));
// update waypoints
function updateConnectionWaypoints(e) {
self.updateConnectionWaypoints(e.context.connection);
}
this.executed([
'connection.layout',
'connection.move',
'connection.updateWaypoints',
], ifBpmn(updateConnectionWaypoints));
this.reverted([
'connection.layout',
'connection.move',
'connection.updateWaypoints',
], ifBpmn(updateConnectionWaypoints));
// update conditional/default flows
this.executed('connection.reconnect', ifBpmn(function(event) {
var context = event.context,
connection = context.connection,
oldSource = context.oldSource,
newSource = context.newSource,
connectionBo = getBusinessObject(connection),
oldSourceBo = getBusinessObject(oldSource),
newSourceBo = getBusinessObject(newSource);
// remove condition from connection on reconnect to new source
// if new source can NOT have condional sequence flow
if (connectionBo.conditionExpression && !isAny(newSourceBo, [
'bpmn:Activity',
'bpmn:ExclusiveGateway',
'bpmn:InclusiveGateway'
])) {
context.oldConditionExpression = connectionBo.conditionExpression;
delete connectionBo.conditionExpression;
}
// remove default from old source flow on reconnect to new source
// if source changed
if (oldSource !== newSource && oldSourceBo.default === connectionBo) {
context.oldDefault = oldSourceBo.default;
delete oldSourceBo.default;
}
}));
this.reverted('connection.reconnect', ifBpmn(function(event) {
var context = event.context,
connection = context.connection,
oldSource = context.oldSource,
newSource = context.newSource,
connectionBo = getBusinessObject(connection),
oldSourceBo = getBusinessObject(oldSource),
newSourceBo = getBusinessObject(newSource);
// add condition to connection on revert reconnect to new source
if (context.oldConditionExpression) {
connectionBo.conditionExpression = context.oldConditionExpression;
}
// add default to old source on revert reconnect to new source
if (context.oldDefault) {
oldSourceBo.default = context.oldDefault;
delete newSourceBo.default;
}
}));
// update attachments
function updateAttachment(e) {
self.updateAttachment(e.context);
}
this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
}
e(BpmnUpdater, CommandInterceptor);
BpmnUpdater.$inject = [
'eventBus',
'bpmnFactory',
'connectionDocking',
'translate'
];
// implementation //////////////////////
BpmnUpdater.prototype.updateAttachment = function(context) {
var shape = context.shape,
businessObject = shape.businessObject,
host = shape.host;
businessObject.attachedToRef = host && host.businessObject;
};
BpmnUpdater.prototype.updateParent = function(element, oldParent) {
// do not update BPMN 2.0 label parent
if (element instanceof Label) {
return;
}
// data stores in collaborations are handled separately by DataStoreBehavior
if (is$1(element, 'bpmn:DataStoreReference') &&
element.parent &&
is$1(element.parent, 'bpmn:Collaboration')) {
return;
}
var parentShape = element.parent;
var businessObject = element.businessObject,
di = getDi(element),
parentBusinessObject = parentShape && parentShape.businessObject,
parentDi = getDi(parentShape);
if (is$1(element, 'bpmn:FlowNode')) {
this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
}
if (is$1(element, 'bpmn:DataOutputAssociation')) {
if (element.source) {
parentBusinessObject = element.source.businessObject;
} else {
parentBusinessObject = null;
}
}
if (is$1(element, 'bpmn:DataInputAssociation')) {
if (element.target) {
parentBusinessObject = element.target.businessObject;
} else {
parentBusinessObject = null;
}
}
this.updateSemanticParent(businessObject, parentBusinessObject);
if (is$1(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
}
this.updateDiParent(di, parentDi);
};
BpmnUpdater.prototype.updateBounds = function(shape) {
var di = getDi(shape),
embeddedLabelBounds = getEmbeddedLabelBounds(shape);
// update embedded label bounds if possible
if (embeddedLabelBounds) {
var embeddedLabelBoundsDelta = delta(embeddedLabelBounds, di.get('bounds'));
assign(embeddedLabelBounds, {
x: shape.x + embeddedLabelBoundsDelta.x,
y: shape.y + embeddedLabelBoundsDelta.y
});
}
var target = (shape instanceof Label) ? this._getLabel(di) : di;
var bounds = target.bounds;
if (!bounds) {
bounds = this._bpmnFactory.createDiBounds();
target.set('bounds', bounds);
}
assign(bounds, {
x: shape.x,
y: shape.y,
width: shape.width,
height: shape.height
});
};
BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {
if (oldContainment === newContainment) {
return;
}
var oldRefs, newRefs;
if (is$1 (oldContainment, 'bpmn:Lane')) {
oldRefs = oldContainment.get('flowNodeRef');
remove(oldRefs, businessObject);
}
if (is$1(newContainment, 'bpmn:Lane')) {
newRefs = newContainment.get('flowNodeRef');
add(newRefs, businessObject);
}
};
// update existing sourceElement and targetElement di information
BpmnUpdater.prototype.updateDiConnection = function(connection, newSource, newTarget) {
var connectionDi = getDi(connection),
newSourceDi = getDi(newSource),
newTargetDi = getDi(newTarget);
if (connectionDi.sourceElement && connectionDi.sourceElement.bpmnElement !== getBusinessObject(newSource)) {
connectionDi.sourceElement = newSource && newSourceDi;
}
if (connectionDi.targetElement && connectionDi.targetElement.bpmnElement !== getBusinessObject(newTarget)) {
connectionDi.targetElement = newTarget && newTargetDi;
}
};
BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {
if (parentDi && !is$1(parentDi, 'bpmndi:BPMNPlane')) {
parentDi = parentDi.$parent;
}
if (di.$parent === parentDi) {
return;
}
var planeElements = (parentDi || di.$parent).get('planeElement');
if (parentDi) {
planeElements.push(di);
di.$parent = parentDi;
} else {
remove(planeElements, di);
di.$parent = null;
}
};
function getDefinitions(element) {
while (element && !is$1(element, 'bpmn:Definitions')) {
element = element.$parent;
}
return element;
}
BpmnUpdater.prototype.getLaneSet = function(container) {
var laneSet, laneSets;
// bpmn:Lane
if (is$1(container, 'bpmn:Lane')) {
laneSet = container.childLaneSet;
if (!laneSet) {
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
container.childLaneSet = laneSet;
laneSet.$parent = container;
}
return laneSet;
}
// bpmn:Participant
if (is$1(container, 'bpmn:Participant')) {
container = container.processRef;
}
// bpmn:FlowElementsContainer
laneSets = container.get('laneSets');
laneSet = laneSets[0];
if (!laneSet) {
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
laneSet.$parent = container;
laneSets.push(laneSet);
}
return laneSet;
};
BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {
var containment,
translate = this._translate;
if (businessObject.$parent === newParent) {
return;
}
if (is$1(businessObject, 'bpmn:DataInput') || is$1(businessObject, 'bpmn:DataOutput')) {
if (is$1(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
newParent = newParent.processRef;
}
// already in correct ioSpecification
if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
return;
}
}
if (is$1(businessObject, 'bpmn:Lane')) {
if (newParent) {
newParent = this.getLaneSet(newParent);
}
containment = 'lanes';
} else
if (is$1(businessObject, 'bpmn:FlowElement')) {
if (newParent) {
if (is$1(newParent, 'bpmn:Participant')) {
newParent = newParent.processRef;
} else
if (is$1(newParent, 'bpmn:Lane')) {
do {
// unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
newParent = newParent.$parent.$parent;
} while (is$1(newParent, 'bpmn:Lane'));
}
}
containment = 'flowElements';
} else
if (is$1(businessObject, 'bpmn:Artifact')) {
while (newParent &&
!is$1(newParent, 'bpmn:Process') &&
!is$1(newParent, 'bpmn:SubProcess') &&
!is$1(newParent, 'bpmn:Collaboration')) {
if (is$1(newParent, 'bpmn:Participant')) {
newParent = newParent.processRef;
break;
} else {
newParent = newParent.$parent;
}
}
containment = 'artifacts';
} else
if (is$1(businessObject, 'bpmn:MessageFlow')) {
containment = 'messageFlows';
} else
if (is$1(businessObject, 'bpmn:Participant')) {
containment = 'participants';
// make sure the participants process is properly attached / detached
// from the XML document
var process = businessObject.processRef,
definitions;
if (process) {
definitions = getDefinitions(businessObject.$parent || newParent);
if (businessObject.$parent) {
remove(definitions.get('rootElements'), process);
process.$parent = null;
}
if (newParent) {
add(definitions.get('rootElements'), process);
process.$parent = definitions;
}
}
} else
if (is$1(businessObject, 'bpmn:DataOutputAssociation')) {
containment = 'dataOutputAssociations';
} else
if (is$1(businessObject, 'bpmn:DataInputAssociation')) {
containment = 'dataInputAssociations';
}
if (!containment) {
throw new Error(translate(
'no parent for {element} in {parent}',
{
element: businessObject.id,
parent: newParent.id
}
));
}
var children;
if (businessObject.$parent) {
// remove from old parent
children = businessObject.$parent.get(containment);
remove(children, businessObject);
}
if (!newParent) {
businessObject.$parent = null;
} else {
// add to new parent
children = newParent.get(containment);
children.push(businessObject);
businessObject.$parent = newParent;
}
if (visualParent) {
var diChildren = visualParent.get(containment);
remove(children, businessObject);
if (newParent) {
if (!diChildren) {
diChildren = [];
newParent.set(containment, diChildren);
}
diChildren.push(businessObject);
}
}
};
BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
var di = getDi(connection);
di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
};
BpmnUpdater.prototype.updateConnection = function(context) {
var connection = context.connection,
businessObject = getBusinessObject(connection),
newSource = connection.source,
newSourceBo = getBusinessObject(newSource),
newTarget = connection.target,
newTargetBo = getBusinessObject(connection.target),
visualParent;
if (!is$1(businessObject, 'bpmn:DataAssociation')) {
var inverseSet = is$1(businessObject, 'bpmn:SequenceFlow');
if (businessObject.sourceRef !== newSourceBo) {
if (inverseSet) {
remove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject);
if (newSourceBo && newSourceBo.get('outgoing')) {
newSourceBo.get('outgoing').push(businessObject);
}
}
businessObject.sourceRef = newSourceBo;
}
if (businessObject.targetRef !== newTargetBo) {
if (inverseSet) {
remove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject);
if (newTargetBo && newTargetBo.get('incoming')) {
newTargetBo.get('incoming').push(businessObject);
}
}
businessObject.targetRef = newTargetBo;
}
} else
if (is$1(businessObject, 'bpmn:DataInputAssociation')) {
// handle obnoxious isMsome sourceRef
businessObject.get('sourceRef')[0] = newSourceBo;
visualParent = context.parent || context.newParent || newTargetBo;
this.updateSemanticParent(businessObject, newTargetBo, visualParent);
} else
if (is$1(businessObject, 'bpmn:DataOutputAssociation')) {
visualParent = context.parent || context.newParent || newSourceBo;
this.updateSemanticParent(businessObject, newSourceBo, visualParent);
// targetRef = new target
businessObject.targetRef = newTargetBo;
}
this.updateConnectionWaypoints(connection);
this.updateDiConnection(connection, newSource, newTarget);
};
// helpers //////////////////////
BpmnUpdater.prototype._getLabel = function(di) {
if (!di.label) {
di.label = this._bpmnFactory.createDiLabel();
}
return di.label;
};
/**
* Make sure the event listener is only called
* if the touched element is a BPMN element.
*
* @param {Function} fn
* @return {Function} guarded function
*/
function ifBpmn(fn) {
return function(event) {
var context = event.context,
element = context.shape || context.connection;
if (is$1(element, 'bpmn:BaseElement')) {
fn(event);
}
};
}
/**
* Return dc:Bounds of bpmndi:BPMNLabel if exists.
*
* @param {djs.model.shape} shape
*
* @returns {Object|undefined}
*/
function getEmbeddedLabelBounds(shape) {
if (!is$1(shape, 'bpmn:Activity')) {
return;
}
var di = getDi(shape);
if (!di) {
return;
}
var label = di.get('label');
if (!label) {
return;
}
return label.get('bounds');
}
/**
* A bpmn-aware factory for diagram-js shapes
*/
function ElementFactory(bpmnFactory, moddle, translate) {
ElementFactory$1.call(this);
this._bpmnFactory = bpmnFactory;
this._moddle = moddle;
this._translate = translate;
}
e(ElementFactory, ElementFactory$1);
ElementFactory.$inject = [
'bpmnFactory',
'moddle',
'translate'
];
ElementFactory.prototype.baseCreate = ElementFactory$1.prototype.create;
ElementFactory.prototype.create = function(elementType, attrs) {
// no special magic for labels,
// we assume their businessObjects have already been created
// and wired via attrs
if (elementType === 'label') {
var di = attrs.di || this._bpmnFactory.createDiLabel();
return this.baseCreate(elementType, assign({ type: 'label', di: di }, DEFAULT_LABEL_SIZE, attrs));
}
return this.createBpmnElement(elementType, attrs);
};
ElementFactory.prototype.createBpmnElement = function(elementType, attrs) {
var size,
translate = this._translate;
attrs = assign({}, attrs || {});
var businessObject = attrs.businessObject,
di = attrs.di;
if (!businessObject) {
if (!attrs.type) {
throw new Error(translate('no shape type specified'));
}
businessObject = this._bpmnFactory.create(attrs.type);
ensureCompatDiRef(businessObject);
}
if (!isModdleDi(di)) {
var diAttrs = assign(
{},
di || {},
{ id: businessObject.id + '_di' }
);
if (elementType === 'root') {
di = this._bpmnFactory.createDiPlane(businessObject, diAttrs);
} else
if (elementType === 'connection') {
di = this._bpmnFactory.createDiEdge(businessObject, diAttrs);
} else {
di = this._bpmnFactory.createDiShape(businessObject, diAttrs);
}
}
if (is$1(businessObject, 'bpmn:Group')) {
attrs = assign({
isFrame: true
}, attrs);
}
attrs = applyAttributes(businessObject, attrs, [
'processRef',
'isInterrupting',
'associationDirection',
'isForCompensation'
]);
if (attrs.isExpanded) {
attrs = applyAttribute(di, attrs, 'isExpanded');
}
if (is$1(businessObject, 'bpmn:SubProcess')) {
attrs.collapsed = !isExpanded(businessObject, di);
}
if (is$1(businessObject, 'bpmn:ExclusiveGateway')) {
di.isMarkerVisible = true;
}
var eventDefinitions,
newEventDefinition;
if (attrs.eventDefinitionType) {
eventDefinitions = businessObject.get('eventDefinitions') || [];
newEventDefinition = this._bpmnFactory.create(attrs.eventDefinitionType, attrs.eventDefinitionAttrs);
if (attrs.eventDefinitionType === 'bpmn:ConditionalEventDefinition') {
newEventDefinition.condition = this._bpmnFactory.create('bpmn:FormalExpression');
}
eventDefinitions.push(newEventDefinition);
newEventDefinition.$parent = businessObject;
businessObject.eventDefinitions = eventDefinitions;
delete attrs.eventDefinitionType;
}
size = this.getDefaultSize(businessObject, di);
attrs = assign({
id: businessObject.id
}, size, attrs, {
businessObject: businessObject,
di: di
});
return this.baseCreate(elementType, attrs);
};
ElementFactory.prototype.getDefaultSize = function(element, di) {
var bo = getBusinessObject(element);
di = di || getDi(element);
if (is$1(bo, 'bpmn:SubProcess')) {
if (isExpanded(bo, di)) {
return { width: 350, height: 200 };
} else {
return { width: 100, height: 80 };
}
}
if (is$1(bo, 'bpmn:Task')) {
return { width: 100, height: 80 };
}
if (is$1(bo, 'bpmn:Gateway')) {
return { width: 50, height: 50 };
}
if (is$1(bo, 'bpmn:Event')) {
return { width: 36, height: 36 };
}
if (is$1(bo, 'bpmn:Participant')) {
if (isExpanded(bo, di)) {
return { width: 600, height: 250 };
} else {
return { width: 400, height: 60 };
}
}
if (is$1(bo, 'bpmn:Lane')) {
return { width: 400, height: 100 };
}
if (is$1(bo, 'bpmn:DataObjectReference')) {
return { width: 36, height: 50 };
}
if (is$1(bo, 'bpmn:DataStoreReference')) {
return { width: 50, height: 50 };
}
if (is$1(bo, 'bpmn:TextAnnotation')) {
return { width: 100, height: 30 };
}
if (is$1(bo, 'bpmn:Group')) {
return { width: 300, height: 300 };
}
return { width: 100, height: 80 };
};
/**
* Create participant.
*
* @param {boolean|Object} [attrs] attrs
*
* @returns {djs.model.Shape}
*/
ElementFactory.prototype.createParticipantShape = function(attrs) {
if (!isObject(attrs)) {
attrs = { isExpanded: attrs };
}
attrs = assign({ type: 'bpmn:Participant' }, attrs || {});
// participants are expanded by default
if (attrs.isExpanded !== false) {
attrs.processRef = this._bpmnFactory.create('bpmn:Process');
}
return this.createShape(attrs);
};
// helpers //////////////////////
/**
* Apply attributes from a map to the given element,
* remove attribute from the map on application.
*
* @param {Base} element
* @param {Object} attrs (in/out map of attributes)
* @param {Array<string>} attributeNames name of attributes to apply
*
* @return {Object} changed attrs
*/
function applyAttributes(element, attrs, attributeNames) {
forEach$1(attributeNames, function(property) {
attrs = applyAttribute(element, attrs, property);
});
return attrs;
}
/**
* Apply named property to element and drain it from the attrs
* collection.
*
* @param {Base} element
* @param {Object} attrs (in/out map of attributes)
* @param {string} attributeName to apply
*
* @return {Object} changed attrs
*/
function applyAttribute(element, attrs, attributeName) {
if (attrs[attributeName] === undefined) {
return attrs;
}
element[attributeName] = attrs[attributeName];
return omit(attrs, [ attributeName ]);
}
function isModdleDi(element) {
return isAny(element, [
'bpmndi:BPMNShape',
'bpmndi:BPMNEdge',
'bpmndi:BPMNDiagram',
'bpmndi:BPMNPlane',
]);
}
/**
* A handler that align elements in a certain way.
*
*/
function AlignElements(modeling, canvas) {
this._modeling = modeling;
this._canvas = canvas;
}
AlignElements.$inject = [ 'modeling', 'canvas' ];
AlignElements.prototype.preExecute = function(context) {
var modeling = this._modeling;
var elements = context.elements,
alignment = context.alignment;
forEach$1(elements, function(element) {
var delta = {
x: 0,
y: 0
};
if (alignment.left) {
delta.x = alignment.left - element.x;
} else if (alignment.right) {
delta.x = (alignment.right - element.width) - element.x;
} else if (alignment.center) {
delta.x = (alignment.center - Math.round(element.width / 2)) - element.x;
} else if (alignment.top) {
delta.y = alignment.top - element.y;
} else if (alignment.bottom) {
delta.y = (alignment.bottom - element.height) - element.y;
} else if (alignment.middle) {
delta.y = (alignment.middle - Math.round(element.height / 2)) - element.y;
}
modeling.moveElements([ element ], delta, element.parent);
});
};
AlignElements.prototype.postExecute = function(context) {
};
/**
* A handler that implements reversible appending of shapes
* to a source shape.
*
* @param {canvas} Canvas
* @param {elementFactory} ElementFactory
* @param {modeling} Modeling
*/
function AppendShapeHandler(modeling) {
this._modeling = modeling;
}
AppendShapeHandler.$inject = [ 'modeling' ];
// api //////////////////////
/**
* Creates a new shape
*
* @param {Object} context
* @param {ElementDescriptor} context.shape the new shape
* @param {ElementDescriptor} context.source the source object
* @param {ElementDescriptor} context.parent the parent object
* @param {Point} context.position position of the new element
*/
AppendShapeHandler.prototype.preExecute = function(context) {
var source = context.source;
if (!source) {
throw new Error('source required');
}
var target = context.target || source.parent,
shape = context.shape,
hints = context.hints || {};
shape = context.shape =
this._modeling.createShape(
shape,
context.position,
target, { attach: hints.attach });
context.shape = shape;
};
AppendShapeHandler.prototype.postExecute = function(context) {
var hints = context.hints || {};
if (!existsConnection(context.source, context.shape)) {
// create connection
if (hints.connectionTarget === context.source) {
this._modeling.connect(context.shape, context.source, context.connection);
} else {
this._modeling.connect(context.source, context.shape, context.connection);
}
}
};
function existsConnection(source, target) {
return some(source.outgoing, function(c) {
return c.target === target;
});
}
function CreateConnectionHandler(canvas, layouter) {
this._canvas = canvas;
this._layouter = layouter;
}
CreateConnectionHandler.$inject = [ 'canvas', 'layouter' ];
// api //////////////////////
/**
* Appends a shape to a target shape
*
* @param {Object} context
* @param {djs.element.Base} context.source the source object
* @param {djs.element.Base} context.target the parent object
* @param {Point} context.position position of the new element
*/
CreateConnectionHandler.prototype.execute = function(context) {
var connection = context.connection,
source = context.source,
target = context.target,
parent = context.parent,
parentIndex = context.parentIndex,
hints = context.hints;
if (!source || !target) {
throw new Error('source and target required');
}
if (!parent) {
throw new Error('parent required');
}
connection.source = source;
connection.target = target;
if (!connection.waypoints) {
connection.waypoints = this._layouter.layoutConnection(connection, hints);
}
// add connection
this._canvas.addConnection(connection, parent, parentIndex);
return connection;
};
CreateConnectionHandler.prototype.revert = function(context) {
var connection = context.connection;
this._canvas.removeConnection(connection);
connection.source = null;
connection.target = null;
return connection;
};
var round$3 = Math.round;
function CreateElementsHandler(modeling) {
this._modeling = modeling;
}
CreateElementsHandler.$inject = [
'modeling'
];
CreateElementsHandler.prototype.preExecute = function(context) {
var elements = context.elements,
parent = context.parent,
parentIndex = context.parentIndex,
position = context.position,
hints = context.hints;
var modeling = this._modeling;
// make sure each element has x and y
forEach$1(elements, function(element) {
if (!isNumber(element.x)) {
element.x = 0;
}
if (!isNumber(element.y)) {
element.y = 0;
}
});
var visibleElements = filter(elements, function(element) {
return !element.hidden;
});
var bbox = getBBox(visibleElements);
// center elements around position
forEach$1(elements, function(element) {
if (isConnection$5(element)) {
element.waypoints = map(element.waypoints, function(waypoint) {
return {
x: round$3(waypoint.x - bbox.x - bbox.width / 2 + position.x),
y: round$3(waypoint.y - bbox.y - bbox.height / 2 + position.y)
};
});
}
assign(element, {
x: round$3(element.x - bbox.x - bbox.width / 2 + position.x),
y: round$3(element.y - bbox.y - bbox.height / 2 + position.y)
});
});
var parents = getParents$1(elements);
var cache = {};
forEach$1(elements, function(element) {
if (isConnection$5(element)) {
cache[ element.id ] = isNumber(parentIndex) ?
modeling.createConnection(
cache[ element.source.id ],
cache[ element.target.id ],
parentIndex,
element,
element.parent || parent,
hints
) :
modeling.createConnection(
cache[ element.source.id ],
cache[ element.target.id ],
element,
element.parent || parent,
hints
);
return;
}
var createShapeHints = assign({}, hints);
if (parents.indexOf(element) === -1) {
createShapeHints.autoResize = false;
}
cache[ element.id ] = isNumber(parentIndex) ?
modeling.createShape(
element,
pick(element, [ 'x', 'y', 'width', 'height' ]),
element.parent || parent,
parentIndex,
createShapeHints
) :
modeling.createShape(
element,
pick(element, [ 'x', 'y', 'width', 'height' ]),
element.parent || parent,
createShapeHints
);
});
context.elements = values(cache);
};
// helpers //////////
function isConnection$5(element) {
return !!element.waypoints;
}
var round$2 = Math.round;
/**
* A handler that implements reversible addition of shapes.
*
* @param {canvas} Canvas
*/
function CreateShapeHandler(canvas) {
this._canvas = canvas;
}
CreateShapeHandler.$inject = [ 'canvas' ];
// api //////////////////////
/**
* Appends a shape to a target shape
*
* @param {Object} context
* @param {djs.model.Base} context.parent the parent object
* @param {Point} context.position position of the new element
*/
CreateShapeHandler.prototype.execute = function(context) {
var shape = context.shape,
positionOrBounds = context.position,
parent = context.parent,
parentIndex = context.parentIndex;
if (!parent) {
throw new Error('parent required');
}
if (!positionOrBounds) {
throw new Error('position required');
}
// (1) add at event center position _or_ at given bounds
if (positionOrBounds.width !== undefined) {
assign(shape, positionOrBounds);
} else {
assign(shape, {
x: positionOrBounds.x - round$2(shape.width / 2),
y: positionOrBounds.y - round$2(shape.height / 2)
});
}
// (2) add to canvas
this._canvas.addShape(shape, parent, parentIndex);
return shape;
};
/**
* Undo append by removing the shape
*/
CreateShapeHandler.prototype.revert = function(context) {
var shape = context.shape;
// (3) remove form canvas
this._canvas.removeShape(shape);
return shape;
};
/**
* A handler that attaches a label to a given target shape.
*
* @param {Canvas} canvas
*/
function CreateLabelHandler(canvas) {
CreateShapeHandler.call(this, canvas);
}
e(CreateLabelHandler, CreateShapeHandler);
CreateLabelHandler.$inject = [ 'canvas' ];
// api //////////////////////
var originalExecute = CreateShapeHandler.prototype.execute;
/**
* Appends a label to a target shape.
*
* @method CreateLabelHandler#execute
*
* @param {Object} context
* @param {ElementDescriptor} context.target the element the label is attached to
* @param {ElementDescriptor} context.parent the parent object
* @param {Point} context.position position of the new element
*/
CreateLabelHandler.prototype.execute = function(context) {
var label = context.shape;
ensureValidDimensions(label);
label.labelTarget = context.labelTarget;
return originalExecute.call(this, context);
};
var originalRevert = CreateShapeHandler.prototype.revert;
/**
* Undo append by removing the shape
*/
CreateLabelHandler.prototype.revert = function(context) {
context.shape.labelTarget = null;
return originalRevert.call(this, context);
};
// helpers //////////////////////
function ensureValidDimensions(label) {
// make sure a label has valid { width, height } dimensions
[ 'width', 'height' ].forEach(function(prop) {
if (typeof label[prop] === 'undefined') {
label[prop] = 0;
}
});
}
/**
* A handler that implements reversible deletion of Connections.
*/
function DeleteConnectionHandler(canvas, modeling) {
this._canvas = canvas;
this._modeling = modeling;
}
DeleteConnectionHandler.$inject = [
'canvas',
'modeling'
];
/**
* - Remove connections
*/
DeleteConnectionHandler.prototype.preExecute = function(context) {
var modeling = this._modeling;
var connection = context.connection;
// remove connections
saveClear(connection.incoming, function(connection) {
// To make sure that the connection isn't removed twice
// For example if a container is removed
modeling.removeConnection(connection, { nested: true });
});
saveClear(connection.outgoing, function(connection) {
modeling.removeConnection(connection, { nested: true });
});
};
DeleteConnectionHandler.prototype.execute = function(context) {
var connection = context.connection,
parent = connection.parent;
context.parent = parent;
// remember containment
context.parentIndex = indexOf(parent.children, connection);
context.source = connection.source;
context.target = connection.target;
this._canvas.removeConnection(connection);
connection.source = null;
connection.target = null;
return connection;
};
/**
* Command revert implementation.
*/
DeleteConnectionHandler.prototype.revert = function(context) {
var connection = context.connection,
parent = context.parent,
parentIndex = context.parentIndex;
connection.source = context.source;
connection.target = context.target;
// restore containment
add(parent.children, connection, parentIndex);
this._canvas.addConnection(connection, parent);
return connection;
};
function DeleteElementsHandler(modeling, elementRegistry) {
this._modeling = modeling;
this._elementRegistry = elementRegistry;
}
DeleteElementsHandler.$inject = [
'modeling',
'elementRegistry'
];
DeleteElementsHandler.prototype.postExecute = function(context) {
var modeling = this._modeling,
elementRegistry = this._elementRegistry,
elements = context.elements;
forEach$1(elements, function(element) {
// element may have been removed with previous
// remove operations already (e.g. in case of nesting)
if (!elementRegistry.get(element.id)) {
return;
}
if (element.waypoints) {
modeling.removeConnection(element);
} else {
modeling.removeShape(element);
}
});
};
/**
* A handler that implements reversible deletion of shapes.
*
*/
function DeleteShapeHandler(canvas, modeling) {
this._canvas = canvas;
this._modeling = modeling;
}
DeleteShapeHandler.$inject = [ 'canvas', 'modeling' ];
/**
* - Remove connections
* - Remove all direct children
*/
DeleteShapeHandler.prototype.preExecute = function(context) {
var modeling = this._modeling;
var shape = context.shape;
// remove connections
saveClear(shape.incoming, function(connection) {
// To make sure that the connection isn't removed twice
// For example if a container is removed
modeling.removeConnection(connection, { nested: true });
});
saveClear(shape.outgoing, function(connection) {
modeling.removeConnection(connection, { nested: true });
});
// remove child shapes and connections
saveClear(shape.children, function(child) {
if (isConnection$4(child)) {
modeling.removeConnection(child, { nested: true });
} else {
modeling.removeShape(child, { nested: true });
}
});
};
/**
* Remove shape and remember the parent
*/
DeleteShapeHandler.prototype.execute = function(context) {
var canvas = this._canvas;
var shape = context.shape,
oldParent = shape.parent;
context.oldParent = oldParent;
// remove containment
context.oldParentIndex = indexOf(oldParent.children, shape);
// remove shape
canvas.removeShape(shape);
return shape;
};
/**
* Command revert implementation
*/
DeleteShapeHandler.prototype.revert = function(context) {
var canvas = this._canvas;
var shape = context.shape,
oldParent = context.oldParent,
oldParentIndex = context.oldParentIndex;
// restore containment
add(oldParent.children, shape, oldParentIndex);
canvas.addShape(shape, oldParent);
return shape;
};
function isConnection$4(element) {
return element.waypoints;
}
/**
* A handler that distributes elements evenly.
*/
function DistributeElements(modeling) {
this._modeling = modeling;
}
DistributeElements.$inject = [ 'modeling' ];
var OFF_AXIS = {
x: 'y',
y: 'x'
};
DistributeElements.prototype.preExecute = function(context) {
var modeling = this._modeling;
var groups = context.groups,
axis = context.axis,
dimension = context.dimension;
function updateRange(group, element) {
group.range.min = Math.min(element[axis], group.range.min);
group.range.max = Math.max(element[axis] + element[dimension], group.range.max);
}
function center(element) {
return element[axis] + element[dimension] / 2;
}
function lastIdx(arr) {
return arr.length - 1;
}
function rangeDiff(range) {
return range.max - range.min;
}
function centerElement(refCenter, element) {
var delta = { y: 0 };
delta[axis] = refCenter - center(element);
if (delta[axis]) {
delta[OFF_AXIS[axis]] = 0;
modeling.moveElements([ element ], delta, element.parent);
}
}
var firstGroup = groups[0],
lastGroupIdx = lastIdx(groups),
lastGroup = groups[ lastGroupIdx ];
var margin,
spaceInBetween,
groupsSize = 0; // the size of each range
forEach$1(groups, function(group, idx) {
var sortedElements,
refElem,
refCenter;
if (group.elements.length < 2) {
if (idx && idx !== groups.length - 1) {
updateRange(group, group.elements[0]);
groupsSize += rangeDiff(group.range);
}
return;
}
sortedElements = sortBy(group.elements, axis);
refElem = sortedElements[0];
if (idx === lastGroupIdx) {
refElem = sortedElements[lastIdx(sortedElements)];
}
refCenter = center(refElem);
// wanna update the ranges after the shapes have been centered
group.range = null;
forEach$1(sortedElements, function(element) {
centerElement(refCenter, element);
if (group.range === null) {
group.range = {
min: element[axis],
max: element[axis] + element[dimension]
};
return;
}
// update group's range after centering the range elements
updateRange(group, element);
});
if (idx && idx !== groups.length - 1) {
groupsSize += rangeDiff(group.range);
}
});
spaceInBetween = Math.abs(lastGroup.range.min - firstGroup.range.max);
margin = Math.round((spaceInBetween - groupsSize) / (groups.length - 1));
if (margin < groups.length - 1) {
return;
}
forEach$1(groups, function(group, groupIdx) {
var delta = {},
prevGroup;
if (group === firstGroup || group === lastGroup) {
return;
}
prevGroup = groups[groupIdx - 1];
group.range.max = 0;
forEach$1(group.elements, function(element, idx) {
delta[OFF_AXIS[axis]] = 0;
delta[axis] = (prevGroup.range.max - element[axis]) + margin;
if (group.range.min !== element[axis]) {
delta[axis] += element[axis] - group.range.min;
}
if (delta[axis]) {
modeling.moveElements([ element ], delta, element.parent);
}
group.range.max = Math.max(element[axis] + element[dimension], idx ? group.range.max : 0);
});
});
};
DistributeElements.prototype.postExecute = function(context) {
};
/**
* A handler that implements reversible moving of shapes.
*/
function LayoutConnectionHandler(layouter, canvas) {
this._layouter = layouter;
this._canvas = canvas;
}
LayoutConnectionHandler.$inject = [ 'layouter', 'canvas' ];
LayoutConnectionHandler.prototype.execute = function(context) {
var connection = context.connection;
var oldWaypoints = connection.waypoints;
assign(context, {
oldWaypoints: oldWaypoints
});
connection.waypoints = this._layouter.layoutConnection(connection, context.hints);
return connection;
};
LayoutConnectionHandler.prototype.revert = function(context) {
var connection = context.connection;
connection.waypoints = context.oldWaypoints;
return connection;
};
/**
* A handler that implements reversible moving of connections.
*
* The handler differs from the layout connection handler in a sense
* that it preserves the connection layout.
*/
function MoveConnectionHandler() { }
MoveConnectionHandler.prototype.execute = function(context) {
var connection = context.connection,
delta = context.delta;
var newParent = context.newParent || connection.parent,
newParentIndex = context.newParentIndex,
oldParent = connection.parent;
// save old parent in context
context.oldParent = oldParent;
context.oldParentIndex = remove(oldParent.children, connection);
// add to new parent at position
add(newParent.children, connection, newParentIndex);
// update parent
connection.parent = newParent;
// update waypoint positions
forEach$1(connection.waypoints, function(p) {
p.x += delta.x;
p.y += delta.y;
if (p.original) {
p.original.x += delta.x;
p.original.y += delta.y;
}
});
return connection;
};
MoveConnectionHandler.prototype.revert = function(context) {
var connection = context.connection,
newParent = connection.parent,
oldParent = context.oldParent,
oldParentIndex = context.oldParentIndex,
delta = context.delta;
// remove from newParent
remove(newParent.children, connection);
// restore previous location in old parent
add(oldParent.children, connection, oldParentIndex);
// restore parent
connection.parent = oldParent;
// revert to old waypoint positions
forEach$1(connection.waypoints, function(p) {
p.x -= delta.x;
p.y -= delta.y;
if (p.original) {
p.original.x -= delta.x;
p.original.y -= delta.y;
}
});
return connection;
};
function MoveClosure() {
this.allShapes = {};
this.allConnections = {};
this.enclosedElements = {};
this.enclosedConnections = {};
this.topLevel = {};
}
MoveClosure.prototype.add = function(element, isTopLevel) {
return this.addAll([ element ], isTopLevel);
};
MoveClosure.prototype.addAll = function(elements, isTopLevel) {
var newClosure = getClosure(elements, !!isTopLevel, this);
assign(this, newClosure);
return this;
};
/**
* A helper that is able to carry out serialized move
* operations on multiple elements.
*
* @param {Modeling} modeling
*/
function MoveHelper(modeling) {
this._modeling = modeling;
}
/**
* Move the specified elements and all children by the given delta.
*
* This moves all enclosed connections, too and layouts all affected
* external connections.
*
* @param {Array<djs.model.Base>} elements
* @param {Point} delta
* @param {djs.model.Base} newParent applied to the first level of shapes
*
* @return {Array<djs.model.Base>} list of touched elements
*/
MoveHelper.prototype.moveRecursive = function(elements, delta, newParent) {
if (!elements) {
return [];
} else {
return this.moveClosure(this.getClosure(elements), delta, newParent);
}
};
/**
* Move the given closure of elmements.
*
* @param {Object} closure
* @param {Point} delta
* @param {djs.model.Base} [newParent]
* @param {djs.model.Base} [newHost]
*/
MoveHelper.prototype.moveClosure = function(closure, delta, newParent, newHost, primaryShape) {
var modeling = this._modeling;
var allShapes = closure.allShapes,
allConnections = closure.allConnections,
enclosedConnections = closure.enclosedConnections,
topLevel = closure.topLevel,
keepParent = false;
if (primaryShape && primaryShape.parent === newParent) {
keepParent = true;
}
// move all shapes
forEach$1(allShapes, function(shape) {
// move the element according to the given delta
modeling.moveShape(shape, delta, topLevel[shape.id] && !keepParent && newParent, {
recurse: false,
layout: false
});
});
// move all child connections / layout external connections
forEach$1(allConnections, function(c) {
var sourceMoved = !!allShapes[c.source.id],
targetMoved = !!allShapes[c.target.id];
if (enclosedConnections[c.id] && sourceMoved && targetMoved) {
modeling.moveConnection(c, delta, topLevel[c.id] && !keepParent && newParent);
} else {
modeling.layoutConnection(c, {
connectionStart: sourceMoved && getMovedSourceAnchor(c, c.source, delta),
connectionEnd: targetMoved && getMovedTargetAnchor(c, c.target, delta)
});
}
});
};
/**
* Returns the closure for the selected elements
*
* @param {Array<djs.model.Base>} elements
* @return {MoveClosure} closure
*/
MoveHelper.prototype.getClosure = function(elements) {
return new MoveClosure().addAll(elements, true);
};
/**
* A handler that implements reversible moving of shapes.
*/
function MoveElementsHandler(modeling) {
this._helper = new MoveHelper(modeling);
}
MoveElementsHandler.$inject = [ 'modeling' ];
MoveElementsHandler.prototype.preExecute = function(context) {
context.closure = this._helper.getClosure(context.shapes);
};
MoveElementsHandler.prototype.postExecute = function(context) {
var hints = context.hints,
primaryShape;
if (hints && hints.primaryShape) {
primaryShape = hints.primaryShape;
hints.oldParent = primaryShape.parent;
}
this._helper.moveClosure(
context.closure,
context.delta,
context.newParent,
context.newHost,
primaryShape
);
};
/**
* A handler that implements reversible moving of shapes.
*/
function MoveShapeHandler(modeling) {
this._modeling = modeling;
this._helper = new MoveHelper(modeling);
}
MoveShapeHandler.$inject = [ 'modeling' ];
MoveShapeHandler.prototype.execute = function(context) {
var shape = context.shape,
delta = context.delta,
newParent = context.newParent || shape.parent,
newParentIndex = context.newParentIndex,
oldParent = shape.parent;
context.oldBounds = pick(shape, [ 'x', 'y', 'width', 'height' ]);
// save old parent in context
context.oldParent = oldParent;
context.oldParentIndex = remove(oldParent.children, shape);
// add to new parent at position
add(newParent.children, shape, newParentIndex);
// update shape parent + position
assign(shape, {
parent: newParent,
x: shape.x + delta.x,
y: shape.y + delta.y
});
return shape;
};
MoveShapeHandler.prototype.postExecute = function(context) {
var shape = context.shape,
delta = context.delta,
hints = context.hints;
var modeling = this._modeling;
if (hints.layout !== false) {
forEach$1(shape.incoming, function(c) {
modeling.layoutConnection(c, {
connectionEnd: getMovedTargetAnchor(c, shape, delta)
});
});
forEach$1(shape.outgoing, function(c) {
modeling.layoutConnection(c, {
connectionStart: getMovedSourceAnchor(c, shape, delta)
});
});
}
if (hints.recurse !== false) {
this.moveChildren(context);
}
};
MoveShapeHandler.prototype.revert = function(context) {
var shape = context.shape,
oldParent = context.oldParent,
oldParentIndex = context.oldParentIndex,
delta = context.delta;
// restore previous location in old parent
add(oldParent.children, shape, oldParentIndex);
// revert to old position and parent
assign(shape, {
parent: oldParent,
x: shape.x - delta.x,
y: shape.y - delta.y
});
return shape;
};
MoveShapeHandler.prototype.moveChildren = function(context) {
var delta = context.delta,
shape = context.shape;
this._helper.moveRecursive(shape.children, delta, null);
};
MoveShapeHandler.prototype.getNewParent = function(context) {
return context.newParent || context.shape.parent;
};
/**
* Reconnect connection handler
*/
function ReconnectConnectionHandler(modeling) {
this._modeling = modeling;
}
ReconnectConnectionHandler.$inject = [ 'modeling' ];
ReconnectConnectionHandler.prototype.execute = function(context) {
var newSource = context.newSource,
newTarget = context.newTarget,
connection = context.connection,
dockingOrPoints = context.dockingOrPoints;
if (!newSource && !newTarget) {
throw new Error('newSource or newTarget required');
}
if (isArray$3(dockingOrPoints)) {
context.oldWaypoints = connection.waypoints;
connection.waypoints = dockingOrPoints;
}
if (newSource) {
context.oldSource = connection.source;
connection.source = newSource;
}
if (newTarget) {
context.oldTarget = connection.target;
connection.target = newTarget;
}
return connection;
};
ReconnectConnectionHandler.prototype.postExecute = function(context) {
var connection = context.connection,
newSource = context.newSource,
newTarget = context.newTarget,
dockingOrPoints = context.dockingOrPoints,
hints = context.hints || {};
var layoutConnectionHints = {};
if (hints.connectionStart) {
layoutConnectionHints.connectionStart = hints.connectionStart;
}
if (hints.connectionEnd) {
layoutConnectionHints.connectionEnd = hints.connectionEnd;
}
if (hints.layoutConnection === false) {
return;
}
if (newSource && (!newTarget || hints.docking === 'source')) {
layoutConnectionHints.connectionStart = layoutConnectionHints.connectionStart
|| getDocking(isArray$3(dockingOrPoints) ? dockingOrPoints[ 0 ] : dockingOrPoints);
}
if (newTarget && (!newSource || hints.docking === 'target')) {
layoutConnectionHints.connectionEnd = layoutConnectionHints.connectionEnd
|| getDocking(isArray$3(dockingOrPoints) ? dockingOrPoints[ dockingOrPoints.length - 1 ] : dockingOrPoints);
}
if (hints.newWaypoints) {
layoutConnectionHints.waypoints = hints.newWaypoints;
}
this._modeling.layoutConnection(connection, layoutConnectionHints);
};
ReconnectConnectionHandler.prototype.revert = function(context) {
var oldSource = context.oldSource,
oldTarget = context.oldTarget,
oldWaypoints = context.oldWaypoints,
connection = context.connection;
if (oldSource) {
connection.source = oldSource;
}
if (oldTarget) {
connection.target = oldTarget;
}
if (oldWaypoints) {
connection.waypoints = oldWaypoints;
}
return connection;
};
// helpers //////////
function getDocking(point) {
return point.original || point;
}
/**
* Replace shape by adding new shape and removing old shape. Incoming and outgoing connections will
* be kept if possible.
*
* @class
* @constructor
*
* @param {Modeling} modeling
* @param {Rules} rules
*/
function ReplaceShapeHandler(modeling, rules) {
this._modeling = modeling;
this._rules = rules;
}
ReplaceShapeHandler.$inject = [ 'modeling', 'rules' ];
/**
* Add new shape.
*
* @param {Object} context
* @param {djs.model.Shape} context.oldShape
* @param {Object} context.newData
* @param {string} context.newData.type
* @param {number} context.newData.x
* @param {number} context.newData.y
* @param {Object} [hints]
*/
ReplaceShapeHandler.prototype.preExecute = function(context) {
var self = this,
modeling = this._modeling,
rules = this._rules;
var oldShape = context.oldShape,
newData = context.newData,
hints = context.hints || {},
newShape;
function canReconnect(source, target, connection) {
return rules.allowed('connection.reconnect', {
connection: connection,
source: source,
target: target
});
}
// (1) add new shape at given position
var position = {
x: newData.x,
y: newData.y
};
var oldBounds = {
x: oldShape.x,
y: oldShape.y,
width: oldShape.width,
height: oldShape.height
};
newShape = context.newShape =
context.newShape ||
self.createShape(newData, position, oldShape.parent, hints);
// (2) update host
if (oldShape.host) {
modeling.updateAttachment(newShape, oldShape.host);
}
// (3) adopt all children from old shape
var children;
if (hints.moveChildren !== false) {
children = oldShape.children.slice();
modeling.moveElements(children, { x: 0, y: 0 }, newShape, hints);
}
// (4) reconnect connections to new shape if possible
var incoming = oldShape.incoming.slice(),
outgoing = oldShape.outgoing.slice();
forEach$1(incoming, function(connection) {
var source = connection.source,
allowed = canReconnect(source, newShape, connection);
if (allowed) {
self.reconnectEnd(
connection, newShape,
getResizedTargetAnchor(connection, newShape, oldBounds),
hints
);
}
});
forEach$1(outgoing, function(connection) {
var target = connection.target,
allowed = canReconnect(newShape, target, connection);
if (allowed) {
self.reconnectStart(
connection, newShape,
getResizedSourceAnchor(connection, newShape, oldBounds),
hints
);
}
});
};
/**
* Remove old shape.
*/
ReplaceShapeHandler.prototype.postExecute = function(context) {
var oldShape = context.oldShape;
this._modeling.removeShape(oldShape);
};
ReplaceShapeHandler.prototype.execute = function(context) {};
ReplaceShapeHandler.prototype.revert = function(context) {};
ReplaceShapeHandler.prototype.createShape = function(shape, position, target, hints) {
return this._modeling.createShape(shape, position, target, hints);
};
ReplaceShapeHandler.prototype.reconnectStart = function(connection, newSource, dockingPoint, hints) {
this._modeling.reconnectStart(connection, newSource, dockingPoint, hints);
};
ReplaceShapeHandler.prototype.reconnectEnd = function(connection, newTarget, dockingPoint, hints) {
this._modeling.reconnectEnd(connection, newTarget, dockingPoint, hints);
};
/**
* A handler that implements reversible resizing of shapes.
*
* @param {Modeling} modeling
*/
function ResizeShapeHandler(modeling) {
this._modeling = modeling;
}
ResizeShapeHandler.$inject = [ 'modeling' ];
/**
* {
* shape: {....}
* newBounds: {
* width: 20,
* height: 40,
* x: 5,
* y: 10
* }
*
* }
*/
ResizeShapeHandler.prototype.execute = function(context) {
var shape = context.shape,
newBounds = context.newBounds,
minBounds = context.minBounds;
if (newBounds.x === undefined || newBounds.y === undefined ||
newBounds.width === undefined || newBounds.height === undefined) {
throw new Error('newBounds must have {x, y, width, height} properties');
}
if (minBounds && (newBounds.width < minBounds.width
|| newBounds.height < minBounds.height)) {
throw new Error('width and height cannot be less than minimum height and width');
} else if (!minBounds
&& newBounds.width < 10 || newBounds.height < 10) {
throw new Error('width and height cannot be less than 10px');
}
// save old bbox in context
context.oldBounds = {
width: shape.width,
height: shape.height,
x: shape.x,
y: shape.y
};
// update shape
assign(shape, {
width: newBounds.width,
height: newBounds.height,
x: newBounds.x,
y: newBounds.y
});
return shape;
};
ResizeShapeHandler.prototype.postExecute = function(context) {
var modeling = this._modeling;
var shape = context.shape,
oldBounds = context.oldBounds,
hints = context.hints || {};
if (hints.layout === false) {
return;
}
forEach$1(shape.incoming, function(c) {
modeling.layoutConnection(c, {
connectionEnd: getResizedTargetAnchor(c, shape, oldBounds)
});
});
forEach$1(shape.outgoing, function(c) {
modeling.layoutConnection(c, {
connectionStart: getResizedSourceAnchor(c, shape, oldBounds)
});
});
};
ResizeShapeHandler.prototype.revert = function(context) {
var shape = context.shape,
oldBounds = context.oldBounds;
// restore previous bbox
assign(shape, {
width: oldBounds.width,
height: oldBounds.height,
x: oldBounds.x,
y: oldBounds.y
});
return shape;
};
/**
* Add or remove space by moving and resizing shapes and updating connection waypoints.
*/
function SpaceToolHandler(modeling) {
this._modeling = modeling;
}
SpaceToolHandler.$inject = [ 'modeling' ];
SpaceToolHandler.prototype.preExecute = function(context) {
var delta = context.delta,
direction = context.direction,
movingShapes = context.movingShapes,
resizingShapes = context.resizingShapes,
start = context.start,
oldBounds = {};
// (1) move shapes
this.moveShapes(movingShapes, delta);
// (2a) save old bounds of resized shapes
forEach$1(resizingShapes, function(shape) {
oldBounds[shape.id] = getBounds(shape);
});
// (2b) resize shapes
this.resizeShapes(resizingShapes, delta, direction);
// (3) update connection waypoints
this.updateConnectionWaypoints(
getWaypointsUpdatingConnections(movingShapes, resizingShapes),
delta,
direction,
start,
movingShapes,
resizingShapes,
oldBounds
);
};
SpaceToolHandler.prototype.execute = function() {};
SpaceToolHandler.prototype.revert = function() {};
SpaceToolHandler.prototype.moveShapes = function(shapes, delta) {
var self = this;
forEach$1(shapes, function(element) {
self._modeling.moveShape(element, delta, null, {
autoResize: false,
layout: false,
recurse: false
});
});
};
SpaceToolHandler.prototype.resizeShapes = function(shapes, delta, direction) {
var self = this;
forEach$1(shapes, function(shape) {
var newBounds = resizeBounds(shape, direction, delta);
self._modeling.resizeShape(shape, newBounds, null, {
attachSupport: false,
autoResize: false,
layout: false
});
});
};
/**
* Update connections waypoints according to the rules:
* 1. Both source and target are moved/resized => move waypoints by the delta
* 2. Only one of source and target is moved/resized => re-layout connection with moved start/end
*/
SpaceToolHandler.prototype.updateConnectionWaypoints = function(
connections,
delta,
direction,
start,
movingShapes,
resizingShapes,
oldBounds
) {
var self = this,
affectedShapes = movingShapes.concat(resizingShapes);
forEach$1(connections, function(connection) {
var source = connection.source,
target = connection.target,
waypoints = copyWaypoints(connection),
axis = getAxisFromDirection(direction),
layoutHints = {
labelBehavior: false
};
if (includes$1(affectedShapes, source) && includes$1(affectedShapes, target)) {
// move waypoints
waypoints = map(waypoints, function(waypoint) {
if (shouldMoveWaypoint(waypoint, start, direction)) {
// move waypoint
waypoint[ axis ] = waypoint[ axis ] + delta[ axis ];
}
if (waypoint.original && shouldMoveWaypoint(waypoint.original, start, direction)) {
// move waypoint original
waypoint.original[ axis ] = waypoint.original[ axis ] + delta[ axis ];
}
return waypoint;
});
self._modeling.updateWaypoints(connection, waypoints, {
labelBehavior: false
});
} else if (includes$1(affectedShapes, source) || includes$1(affectedShapes, target)) {
// re-layout connection with moved start/end
if (includes$1(movingShapes, source)) {
layoutHints.connectionStart = getMovedSourceAnchor(connection, source, delta);
} else if (includes$1(movingShapes, target)) {
layoutHints.connectionEnd = getMovedTargetAnchor(connection, target, delta);
} else if (includes$1(resizingShapes, source)) {
layoutHints.connectionStart = getResizedSourceAnchor(
connection, source, oldBounds[source.id]
);
} else if (includes$1(resizingShapes, target)) {
layoutHints.connectionEnd = getResizedTargetAnchor(
connection, target, oldBounds[target.id]
);
}
self._modeling.layoutConnection(connection, layoutHints);
}
});
};
// helpers //////////
function copyWaypoint(waypoint) {
return assign({}, waypoint);
}
function copyWaypoints(connection) {
return map(connection.waypoints, function(waypoint) {
waypoint = copyWaypoint(waypoint);
if (waypoint.original) {
waypoint.original = copyWaypoint(waypoint.original);
}
return waypoint;
});
}
function getAxisFromDirection(direction) {
switch (direction) {
case 'n':
return 'y';
case 'w':
return 'x';
case 's':
return 'y';
case 'e':
return 'x';
}
}
function shouldMoveWaypoint(waypoint, start, direction) {
var relevantAxis = getAxisFromDirection(direction);
if (/e|s/.test(direction)) {
return waypoint[ relevantAxis ] > start;
} else if (/n|w/.test(direction)) {
return waypoint[ relevantAxis ] < start;
}
}
function includes$1(array, item) {
return array.indexOf(item) !== -1;
}
function getBounds(shape) {
return {
x: shape.x,
y: shape.y,
height: shape.height,
width: shape.width
};
}
/**
* A handler that toggles the collapsed state of an element
* and the visibility of all its children.
*
* @param {Modeling} modeling
*/
function ToggleShapeCollapseHandler(modeling) {
this._modeling = modeling;
}
ToggleShapeCollapseHandler.$inject = [ 'modeling' ];
ToggleShapeCollapseHandler.prototype.execute = function(context) {
var shape = context.shape,
children = shape.children;
// recursively remember previous visibility of children
context.oldChildrenVisibility = getElementsVisibilityRecursive(children);
// toggle state
shape.collapsed = !shape.collapsed;
// recursively hide/show children
var result = setHiddenRecursive(children, shape.collapsed);
return [ shape ].concat(result);
};
ToggleShapeCollapseHandler.prototype.revert = function(context) {
var shape = context.shape,
oldChildrenVisibility = context.oldChildrenVisibility;
var children = shape.children;
// recursively set old visability of children
var result = restoreVisibilityRecursive(children, oldChildrenVisibility);
// retoggle state
shape.collapsed = !shape.collapsed;
return [ shape ].concat(result);
};
// helpers //////////////////////
/**
* Return a map { elementId -> hiddenState}.
*
* @param {Array<djs.model.Shape>} elements
*
* @return {Object}
*/
function getElementsVisibilityRecursive(elements) {
var result = {};
forEach$1(elements, function(element) {
result[element.id] = element.hidden;
if (element.children) {
result = assign({}, result, getElementsVisibilityRecursive(element.children));
}
});
return result;
}
function setHiddenRecursive(elements, newHidden) {
var result = [];
forEach$1(elements, function(element) {
element.hidden = newHidden;
result = result.concat(element);
if (element.children) {
result = result.concat(setHiddenRecursive(element.children, element.collapsed || newHidden));
}
});
return result;
}
function restoreVisibilityRecursive(elements, lastState) {
var result = [];
forEach$1(elements, function(element) {
element.hidden = lastState[element.id];
result = result.concat(element);
if (element.children) {
result = result.concat(restoreVisibilityRecursive(element.children, lastState));
}
});
return result;
}
/**
* A handler that implements reversible attaching/detaching of shapes.
*/
function UpdateAttachmentHandler(modeling) {
this._modeling = modeling;
}
UpdateAttachmentHandler.$inject = [ 'modeling' ];
UpdateAttachmentHandler.prototype.execute = function(context) {
var shape = context.shape,
newHost = context.newHost,
oldHost = shape.host;
// (0) detach from old host
context.oldHost = oldHost;
context.attacherIdx = removeAttacher(oldHost, shape);
// (1) attach to new host
addAttacher(newHost, shape);
// (2) update host
shape.host = newHost;
return shape;
};
UpdateAttachmentHandler.prototype.revert = function(context) {
var shape = context.shape,
newHost = context.newHost,
oldHost = context.oldHost,
attacherIdx = context.attacherIdx;
// (2) update host
shape.host = oldHost;
// (1) attach to new host
removeAttacher(newHost, shape);
// (0) detach from old host
addAttacher(oldHost, shape, attacherIdx);
return shape;
};
function removeAttacher(host, attacher) {
// remove attacher from host
return remove(host && host.attachers, attacher);
}
function addAttacher(host, attacher, idx) {
if (!host) {
return;
}
var attachers = host.attachers;
if (!attachers) {
host.attachers = attachers = [];
}
add(attachers, attacher, idx);
}
function UpdateWaypointsHandler() { }
UpdateWaypointsHandler.prototype.execute = function(context) {
var connection = context.connection,
newWaypoints = context.newWaypoints;
context.oldWaypoints = connection.waypoints;
connection.waypoints = newWaypoints;
return connection;
};
UpdateWaypointsHandler.prototype.revert = function(context) {
var connection = context.connection,
oldWaypoints = context.oldWaypoints;
connection.waypoints = oldWaypoints;
return connection;
};
/**
* The basic modeling entry point.
*
* @param {EventBus} eventBus
* @param {ElementFactory} elementFactory
* @param {CommandStack} commandStack
*/
function Modeling$1(eventBus, elementFactory, commandStack) {
this._eventBus = eventBus;
this._elementFactory = elementFactory;
this._commandStack = commandStack;
var self = this;
eventBus.on('diagram.init', function() {
// register modeling handlers
self.registerHandlers(commandStack);
});
}
Modeling$1.$inject = [ 'eventBus', 'elementFactory', 'commandStack' ];
Modeling$1.prototype.getHandlers = function() {
return {
'shape.append': AppendShapeHandler,
'shape.create': CreateShapeHandler,
'shape.delete': DeleteShapeHandler,
'shape.move': MoveShapeHandler,
'shape.resize': ResizeShapeHandler,
'shape.replace': ReplaceShapeHandler,
'shape.toggleCollapse': ToggleShapeCollapseHandler,
'spaceTool': SpaceToolHandler,
'label.create': CreateLabelHandler,
'connection.create': CreateConnectionHandler,
'connection.delete': DeleteConnectionHandler,
'connection.move': MoveConnectionHandler,
'connection.layout': LayoutConnectionHandler,
'connection.updateWaypoints': UpdateWaypointsHandler,
'connection.reconnect': ReconnectConnectionHandler,
'elements.create': CreateElementsHandler,
'elements.move': MoveElementsHandler,
'elements.delete': DeleteElementsHandler,
'elements.distribute': DistributeElements,
'elements.align': AlignElements,
'element.updateAttachment': UpdateAttachmentHandler
};
};
/**
* Register handlers with the command stack
*
* @param {CommandStack} commandStack
*/
Modeling$1.prototype.registerHandlers = function(commandStack) {
forEach$1(this.getHandlers(), function(handler, id) {
commandStack.registerHandler(id, handler);
});
};
// modeling helpers //////////////////////
Modeling$1.prototype.moveShape = function(shape, delta, newParent, newParentIndex, hints) {
if (typeof newParentIndex === 'object') {
hints = newParentIndex;
newParentIndex = null;
}
var context = {
shape: shape,
delta: delta,
newParent: newParent,
newParentIndex: newParentIndex,
hints: hints || {}
};
this._commandStack.execute('shape.move', context);
};
/**
* Update the attachment of the given shape.
*
* @param {djs.mode.Base} shape
* @param {djs.model.Base} [newHost]
*/
Modeling$1.prototype.updateAttachment = function(shape, newHost) {
var context = {
shape: shape,
newHost: newHost
};
this._commandStack.execute('element.updateAttachment', context);
};
/**
* Move a number of shapes to a new target, either setting it as
* the new parent or attaching it.
*
* @param {Array<djs.mode.Base>} shapes
* @param {Point} delta
* @param {djs.model.Base} [target]
* @param {Object} [hints]
* @param {boolean} [hints.attach=false]
*/
Modeling$1.prototype.moveElements = function(shapes, delta, target, hints) {
hints = hints || {};
var attach = hints.attach;
var newParent = target,
newHost;
if (attach === true) {
newHost = target;
newParent = target.parent;
} else
if (attach === false) {
newHost = null;
}
var context = {
shapes: shapes,
delta: delta,
newParent: newParent,
newHost: newHost,
hints: hints
};
this._commandStack.execute('elements.move', context);
};
Modeling$1.prototype.moveConnection = function(connection, delta, newParent, newParentIndex, hints) {
if (typeof newParentIndex === 'object') {
hints = newParentIndex;
newParentIndex = undefined;
}
var context = {
connection: connection,
delta: delta,
newParent: newParent,
newParentIndex: newParentIndex,
hints: hints || {}
};
this._commandStack.execute('connection.move', context);
};
Modeling$1.prototype.layoutConnection = function(connection, hints) {
var context = {
connection: connection,
hints: hints || {}
};
this._commandStack.execute('connection.layout', context);
};
/**
* Create connection.
*
* @param {djs.model.Base} source
* @param {djs.model.Base} target
* @param {number} [parentIndex]
* @param {Object|djs.model.Connection} connection
* @param {djs.model.Base} parent
* @param {Object} hints
*
* @return {djs.model.Connection} the created connection.
*/
Modeling$1.prototype.createConnection = function(source, target, parentIndex, connection, parent, hints) {
if (typeof parentIndex === 'object') {
hints = parent;
parent = connection;
connection = parentIndex;
parentIndex = undefined;
}
connection = this._create('connection', connection);
var context = {
source: source,
target: target,
parent: parent,
parentIndex: parentIndex,
connection: connection,
hints: hints
};
this._commandStack.execute('connection.create', context);
return context.connection;
};
/**
* Create a shape at the specified position.
*
* @param {djs.model.Shape|Object} shape
* @param {Point} position
* @param {djs.model.Shape|djs.model.Root} target
* @param {number} [parentIndex] position in parents children list
* @param {Object} [hints]
* @param {boolean} [hints.attach] whether to attach to target or become a child
*
* @return {djs.model.Shape} the created shape
*/
Modeling$1.prototype.createShape = function(shape, position, target, parentIndex, hints) {
if (typeof parentIndex !== 'number') {
hints = parentIndex;
parentIndex = undefined;
}
hints = hints || {};
var attach = hints.attach,
parent,
host;
shape = this._create('shape', shape);
if (attach) {
parent = target.parent;
host = target;
} else {
parent = target;
}
var context = {
position: position,
shape: shape,
parent: parent,
parentIndex: parentIndex,
host: host,
hints: hints
};
this._commandStack.execute('shape.create', context);
return context.shape;
};
Modeling$1.prototype.createElements = function(elements, position, parent, parentIndex, hints) {
if (!isArray$3(elements)) {
elements = [ elements ];
}
if (typeof parentIndex !== 'number') {
hints = parentIndex;
parentIndex = undefined;
}
hints = hints || {};
var context = {
position: position,
elements: elements,
parent: parent,
parentIndex: parentIndex,
hints: hints
};
this._commandStack.execute('elements.create', context);
return context.elements;
};
Modeling$1.prototype.createLabel = function(labelTarget, position, label, parent) {
label = this._create('label', label);
var context = {
labelTarget: labelTarget,
position: position,
parent: parent || labelTarget.parent,
shape: label
};
this._commandStack.execute('label.create', context);
return context.shape;
};
/**
* Append shape to given source, drawing a connection
* between source and the newly created shape.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape|Object} shape
* @param {Point} position
* @param {djs.model.Shape} target
* @param {Object} [hints]
* @param {boolean} [hints.attach]
* @param {djs.model.Connection|Object} [hints.connection]
* @param {djs.model.Base} [hints.connectionParent]
*
* @return {djs.model.Shape} the newly created shape
*/
Modeling$1.prototype.appendShape = function(source, shape, position, target, hints) {
hints = hints || {};
shape = this._create('shape', shape);
var context = {
source: source,
position: position,
target: target,
shape: shape,
connection: hints.connection,
connectionParent: hints.connectionParent,
hints: hints
};
this._commandStack.execute('shape.append', context);
return context.shape;
};
Modeling$1.prototype.removeElements = function(elements) {
var context = {
elements: elements
};
this._commandStack.execute('elements.delete', context);
};
Modeling$1.prototype.distributeElements = function(groups, axis, dimension) {
var context = {
groups: groups,
axis: axis,
dimension: dimension
};
this._commandStack.execute('elements.distribute', context);
};
Modeling$1.prototype.removeShape = function(shape, hints) {
var context = {
shape: shape,
hints: hints || {}
};
this._commandStack.execute('shape.delete', context);
};
Modeling$1.prototype.removeConnection = function(connection, hints) {
var context = {
connection: connection,
hints: hints || {}
};
this._commandStack.execute('connection.delete', context);
};
Modeling$1.prototype.replaceShape = function(oldShape, newShape, hints) {
var context = {
oldShape: oldShape,
newData: newShape,
hints: hints || {}
};
this._commandStack.execute('shape.replace', context);
return context.newShape;
};
Modeling$1.prototype.alignElements = function(elements, alignment) {
var context = {
elements: elements,
alignment: alignment
};
this._commandStack.execute('elements.align', context);
};
Modeling$1.prototype.resizeShape = function(shape, newBounds, minBounds, hints) {
var context = {
shape: shape,
newBounds: newBounds,
minBounds: minBounds,
hints: hints
};
this._commandStack.execute('shape.resize', context);
};
Modeling$1.prototype.createSpace = function(movingShapes, resizingShapes, delta, direction, start) {
var context = {
delta: delta,
direction: direction,
movingShapes: movingShapes,
resizingShapes: resizingShapes,
start: start
};
this._commandStack.execute('spaceTool', context);
};
Modeling$1.prototype.updateWaypoints = function(connection, newWaypoints, hints) {
var context = {
connection: connection,
newWaypoints: newWaypoints,
hints: hints || {}
};
this._commandStack.execute('connection.updateWaypoints', context);
};
Modeling$1.prototype.reconnect = function(connection, source, target, dockingOrPoints, hints) {
var context = {
connection: connection,
newSource: source,
newTarget: target,
dockingOrPoints: dockingOrPoints,
hints: hints || {}
};
this._commandStack.execute('connection.reconnect', context);
};
Modeling$1.prototype.reconnectStart = function(connection, newSource, dockingOrPoints, hints) {
if (!hints) {
hints = {};
}
this.reconnect(connection, newSource, connection.target, dockingOrPoints, assign(hints, {
docking: 'source'
}));
};
Modeling$1.prototype.reconnectEnd = function(connection, newTarget, dockingOrPoints, hints) {
if (!hints) {
hints = {};
}
this.reconnect(connection, connection.source, newTarget, dockingOrPoints, assign(hints, {
docking: 'target'
}));
};
Modeling$1.prototype.connect = function(source, target, attrs, hints) {
return this.createConnection(source, target, attrs || {}, source.parent, hints);
};
Modeling$1.prototype._create = function(type, attrs) {
if (attrs instanceof Base$1) {
return attrs;
} else {
return this._elementFactory.create(type, attrs);
}
};
Modeling$1.prototype.toggleCollapse = function(shape, hints) {
var context = {
shape: shape,
hints: hints || {}
};
this._commandStack.execute('shape.toggleCollapse', context);
};
function UpdateModdlePropertiesHandler(elementRegistry) {
this._elementRegistry = elementRegistry;
}
UpdateModdlePropertiesHandler.$inject = [ 'elementRegistry' ];
UpdateModdlePropertiesHandler.prototype.execute = function(context) {
var element = context.element,
moddleElement = context.moddleElement,
properties = context.properties;
if (!moddleElement) {
throw new Error('<moddleElement> required');
}
// TODO(nikku): we need to ensure that ID properties
// are properly registered / unregistered via
// this._moddle.ids.assigned(id)
var changed = context.changed || this.getVisualReferences(moddleElement).concat(element);
var oldProperties = context.oldProperties || getModdleProperties(moddleElement, keys(properties));
setModdleProperties(moddleElement, properties);
context.oldProperties = oldProperties;
context.changed = changed;
return changed;
};
UpdateModdlePropertiesHandler.prototype.revert = function(context) {
var oldProperties = context.oldProperties,
moddleElement = context.moddleElement,
changed = context.changed;
setModdleProperties(moddleElement, oldProperties);
return changed;
};
/**
* Return visual references of given moddle element within the diagram.
*
* @param {ModdleElement} moddleElement
*
* @return {Array<djs.model.Element>}
*/
UpdateModdlePropertiesHandler.prototype.getVisualReferences = function(moddleElement) {
var elementRegistry = this._elementRegistry;
if (is$1(moddleElement, 'bpmn:DataObject')) {
return getAllDataObjectReferences(moddleElement, elementRegistry);
}
return [];
};
// helpers /////////////////
function getModdleProperties(moddleElement, propertyNames) {
return reduce(propertyNames, function(result, key) {
result[key] = moddleElement.get(key);
return result;
}, {});
}
function setModdleProperties(moddleElement, properties) {
forEach$1(properties, function(value, key) {
moddleElement.set(key, value);
});
}
function getAllDataObjectReferences(dataObject, elementRegistry) {
return elementRegistry.filter(function(element) {
return (
is$1(element, 'bpmn:DataObjectReference') &&
getBusinessObject(element).dataObjectRef === dataObject
);
});
}
var DEFAULT_FLOW = 'default',
ID = 'id',
DI = 'di';
var NULL_DIMENSIONS$1 = {
width: 0,
height: 0
};
/**
* A handler that implements a BPMN 2.0 property update.
*
* This should be used to set simple properties on elements with
* an underlying BPMN business object.
*
* Use respective diagram-js provided handlers if you would
* like to perform automated modeling.
*/
function UpdatePropertiesHandler(
elementRegistry, moddle, translate,
modeling, textRenderer) {
this._elementRegistry = elementRegistry;
this._moddle = moddle;
this._translate = translate;
this._modeling = modeling;
this._textRenderer = textRenderer;
}
UpdatePropertiesHandler.$inject = [
'elementRegistry',
'moddle',
'translate',
'modeling',
'textRenderer'
];
// api //////////////////////
/**
* Updates a BPMN element with a list of new properties
*
* @param {Object} context
* @param {djs.model.Base} context.element the element to update
* @param {Object} context.properties a list of properties to set on the element's
* businessObject (the BPMN model element)
*
* @return {Array<djs.model.Base>} the updated element
*/
UpdatePropertiesHandler.prototype.execute = function(context) {
var element = context.element,
changed = [ element ],
translate = this._translate;
if (!element) {
throw new Error(translate('element required'));
}
var elementRegistry = this._elementRegistry,
ids = this._moddle.ids;
var businessObject = element.businessObject,
properties = unwrapBusinessObjects(context.properties),
oldProperties = context.oldProperties || getProperties(element, properties);
if (isIdChange(properties, businessObject)) {
ids.unclaim(businessObject[ID]);
elementRegistry.updateId(element, properties[ID]);
ids.claim(properties[ID], businessObject);
}
// correctly indicate visual changes on default flow updates
if (DEFAULT_FLOW in properties) {
if (properties[DEFAULT_FLOW]) {
changed.push(elementRegistry.get(properties[DEFAULT_FLOW].id));
}
if (businessObject[DEFAULT_FLOW]) {
changed.push(elementRegistry.get(businessObject[DEFAULT_FLOW].id));
}
}
// update properties
setProperties(element, properties);
// store old values
context.oldProperties = oldProperties;
context.changed = changed;
// indicate changed on objects affected by the update
return changed;
};
UpdatePropertiesHandler.prototype.postExecute = function(context) {
var element = context.element,
label = element.label;
var text = label && getBusinessObject(label).name;
if (!text) {
return;
}
// get layouted text bounds and resize external
// external label accordingly
var newLabelBounds = this._textRenderer.getExternalLabelBounds(label, text);
this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS$1);
};
/**
* Reverts the update on a BPMN elements properties.
*
* @param {Object} context
*
* @return {djs.model.Base} the updated element
*/
UpdatePropertiesHandler.prototype.revert = function(context) {
var element = context.element,
properties = context.properties,
oldProperties = context.oldProperties,
businessObject = element.businessObject,
elementRegistry = this._elementRegistry,
ids = this._moddle.ids;
// update properties
setProperties(element, oldProperties);
if (isIdChange(properties, businessObject)) {
ids.unclaim(properties[ID]);
elementRegistry.updateId(element, oldProperties[ID]);
ids.claim(oldProperties[ID], businessObject);
}
return context.changed;
};
function isIdChange(properties, businessObject) {
return ID in properties && properties[ID] !== businessObject[ID];
}
function getProperties(element, properties) {
var propertyNames = keys(properties),
businessObject = element.businessObject,
di = getDi(element);
return reduce(propertyNames, function(result, key) {
// handle DI separately
if (key !== DI) {
result[key] = businessObject.get(key);
} else {
result[key] = getDiProperties(di, keys(properties.di));
}
return result;
}, {});
}
function getDiProperties(di, propertyNames) {
return reduce(propertyNames, function(result, key) {
result[key] = di && di.get(key);
return result;
}, {});
}
function setProperties(element, properties) {
var businessObject = element.businessObject,
di = getDi(element);
forEach$1(properties, function(value, key) {
if (key !== DI) {
businessObject.set(key, value);
} else {
// only update, if di exists
if (di) {
setDiProperties(di, value);
}
}
});
}
function setDiProperties(di, properties) {
forEach$1(properties, function(value, key) {
di.set(key, value);
});
}
var referencePropertyNames = [ 'default' ];
/**
* Make sure we unwrap the actual business object
* behind diagram element that may have been
* passed as arguments.
*
* @param {Object} properties
*
* @return {Object} unwrappedProps
*/
function unwrapBusinessObjects(properties) {
var unwrappedProps = assign({}, properties);
referencePropertyNames.forEach(function(name) {
if (name in properties) {
unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
}
});
return unwrappedProps;
}
function UpdateCanvasRootHandler(canvas, modeling) {
this._canvas = canvas;
this._modeling = modeling;
}
UpdateCanvasRootHandler.$inject = [
'canvas',
'modeling'
];
UpdateCanvasRootHandler.prototype.execute = function(context) {
var canvas = this._canvas;
var newRoot = context.newRoot,
newRootBusinessObject = newRoot.businessObject,
oldRoot = canvas.getRootElement(),
oldRootBusinessObject = oldRoot.businessObject,
bpmnDefinitions = oldRootBusinessObject.$parent,
diPlane = getDi(oldRoot);
// (1) replace process old <> new root
canvas.setRootElement(newRoot);
canvas.removeRootElement(oldRoot);
// (2) update root elements
add(bpmnDefinitions.rootElements, newRootBusinessObject);
newRootBusinessObject.$parent = bpmnDefinitions;
remove(bpmnDefinitions.rootElements, oldRootBusinessObject);
oldRootBusinessObject.$parent = null;
// (3) wire di
oldRoot.di = null;
diPlane.bpmnElement = newRootBusinessObject;
newRoot.di = diPlane;
context.oldRoot = oldRoot;
// TODO(nikku): return changed elements?
// return [ newRoot, oldRoot ];
};
UpdateCanvasRootHandler.prototype.revert = function(context) {
var canvas = this._canvas;
var newRoot = context.newRoot,
newRootBusinessObject = newRoot.businessObject,
oldRoot = context.oldRoot,
oldRootBusinessObject = oldRoot.businessObject,
bpmnDefinitions = newRootBusinessObject.$parent,
diPlane = getDi(newRoot);
// (1) replace process old <> new root
canvas.setRootElement(oldRoot);
canvas.removeRootElement(newRoot);
// (2) update root elements
remove(bpmnDefinitions.rootElements, newRootBusinessObject);
newRootBusinessObject.$parent = null;
add(bpmnDefinitions.rootElements, oldRootBusinessObject);
oldRootBusinessObject.$parent = bpmnDefinitions;
// (3) wire di
newRoot.di = null;
diPlane.bpmnElement = oldRootBusinessObject;
oldRoot.di = diPlane;
// TODO(nikku): return changed elements?
// return [ newRoot, oldRoot ];
};
/**
* A handler that allows us to add a new lane
* above or below an existing one.
*
* @param {Modeling} modeling
* @param {SpaceTool} spaceTool
*/
function AddLaneHandler(modeling, spaceTool) {
this._modeling = modeling;
this._spaceTool = spaceTool;
}
AddLaneHandler.$inject = [
'modeling',
'spaceTool'
];
AddLaneHandler.prototype.preExecute = function(context) {
var spaceTool = this._spaceTool,
modeling = this._modeling;
var shape = context.shape,
location = context.location;
var lanesRoot = getLanesRoot(shape);
var isRoot = lanesRoot === shape,
laneParent = isRoot ? shape : shape.parent;
var existingChildLanes = getChildLanes(laneParent);
// (0) add a lane if we currently got none and are adding to root
if (!existingChildLanes.length) {
modeling.createShape({ type: 'bpmn:Lane' }, {
x: shape.x + LANE_INDENTATION,
y: shape.y,
width: shape.width - LANE_INDENTATION,
height: shape.height
}, laneParent);
}
// (1) collect affected elements to create necessary space
var allAffected = [];
eachElement(lanesRoot, function(element) {
allAffected.push(element);
// handle element labels in the diagram root
if (element.label) {
allAffected.push(element.label);
}
if (element === shape) {
return [];
}
return filter(element.children, function(c) {
return c !== shape;
});
});
var offset = location === 'top' ? -120 : 120,
lanePosition = location === 'top' ? shape.y : shape.y + shape.height,
spacePos = lanePosition + (location === 'top' ? 10 : -10),
direction = location === 'top' ? 'n' : 's';
var adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);
spaceTool.makeSpace(
adjustments.movingShapes,
adjustments.resizingShapes,
{ x: 0, y: offset },
direction,
spacePos
);
// (2) create new lane at open space
context.newLane = modeling.createShape({ type: 'bpmn:Lane' }, {
x: shape.x + (isRoot ? LANE_INDENTATION : 0),
y: lanePosition - (location === 'top' ? 120 : 0),
width: shape.width - (isRoot ? LANE_INDENTATION : 0),
height: 120
}, laneParent);
};
/**
* A handler that splits a lane into a number of sub-lanes,
* creating new sub lanes, if necessary.
*
* @param {Modeling} modeling
*/
function SplitLaneHandler(modeling, translate) {
this._modeling = modeling;
this._translate = translate;
}
SplitLaneHandler.$inject = [
'modeling',
'translate'
];
SplitLaneHandler.prototype.preExecute = function(context) {
var modeling = this._modeling,
translate = this._translate;
var shape = context.shape,
newLanesCount = context.count;
var childLanes = getChildLanes(shape),
existingLanesCount = childLanes.length;
if (existingLanesCount > newLanesCount) {
throw new Error(translate('more than {count} child lanes', { count: newLanesCount }));
}
var newLanesHeight = Math.round(shape.height / newLanesCount);
// Iterate from top to bottom in child lane order,
// resizing existing lanes and creating new ones
// so that they split the parent proportionally.
//
// Due to rounding related errors, the bottom lane
// needs to take up all the remaining space.
var laneY,
laneHeight,
laneBounds,
newLaneAttrs,
idx;
for (idx = 0; idx < newLanesCount; idx++) {
laneY = shape.y + idx * newLanesHeight;
// if bottom lane
if (idx === newLanesCount - 1) {
laneHeight = shape.height - (newLanesHeight * idx);
} else {
laneHeight = newLanesHeight;
}
laneBounds = {
x: shape.x + LANE_INDENTATION,
y: laneY,
width: shape.width - LANE_INDENTATION,
height: laneHeight
};
if (idx < existingLanesCount) {
// resize existing lane
modeling.resizeShape(childLanes[idx], laneBounds);
} else {
// create a new lane at position
newLaneAttrs = {
type: 'bpmn:Lane'
};
modeling.createShape(newLaneAttrs, laneBounds, shape);
}
}
};
/**
* A handler that resizes a lane.
*
* @param {Modeling} modeling
*/
function ResizeLaneHandler(modeling, spaceTool) {
this._modeling = modeling;
this._spaceTool = spaceTool;
}
ResizeLaneHandler.$inject = [
'modeling',
'spaceTool'
];
ResizeLaneHandler.prototype.preExecute = function(context) {
var shape = context.shape,
newBounds = context.newBounds,
balanced = context.balanced;
if (balanced !== false) {
this.resizeBalanced(shape, newBounds);
} else {
this.resizeSpace(shape, newBounds);
}
};
/**
* Resize balanced, adjusting next / previous lane sizes.
*
* @param {djs.model.Shape} shape
* @param {Bounds} newBounds
*/
ResizeLaneHandler.prototype.resizeBalanced = function(shape, newBounds) {
var modeling = this._modeling;
var resizeNeeded = computeLanesResize(shape, newBounds);
// resize the lane
modeling.resizeShape(shape, newBounds);
// resize other lanes as needed
resizeNeeded.forEach(function(r) {
modeling.resizeShape(r.shape, r.newBounds);
});
};
/**
* Resize, making actual space and moving below / above elements.
*
* @param {djs.model.Shape} shape
* @param {Bounds} newBounds
*/
ResizeLaneHandler.prototype.resizeSpace = function(shape, newBounds) {
var spaceTool = this._spaceTool;
var shapeTrbl = asTRBL(shape),
newTrbl = asTRBL(newBounds);
var trblDiff = substractTRBL(newTrbl, shapeTrbl);
var lanesRoot = getLanesRoot(shape);
var allAffected = [],
allLanes = [];
eachElement(lanesRoot, function(element) {
allAffected.push(element);
if (is$1(element, 'bpmn:Lane') || is$1(element, 'bpmn:Participant')) {
allLanes.push(element);
}
return element.children;
});
var change,
spacePos,
direction,
offset,
adjustments;
if (trblDiff.bottom || trblDiff.top) {
change = trblDiff.bottom || trblDiff.top;
spacePos = shape.y + (trblDiff.bottom ? shape.height : 0) + (trblDiff.bottom ? -10 : 10);
direction = trblDiff.bottom ? 's' : 'n';
offset = trblDiff.top > 0 || trblDiff.bottom < 0 ? -change : change;
adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);
spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: 0, y: change }, direction);
}
if (trblDiff.left || trblDiff.right) {
change = trblDiff.right || trblDiff.left;
spacePos = shape.x + (trblDiff.right ? shape.width : 0) + (trblDiff.right ? -10 : 100);
direction = trblDiff.right ? 'e' : 'w';
offset = trblDiff.left > 0 || trblDiff.right < 0 ? -change : change;
adjustments = spaceTool.calculateAdjustments(allLanes, 'x', offset, spacePos);
spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: change, y: 0 }, direction);
}
};
var FLOW_NODE_REFS_ATTR = 'flowNodeRef',
LANES_ATTR = 'lanes';
/**
* A handler that updates lane refs on changed elements
*/
function UpdateFlowNodeRefsHandler(elementRegistry) {
this._elementRegistry = elementRegistry;
}
UpdateFlowNodeRefsHandler.$inject = [
'elementRegistry'
];
UpdateFlowNodeRefsHandler.prototype.computeUpdates = function(flowNodeShapes, laneShapes) {
var handledNodes = [];
var updates = [];
var participantCache = {};
var allFlowNodeShapes = [];
function isInLaneShape(element, laneShape) {
var laneTrbl = asTRBL(laneShape);
var elementMid = {
x: element.x + element.width / 2,
y: element.y + element.height / 2
};
return elementMid.x > laneTrbl.left &&
elementMid.x < laneTrbl.right &&
elementMid.y > laneTrbl.top &&
elementMid.y < laneTrbl.bottom;
}
function addFlowNodeShape(flowNodeShape) {
if (handledNodes.indexOf(flowNodeShape) === -1) {
allFlowNodeShapes.push(flowNodeShape);
handledNodes.push(flowNodeShape);
}
}
function getAllLaneShapes(flowNodeShape) {
var root = getLanesRoot(flowNodeShape);
if (!participantCache[root.id]) {
participantCache[root.id] = collectLanes(root);
}
return participantCache[root.id];
}
function getNewLanes(flowNodeShape) {
if (!flowNodeShape.parent) {
return [];
}
var allLaneShapes = getAllLaneShapes(flowNodeShape);
return allLaneShapes.filter(function(l) {
return isInLaneShape(flowNodeShape, l);
}).map(function(shape) {
return shape.businessObject;
});
}
laneShapes.forEach(function(laneShape) {
var root = getLanesRoot(laneShape);
if (!root || handledNodes.indexOf(root) !== -1) {
return;
}
var children = root.children.filter(function(c) {
return is$1(c, 'bpmn:FlowNode');
});
children.forEach(addFlowNodeShape);
handledNodes.push(root);
});
flowNodeShapes.forEach(addFlowNodeShape);
allFlowNodeShapes.forEach(function(flowNodeShape) {
var flowNode = flowNodeShape.businessObject;
var lanes = flowNode.get(LANES_ATTR),
remove = lanes.slice(),
add = getNewLanes(flowNodeShape);
updates.push({ flowNode: flowNode, remove: remove, add: add });
});
laneShapes.forEach(function(laneShape) {
var lane = laneShape.businessObject;
// lane got removed XX-)
if (!laneShape.parent) {
lane.get(FLOW_NODE_REFS_ATTR).forEach(function(flowNode) {
updates.push({ flowNode: flowNode, remove: [ lane ], add: [] });
});
}
});
return updates;
};
UpdateFlowNodeRefsHandler.prototype.execute = function(context) {
var updates = context.updates;
if (!updates) {
updates = context.updates = this.computeUpdates(context.flowNodeShapes, context.laneShapes);
}
updates.forEach(function(update) {
var flowNode = update.flowNode,
lanes = flowNode.get(LANES_ATTR);
// unwire old
update.remove.forEach(function(oldLane) {
remove(lanes, oldLane);
remove(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
// wire new
update.add.forEach(function(newLane) {
add(lanes, newLane);
add(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
});
// TODO(nikku): return changed elements
// return [ ... ];
};
UpdateFlowNodeRefsHandler.prototype.revert = function(context) {
var updates = context.updates;
updates.forEach(function(update) {
var flowNode = update.flowNode,
lanes = flowNode.get(LANES_ATTR);
// unwire new
update.add.forEach(function(newLane) {
remove(lanes, newLane);
remove(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
// wire old
update.remove.forEach(function(oldLane) {
add(lanes, oldLane);
add(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
});
});
// TODO(nikku): return changed elements
// return [ ... ];
};
function IdClaimHandler(moddle) {
this._moddle = moddle;
}
IdClaimHandler.$inject = [ 'moddle' ];
IdClaimHandler.prototype.execute = function(context) {
var ids = this._moddle.ids,
id = context.id,
element = context.element,
claiming = context.claiming;
if (claiming) {
ids.claim(id, element);
} else {
ids.unclaim(id);
}
};
/**
* Command revert implementation.
*/
IdClaimHandler.prototype.revert = function(context) {
var ids = this._moddle.ids,
id = context.id,
element = context.element,
claiming = context.claiming;
if (claiming) {
ids.unclaim(id);
} else {
ids.claim(id, element);
}
};
var DEFAULT_COLORS = {
fill: undefined,
stroke: undefined
};
function SetColorHandler(commandStack) {
this._commandStack = commandStack;
this._normalizeColor = function(color) {
// Remove color for falsy values.
if (!color) {
return undefined;
}
if (isString(color)) {
var hexColor = colorToHex(color);
if (hexColor) {
return hexColor;
}
}
throw new Error('invalid color value: ' + color);
};
}
SetColorHandler.$inject = [
'commandStack'
];
SetColorHandler.prototype.postExecute = function(context) {
var elements = context.elements,
colors = context.colors || DEFAULT_COLORS;
var self = this;
var di = {};
if ('fill' in colors) {
assign(di, {
'background-color': this._normalizeColor(colors.fill) });
}
if ('stroke' in colors) {
assign(di, {
'border-color': this._normalizeColor(colors.stroke) });
}
forEach$1(elements, function(element) {
var assignedDi = isConnection$3(element) ? pick(di, [ 'border-color' ]) : di;
// TODO @barmac: remove once we drop bpmn.io properties
ensureLegacySupport(assignedDi);
if (isLabel$6(element)) {
// set label colors as bpmndi:BPMNLabel#color
self._commandStack.execute('element.updateModdleProperties', {
element: element,
moddleElement: getDi(element).label,
properties: {
color: di['border-color']
}
});
} else {
// set colors bpmndi:BPMNEdge or bpmndi:BPMNShape
self._commandStack.execute('element.updateProperties', {
element: element,
properties: {
di: assignedDi
}
});
}
});
};
/**
* Convert color from rgb(a)/hsl to hex. Returns `null` for unknown color names and for colors
* with alpha less than 1.0. This depends on `<canvas>` serialization of the `context.fillStyle`.
* Cf. https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle
*
* @example
* ```js
* var color = 'fuchsia';
* console.log(colorToHex(color));
* // "#ff00ff"
* color = 'rgba(1,2,3,0.4)';
* console.log(colorToHex(color));
* // null
* ```
*
* @param {string} color
* @returns {string|null}
*/
function colorToHex(color) {
var context = document.createElement('canvas').getContext('2d');
// (0) Start with transparent to account for browser default values.
context.fillStyle = 'transparent';
// (1) Assign color so that it's serialized.
context.fillStyle = color;
// (2) Return null for non-hex serialization result.
return /^#[0-9a-fA-F]{6}$/.test(context.fillStyle) ? context.fillStyle : null;
}
function isConnection$3(element) {
return !!element.waypoints;
}
/**
* Add legacy properties if required.
* @param {{ 'border-color': string?, 'background-color': string? }} di
*/
function ensureLegacySupport(di) {
if ('border-color' in di) {
di.stroke = di['border-color'];
}
if ('background-color' in di) {
di.fill = di['background-color'];
}
}
var NULL_DIMENSIONS = {
width: 0,
height: 0
};
/**
* A handler that updates the text of a BPMN element.
*/
function UpdateLabelHandler(modeling, textRenderer, bpmnFactory) {
/**
* Creates an empty `diLabel` attribute for embedded labels.
*
* @param {djs.model.Base} element
* @param {string} text
*/
function ensureInternalLabelDi(element, text) {
if (isLabelExternal(element)) {
return;
}
var di = getDi(element);
if (text && !di.label) {
di.label = bpmnFactory.create('bpmndi:BPMNLabel');
}
if (!text && di.label) {
delete di.label;
}
}
/**
* Set the label and return the changed elements.
*
* Element parameter can be label itself or connection (i.e. sequence flow).
*
* @param {djs.model.Base} element
* @param {string} text
*/
function setText(element, text) {
// external label if present
var label = element.label || element;
var labelTarget = element.labelTarget || element;
setLabel(label, text);
ensureInternalLabelDi(element, text);
return [ label, labelTarget ];
}
function preExecute(ctx) {
var element = ctx.element,
businessObject = element.businessObject,
newLabel = ctx.newLabel;
if (!isLabel$6(element)
&& isLabelExternal(element)
&& !hasExternalLabel(element)
&& !isEmptyText(newLabel)) {
// create label
var paddingTop = 7;
var labelCenter = getExternalLabelMid(element);
labelCenter = {
x: labelCenter.x,
y: labelCenter.y + paddingTop
};
modeling.createLabel(element, labelCenter, {
id: businessObject.id + '_label',
businessObject: businessObject,
di: element.di
});
}
}
function execute(ctx) {
ctx.oldLabel = getLabel(ctx.element);
return setText(ctx.element, ctx.newLabel);
}
function revert(ctx) {
return setText(ctx.element, ctx.oldLabel);
}
function postExecute(ctx) {
var element = ctx.element,
label = element.label || element,
newLabel = ctx.newLabel,
newBounds = ctx.newBounds,
hints = ctx.hints || {};
// ignore internal labels for elements except text annotations
if (!isLabel$6(label) && !is$1(label, 'bpmn:TextAnnotation')) {
return;
}
if (isLabel$6(label) && isEmptyText(newLabel)) {
if (hints.removeShape !== false) {
modeling.removeShape(label, { unsetLabel: false });
}
return;
}
var text = getLabel(label);
// resize element based on label _or_ pre-defined bounds
if (typeof newBounds === 'undefined') {
newBounds = textRenderer.getExternalLabelBounds(label, text);
}
// setting newBounds to false or _null_ will
// disable the postExecute resize operation
if (newBounds) {
modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
}
}
// API
this.preExecute = preExecute;
this.execute = execute;
this.revert = revert;
this.postExecute = postExecute;
}
UpdateLabelHandler.$inject = [
'modeling',
'textRenderer',
'bpmnFactory'
];
// helpers ///////////////////////
function isEmptyText(label) {
return !label || !label.trim();
}
/**
* BPMN 2.0 modeling features activator
*
* @param {EventBus} eventBus
* @param {ElementFactory} elementFactory
* @param {CommandStack} commandStack
* @param {BpmnRules} bpmnRules
*/
function Modeling(
eventBus, elementFactory, commandStack,
bpmnRules) {
Modeling$1.call(this, eventBus, elementFactory, commandStack);
this._bpmnRules = bpmnRules;
}
e(Modeling, Modeling$1);
Modeling.$inject = [
'eventBus',
'elementFactory',
'commandStack',
'bpmnRules'
];
Modeling.prototype.getHandlers = function() {
var handlers = Modeling$1.prototype.getHandlers.call(this);
handlers['element.updateModdleProperties'] = UpdateModdlePropertiesHandler;
handlers['element.updateProperties'] = UpdatePropertiesHandler;
handlers['canvas.updateRoot'] = UpdateCanvasRootHandler;
handlers['lane.add'] = AddLaneHandler;
handlers['lane.resize'] = ResizeLaneHandler;
handlers['lane.split'] = SplitLaneHandler;
handlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler;
handlers['id.updateClaim'] = IdClaimHandler;
handlers['element.setColor'] = SetColorHandler;
handlers['element.updateLabel'] = UpdateLabelHandler;
return handlers;
};
Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
this._commandStack.execute('element.updateLabel', {
element: element,
newLabel: newLabel,
newBounds: newBounds,
hints: hints || {}
});
};
Modeling.prototype.connect = function(source, target, attrs, hints) {
var bpmnRules = this._bpmnRules;
if (!attrs) {
attrs = bpmnRules.canConnect(source, target);
}
if (!attrs) {
return;
}
return this.createConnection(source, target, attrs, source.parent, hints);
};
Modeling.prototype.updateModdleProperties = function(element, moddleElement, properties) {
this._commandStack.execute('element.updateModdleProperties', {
element: element,
moddleElement: moddleElement,
properties: properties
});
};
Modeling.prototype.updateProperties = function(element, properties) {
this._commandStack.execute('element.updateProperties', {
element: element,
properties: properties
});
};
Modeling.prototype.resizeLane = function(laneShape, newBounds, balanced) {
this._commandStack.execute('lane.resize', {
shape: laneShape,
newBounds: newBounds,
balanced: balanced
});
};
Modeling.prototype.addLane = function(targetLaneShape, location) {
var context = {
shape: targetLaneShape,
location: location
};
this._commandStack.execute('lane.add', context);
return context.newLane;
};
Modeling.prototype.splitLane = function(targetLane, count) {
this._commandStack.execute('lane.split', {
shape: targetLane,
count: count
});
};
/**
* Transform the current diagram into a collaboration.
*
* @return {djs.model.Root} the new root element
*/
Modeling.prototype.makeCollaboration = function() {
var collaborationElement = this._create('root', {
type: 'bpmn:Collaboration'
});
var context = {
newRoot: collaborationElement
};
this._commandStack.execute('canvas.updateRoot', context);
return collaborationElement;
};
Modeling.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) {
this._commandStack.execute('lane.updateRefs', {
flowNodeShapes: flowNodeShapes,
laneShapes: laneShapes
});
};
/**
* Transform the current diagram into a process.
*
* @return {djs.model.Root} the new root element
*/
Modeling.prototype.makeProcess = function() {
var processElement = this._create('root', {
type: 'bpmn:Process'
});
var context = {
newRoot: processElement
};
this._commandStack.execute('canvas.updateRoot', context);
};
Modeling.prototype.claimId = function(id, moddleElement) {
this._commandStack.execute('id.updateClaim', {
id: id,
element: moddleElement,
claiming: true
});
};
Modeling.prototype.unclaimId = function(id, moddleElement) {
this._commandStack.execute('id.updateClaim', {
id: id,
element: moddleElement
});
};
Modeling.prototype.setColor = function(elements, colors) {
if (!elements.length) {
elements = [ elements ];
}
this._commandStack.execute('element.setColor', {
elements: elements,
colors: colors
});
};
/**
* A base connection layouter implementation
* that layouts the connection by directly connecting
* mid(source) + mid(target).
*/
function BaseLayouter() {}
/**
* Return the new layouted waypoints for the given connection.
*
* The connection passed is still unchanged; you may figure out about
* the new connection start / end via the layout hints provided.
*
* @param {djs.model.Connection} connection
* @param {Object} [hints]
* @param {Point} [hints.connectionStart]
* @param {Point} [hints.connectionEnd]
* @param {Point} [hints.source]
* @param {Point} [hints.target]
*
* @return {Array<Point>} the layouted connection waypoints
*/
BaseLayouter.prototype.layoutConnection = function(connection, hints) {
hints = hints || {};
return [
hints.connectionStart || getMid(hints.source || connection.source),
hints.connectionEnd || getMid(hints.target || connection.target)
];
};
var MIN_SEGMENT_LENGTH = 20,
POINT_ORIENTATION_PADDING = 5;
var round$1 = Math.round;
var INTERSECTION_THRESHOLD = 20,
ORIENTATION_THRESHOLD = {
'h:h': 20,
'v:v': 20,
'h:v': -10,
'v:h': -10
};
function needsTurn(orientation, startDirection) {
return !{
t: /top/,
r: /right/,
b: /bottom/,
l: /left/,
h: /./,
v: /./
}[startDirection].test(orientation);
}
function canLayoutStraight(direction, targetOrientation) {
return {
t: /top/,
r: /right/,
b: /bottom/,
l: /left/,
h: /left|right/,
v: /top|bottom/
}[direction].test(targetOrientation);
}
function getSegmentBendpoints(a, b, directions) {
var orientation = getOrientation(b, a, POINT_ORIENTATION_PADDING);
var startDirection = directions.split(':')[0];
var xmid = round$1((b.x - a.x) / 2 + a.x),
ymid = round$1((b.y - a.y) / 2 + a.y);
var segmentEnd, segmentDirections;
var layoutStraight = canLayoutStraight(startDirection, orientation),
layoutHorizontal = /h|r|l/.test(startDirection),
layoutTurn = false;
var turnNextDirections = false;
if (layoutStraight) {
segmentEnd = layoutHorizontal ? { x: xmid, y: a.y } : { x: a.x, y: ymid };
segmentDirections = layoutHorizontal ? 'h:h' : 'v:v';
} else {
layoutTurn = needsTurn(orientation, startDirection);
segmentDirections = layoutHorizontal ? 'h:v' : 'v:h';
if (layoutTurn) {
if (layoutHorizontal) {
turnNextDirections = ymid === a.y;
segmentEnd = {
x: a.x + MIN_SEGMENT_LENGTH * (/l/.test(startDirection) ? -1 : 1),
y: turnNextDirections ? ymid + MIN_SEGMENT_LENGTH : ymid
};
} else {
turnNextDirections = xmid === a.x;
segmentEnd = {
x: turnNextDirections ? xmid + MIN_SEGMENT_LENGTH : xmid,
y: a.y + MIN_SEGMENT_LENGTH * (/t/.test(startDirection) ? -1 : 1)
};
}
} else {
segmentEnd = {
x: xmid,
y: ymid
};
}
}
return {
waypoints: getBendpoints(a, segmentEnd, segmentDirections).concat(segmentEnd),
directions: segmentDirections,
turnNextDirections: turnNextDirections
};
}
function getStartSegment(a, b, directions) {
return getSegmentBendpoints(a, b, directions);
}
function getEndSegment(a, b, directions) {
var invertedSegment = getSegmentBendpoints(b, a, invertDirections(directions));
return {
waypoints: invertedSegment.waypoints.slice().reverse(),
directions: invertDirections(invertedSegment.directions),
turnNextDirections: invertedSegment.turnNextDirections
};
}
function getMidSegment(startSegment, endSegment) {
var startDirection = startSegment.directions.split(':')[1],
endDirection = endSegment.directions.split(':')[0];
if (startSegment.turnNextDirections) {
startDirection = startDirection == 'h' ? 'v' : 'h';
}
if (endSegment.turnNextDirections) {
endDirection = endDirection == 'h' ? 'v' : 'h';
}
var directions = startDirection + ':' + endDirection;
var bendpoints = getBendpoints(
startSegment.waypoints[startSegment.waypoints.length - 1],
endSegment.waypoints[0],
directions
);
return {
waypoints: bendpoints,
directions: directions
};
}
function invertDirections(directions) {
return directions.split(':').reverse().join(':');
}
/**
* Handle simple layouts with maximum two bendpoints.
*/
function getSimpleBendpoints(a, b, directions) {
var xmid = round$1((b.x - a.x) / 2 + a.x),
ymid = round$1((b.y - a.y) / 2 + a.y);
// one point, right or left from a
if (directions === 'h:v') {
return [ { x: b.x, y: a.y } ];
}
// one point, above or below a
if (directions === 'v:h') {
return [ { x: a.x, y: b.y } ];
}
// vertical segment between a and b
if (directions === 'h:h') {
return [
{ x: xmid, y: a.y },
{ x: xmid, y: b.y }
];
}
// horizontal segment between a and b
if (directions === 'v:v') {
return [
{ x: a.x, y: ymid },
{ x: b.x, y: ymid }
];
}
throw new Error('invalid directions: can only handle varians of [hv]:[hv]');
}
/**
* Returns the mid points for a manhattan connection between two points.
*
* @example h:h (horizontal:horizontal)
*
* [a]----[x]
* |
* [x]----[b]
*
* @example h:v (horizontal:vertical)
*
* [a]----[x]
* |
* [b]
*
* @example h:r (horizontal:right)
*
* [a]----[x]
* |
* [b]-[x]
*
* @param {Point} a
* @param {Point} b
* @param {string} directions
*
* @return {Array<Point>}
*/
function getBendpoints(a, b, directions) {
directions = directions || 'h:h';
if (!isValidDirections(directions)) {
throw new Error(
'unknown directions: <' + directions + '>: ' +
'must be specified as <start>:<end> ' +
'with start/end in { h,v,t,r,b,l }'
);
}
// compute explicit directions, involving trbl dockings
// using a three segmented layouting algorithm
if (isExplicitDirections(directions)) {
var startSegment = getStartSegment(a, b, directions),
endSegment = getEndSegment(a, b, directions),
midSegment = getMidSegment(startSegment, endSegment);
return [].concat(
startSegment.waypoints,
midSegment.waypoints,
endSegment.waypoints
);
}
// handle simple [hv]:[hv] cases that can be easily computed
return getSimpleBendpoints(a, b, directions);
}
/**
* Create a connection between the two points according
* to the manhattan layout (only horizontal and vertical) edges.
*
* @param {Point} a
* @param {Point} b
*
* @param {string} [directions='h:h'] specifies manhattan directions for each point as {adirection}:{bdirection}.
A directionfor a point is either `h` (horizontal) or `v` (vertical)
*
* @return {Array<Point>}
*/
function connectPoints(a, b, directions) {
var points = getBendpoints(a, b, directions);
points.unshift(a);
points.push(b);
return withoutRedundantPoints(points);
}
/**
* Connect two rectangles using a manhattan layouted connection.
*
* @param {Bounds} source source rectangle
* @param {Bounds} target target rectangle
* @param {Point} [start] source docking
* @param {Point} [end] target docking
*
* @param {Object} [hints]
* @param {string} [hints.preserveDocking=source] preserve docking on selected side
* @param {Array<string>} [hints.preferredLayouts]
* @param {Point|boolean} [hints.connectionStart] whether the start changed
* @param {Point|boolean} [hints.connectionEnd] whether the end changed
*
* @return {Array<Point>} connection points
*/
function connectRectangles(source, target, start, end, hints) {
var preferredLayouts = hints && hints.preferredLayouts || [];
var preferredLayout = without(preferredLayouts, 'straight')[0] || 'h:h';
var threshold = ORIENTATION_THRESHOLD[preferredLayout] || 0;
var orientation = getOrientation(source, target, threshold);
var directions = getDirections(orientation, preferredLayout);
start = start || getMid(source);
end = end || getMid(target);
var directionSplit = directions.split(':');
// compute actual docking points for start / end
// this ensures we properly layout only parts of the
// connection that lies in between the two rectangles
var startDocking = getDockingPoint(start, source, directionSplit[0], invertOrientation(orientation)),
endDocking = getDockingPoint(end, target, directionSplit[1], orientation);
return connectPoints(startDocking, endDocking, directions);
}
/**
* Repair the connection between two rectangles, of which one has been updated.
*
* @param {Bounds} source
* @param {Bounds} target
* @param {Point} [start]
* @param {Point} [end]
* @param {Array<Point>} [waypoints]
* @param {Object} [hints]
* @param {Array<string>} [hints.preferredLayouts] list of preferred layouts
* @param {boolean} [hints.connectionStart]
* @param {boolean} [hints.connectionEnd]
*
* @return {Array<Point>} repaired waypoints
*/
function repairConnection(source, target, start, end, waypoints, hints) {
if (isArray$3(start)) {
waypoints = start;
hints = end;
start = getMid(source);
end = getMid(target);
}
hints = assign({ preferredLayouts: [] }, hints);
waypoints = waypoints || [];
var preferredLayouts = hints.preferredLayouts,
preferStraight = preferredLayouts.indexOf('straight') !== -1,
repairedWaypoints;
// just layout non-existing or simple connections
// attempt to render straight lines, if required
// attempt to layout a straight line
repairedWaypoints = preferStraight && tryLayoutStraight(source, target, start, end, hints);
if (repairedWaypoints) {
return repairedWaypoints;
}
// try to layout from end
repairedWaypoints = hints.connectionEnd && tryRepairConnectionEnd(target, source, end, waypoints);
if (repairedWaypoints) {
return repairedWaypoints;
}
// try to layout from start
repairedWaypoints = hints.connectionStart && tryRepairConnectionStart(source, target, start, waypoints);
if (repairedWaypoints) {
return repairedWaypoints;
}
// or whether nothing seems to have changed
if (!hints.connectionStart && !hints.connectionEnd && waypoints && waypoints.length) {
return waypoints;
}
// simply reconnect if nothing else worked
return connectRectangles(source, target, start, end, hints);
}
function inRange(a, start, end) {
return a >= start && a <= end;
}
function isInRange(axis, a, b) {
var size = {
x: 'width',
y: 'height'
};
return inRange(a[axis], b[axis], b[axis] + b[size[axis]]);
}
/**
* Layout a straight connection
*
* @param {Bounds} source
* @param {Bounds} target
* @param {Point} start
* @param {Point} end
* @param {Object} [hints]
*
* @return {Array<Point>|null} waypoints if straight layout worked
*/
function tryLayoutStraight(source, target, start, end, hints) {
var axis = {},
primaryAxis,
orientation;
orientation = getOrientation(source, target);
// only layout a straight connection if shapes are
// horizontally or vertically aligned
if (!/^(top|bottom|left|right)$/.test(orientation)) {
return null;
}
if (/top|bottom/.test(orientation)) {
primaryAxis = 'x';
}
if (/left|right/.test(orientation)) {
primaryAxis = 'y';
}
if (hints.preserveDocking === 'target') {
if (!isInRange(primaryAxis, end, source)) {
return null;
}
axis[primaryAxis] = end[primaryAxis];
return [
{
x: axis.x !== undefined ? axis.x : start.x,
y: axis.y !== undefined ? axis.y : start.y,
original: {
x: axis.x !== undefined ? axis.x : start.x,
y: axis.y !== undefined ? axis.y : start.y
}
},
{
x: end.x,
y: end.y
}
];
} else {
if (!isInRange(primaryAxis, start, target)) {
return null;
}
axis[primaryAxis] = start[primaryAxis];
return [
{
x: start.x,
y: start.y
},
{
x: axis.x !== undefined ? axis.x : end.x,
y: axis.y !== undefined ? axis.y : end.y,
original: {
x: axis.x !== undefined ? axis.x : end.x,
y: axis.y !== undefined ? axis.y : end.y
}
}
];
}
}
/**
* Repair a connection from start.
*
* @param {Bounds} moved
* @param {Bounds} other
* @param {Point} newDocking
* @param {Array<Point>} points originalPoints from moved to other
*
* @return {Array<Point>|null} the repaired points between the two rectangles
*/
function tryRepairConnectionStart(moved, other, newDocking, points) {
return _tryRepairConnectionSide(moved, other, newDocking, points);
}
/**
* Repair a connection from end.
*
* @param {Bounds} moved
* @param {Bounds} other
* @param {Point} newDocking
* @param {Array<Point>} points originalPoints from moved to other
*
* @return {Array<Point>|null} the repaired points between the two rectangles
*/
function tryRepairConnectionEnd(moved, other, newDocking, points) {
var waypoints = points.slice().reverse();
waypoints = _tryRepairConnectionSide(moved, other, newDocking, waypoints);
return waypoints ? waypoints.reverse() : null;
}
/**
* Repair a connection from one side that moved.
*
* @param {Bounds} moved
* @param {Bounds} other
* @param {Point} newDocking
* @param {Array<Point>} points originalPoints from moved to other
*
* @return {Array<Point>} the repaired points between the two rectangles
*/
function _tryRepairConnectionSide(moved, other, newDocking, points) {
function needsRelayout(points) {
if (points.length < 3) {
return true;
}
if (points.length > 4) {
return false;
}
// relayout if two points overlap
// this is most likely due to
return !!find(points, function(p, idx) {
var q = points[idx - 1];
return q && pointDistance(p, q) < 3;
});
}
function repairBendpoint(candidate, oldPeer, newPeer) {
var alignment = pointsAligned(oldPeer, candidate);
switch (alignment) {
case 'v':
// repair horizontal alignment
return { x: newPeer.x, y: candidate.y };
case 'h':
// repair vertical alignment
return { x: candidate.x, y: newPeer.y };
}
return { x: candidate.x, y: candidate. y };
}
function removeOverlapping(points, a, b) {
var i;
for (i = points.length - 2; i !== 0; i--) {
// intersects (?) break, remove all bendpoints up to this one and relayout
if (pointInRect(points[i], a, INTERSECTION_THRESHOLD) ||
pointInRect(points[i], b, INTERSECTION_THRESHOLD)) {
// return sliced old connection
return points.slice(i);
}
}
return points;
}
// (0) only repair what has layoutable bendpoints
// (1) if only one bendpoint and on shape moved onto other shapes axis
// (horizontally / vertically), relayout
if (needsRelayout(points)) {
return null;
}
var oldDocking = points[0],
newPoints = points.slice(),
slicedPoints;
// (2) repair only last line segment and only if it was layouted before
newPoints[0] = newDocking;
newPoints[1] = repairBendpoint(newPoints[1], oldDocking, newDocking);
// (3) if shape intersects with any bendpoint after repair,
// remove all segments up to this bendpoint and repair from there
slicedPoints = removeOverlapping(newPoints, moved, other);
if (slicedPoints !== newPoints) {
newPoints = _tryRepairConnectionSide(moved, other, newDocking, slicedPoints);
}
// (4) do NOT repair if repaired bendpoints are aligned
if (newPoints && pointsAligned(newPoints)) {
return null;
}
return newPoints;
}
/**
* Returns the manhattan directions connecting two rectangles
* with the given orientation.
*
* Will always return the default layout, if it is specific
* regarding sides already (trbl).
*
* @example
*
* getDirections('top'); // -> 'v:v'
* getDirections('intersect'); // -> 't:t'
*
* getDirections('top-right', 'v:h'); // -> 'v:h'
* getDirections('top-right', 'h:h'); // -> 'h:h'
*
*
* @param {string} orientation
* @param {string} defaultLayout
*
* @return {string}
*/
function getDirections(orientation, defaultLayout) {
// don't override specific trbl directions
if (isExplicitDirections(defaultLayout)) {
return defaultLayout;
}
switch (orientation) {
case 'intersect':
return 't:t';
case 'top':
case 'bottom':
return 'v:v';
case 'left':
case 'right':
return 'h:h';
// 'top-left'
// 'top-right'
// 'bottom-left'
// 'bottom-right'
default:
return defaultLayout;
}
}
function isValidDirections(directions) {
return directions && /^h|v|t|r|b|l:h|v|t|r|b|l$/.test(directions);
}
function isExplicitDirections(directions) {
return directions && /t|r|b|l/.test(directions);
}
function invertOrientation(orientation) {
return {
'top': 'bottom',
'bottom': 'top',
'left': 'right',
'right': 'left',
'top-left': 'bottom-right',
'bottom-right': 'top-left',
'top-right': 'bottom-left',
'bottom-left': 'top-right',
}[orientation];
}
function getDockingPoint(point, rectangle, dockingDirection, targetOrientation) {
// ensure we end up with a specific docking direction
// based on the targetOrientation, if <h|v> is being passed
if (dockingDirection === 'h') {
dockingDirection = /left/.test(targetOrientation) ? 'l' : 'r';
}
if (dockingDirection === 'v') {
dockingDirection = /top/.test(targetOrientation) ? 't' : 'b';
}
if (dockingDirection === 't') {
return { original: point, x: point.x, y: rectangle.y };
}
if (dockingDirection === 'r') {
return { original: point, x: rectangle.x + rectangle.width, y: point.y };
}
if (dockingDirection === 'b') {
return { original: point, x: point.x, y: rectangle.y + rectangle.height };
}
if (dockingDirection === 'l') {
return { original: point, x: rectangle.x, y: point.y };
}
throw new Error('unexpected dockingDirection: <' + dockingDirection + '>');
}
/**
* Return list of waypoints with redundant ones filtered out.
*
* @example
*
* Original points:
*
* [x] ----- [x] ------ [x]
* |
* [x] ----- [x] - [x]
*
* Filtered:
*
* [x] ---------------- [x]
* |
* [x] ----------- [x]
*
* @param {Array<Point>} waypoints
*
* @return {Array<Point>}
*/
function withoutRedundantPoints(waypoints) {
return waypoints.reduce(function(points, p, idx) {
var previous = points[points.length - 1],
next = waypoints[idx + 1];
if (!pointsOnLine(previous, next, p, 0)) {
points.push(p);
}
return points;
}, []);
}
var ATTACH_ORIENTATION_PADDING = -10,
BOUNDARY_TO_HOST_THRESHOLD$1 = 40;
var oppositeOrientationMapping = {
'top': 'bottom',
'top-right': 'bottom-left',
'top-left': 'bottom-right',
'right': 'left',
'bottom': 'top',
'bottom-right': 'top-left',
'bottom-left': 'top-right',
'left': 'right'
};
var orientationDirectionMapping = {
top: 't',
right: 'r',
bottom: 'b',
left: 'l'
};
function BpmnLayouter() {}
e(BpmnLayouter, BaseLayouter);
BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
if (!hints) {
hints = {};
}
var source = hints.source || connection.source,
target = hints.target || connection.target,
waypoints = hints.waypoints || connection.waypoints,
connectionStart = hints.connectionStart,
connectionEnd = hints.connectionEnd;
var manhattanOptions,
updatedWaypoints;
if (!connectionStart) {
connectionStart = getConnectionDocking(waypoints && waypoints[ 0 ], source);
}
if (!connectionEnd) {
connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target);
}
// TODO(nikku): support vertical modeling
// and invert preferredLayouts accordingly
if (is$1(connection, 'bpmn:Association') ||
is$1(connection, 'bpmn:DataAssociation')) {
if (waypoints && !isCompensationAssociation(source, target)) {
return [].concat([ connectionStart ], waypoints.slice(1, -1), [ connectionEnd ]);
}
}
if (is$1(connection, 'bpmn:MessageFlow')) {
manhattanOptions = getMessageFlowManhattanOptions(source, target);
} else if (is$1(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) {
// layout all connection between flow elements h:h, except for
// (1) outgoing of boundary events -> layout based on attach orientation and target orientation
// (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming
// (3) loops
if (source === target) {
manhattanOptions = {
preferredLayouts: getLoopPreferredLayout(source, connection)
};
} else if (is$1(source, 'bpmn:BoundaryEvent')) {
manhattanOptions = {
preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd)
};
} else if (isExpandedSubProcess(source) || isExpandedSubProcess(target)) {
manhattanOptions = getSubProcessManhattanOptions(source);
} else if (is$1(source, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: [ 'v:h' ]
};
} else if (is$1(target, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: [ 'h:v' ]
};
} else {
manhattanOptions = {
preferredLayouts: [ 'h:h' ]
};
}
}
if (manhattanOptions) {
manhattanOptions = assign(manhattanOptions, hints);
updatedWaypoints = withoutRedundantPoints(repairConnection(
source,
target,
connectionStart,
connectionEnd,
waypoints,
manhattanOptions
));
}
return updatedWaypoints || [ connectionStart, connectionEnd ];
};
// helpers //////////
function getAttachOrientation(attachedElement) {
var hostElement = attachedElement.host;
return getOrientation(getMid(attachedElement), hostElement, ATTACH_ORIENTATION_PADDING);
}
function getMessageFlowManhattanOptions(source, target) {
return {
preferredLayouts: [ 'straight', 'v:v' ],
preserveDocking: getMessageFlowPreserveDocking(source, target)
};
}
function getMessageFlowPreserveDocking(source, target) {
// (1) docking element connected to participant has precedence
if (is$1(target, 'bpmn:Participant')) {
return 'source';
}
if (is$1(source, 'bpmn:Participant')) {
return 'target';
}
// (2) docking element connected to expanded sub-process has precedence
if (isExpandedSubProcess(target)) {
return 'source';
}
if (isExpandedSubProcess(source)) {
return 'target';
}
// (3) docking event has precedence
if (is$1(target, 'bpmn:Event')) {
return 'target';
}
if (is$1(source, 'bpmn:Event')) {
return 'source';
}
return null;
}
function getSubProcessManhattanOptions(source) {
return {
preferredLayouts: [ 'straight', 'h:h' ],
preserveDocking: getSubProcessPreserveDocking(source)
};
}
function getSubProcessPreserveDocking(source) {
return isExpandedSubProcess(source) ? 'target' : 'source';
}
function getConnectionDocking(point, shape) {
return point ? (point.original || point) : getMid(shape);
}
function isCompensationAssociation(source, target) {
return is$1(target, 'bpmn:Activity') &&
is$1(source, 'bpmn:BoundaryEvent') &&
target.businessObject.isForCompensation;
}
function isExpandedSubProcess(element) {
return is$1(element, 'bpmn:SubProcess') && isExpanded(element);
}
function isSame(a, b) {
return a === b;
}
function isAnyOrientation(orientation, orientations) {
return orientations.indexOf(orientation) !== -1;
}
function getHorizontalOrientation(orientation) {
var matches = /right|left/.exec(orientation);
return matches && matches[0];
}
function getVerticalOrientation(orientation) {
var matches = /top|bottom/.exec(orientation);
return matches && matches[0];
}
function isOppositeOrientation(a, b) {
return oppositeOrientationMapping[a] === b;
}
function isOppositeHorizontalOrientation(a, b) {
var horizontalOrientation = getHorizontalOrientation(a);
var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];
return b.indexOf(oppositeHorizontalOrientation) !== -1;
}
function isOppositeVerticalOrientation(a, b) {
var verticalOrientation = getVerticalOrientation(a);
var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];
return b.indexOf(oppositeVerticalOrientation) !== -1;
}
function isHorizontalOrientation(orientation) {
return orientation === 'right' || orientation === 'left';
}
function getLoopPreferredLayout(source, connection) {
var waypoints = connection.waypoints;
var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source);
if (orientation === 'top') {
return [ 't:r' ];
} else if (orientation === 'right') {
return [ 'r:b' ];
} else if (orientation === 'left') {
return [ 'l:t' ];
}
return [ 'b:l' ];
}
function getBoundaryEventPreferredLayouts(source, target, end) {
var sourceMid = getMid(source),
targetMid = getMid(target),
attachOrientation = getAttachOrientation(source),
sourceLayout,
targetLayout;
var isLoop = isSame(source.host, target);
var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);
var targetOrientation = getOrientation(targetMid, sourceMid, {
x: source.width / 2 + target.width / 2,
y: source.height / 2 + target.height / 2
});
if (isLoop) {
return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end);
}
// source layout
sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide);
// target layout
targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide);
return [ sourceLayout + ':' + targetLayout ];
}
function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) {
var orientation = attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation),
sourceLayout = orientationDirectionMapping[ orientation ],
targetLayout;
if (attachedToSide) {
if (isHorizontalOrientation(attachOrientation)) {
targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b';
} else {
targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l';
}
} else {
targetLayout = 'v';
}
return [ sourceLayout + ':' + targetLayout ];
}
function shouldConnectToSameSide(axis, source, target, end) {
var threshold = BOUNDARY_TO_HOST_THRESHOLD$1;
return !(
areCloseOnAxis(axis, end, target, threshold) ||
areCloseOnAxis(axis, end, {
x: target.x + target.width,
y: target.y + target.height
}, threshold) ||
areCloseOnAxis(axis, end, getMid(source), threshold)
);
}
function areCloseOnAxis(axis, a, b, threshold) {
return Math.abs(a[ axis ] - b[ axis ]) < threshold;
}
function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) {
// attached to either top, right, bottom or left side
if (attachedToSide) {
return orientationDirectionMapping[ attachOrientation ];
}
// attached to either top-right, top-left, bottom-right or bottom-left corner
// same vertical or opposite horizontal orientation
if (isSame(
getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
) || isOppositeOrientation(
getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
)) {
return orientationDirectionMapping[ getVerticalOrientation(attachOrientation) ];
}
// fallback
return orientationDirectionMapping[ getHorizontalOrientation(attachOrientation) ];
}
function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) {
// attached to either top, right, bottom or left side
if (attachedToSide) {
if (isHorizontalOrientation(attachOrientation)) {
// orientation is right or left
// opposite horizontal orientation or same orientation
if (
isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
isSame(attachOrientation, targetOrientation)
) {
return 'h';
}
// fallback
return 'v';
} else {
// orientation is top or bottom
// opposite vertical orientation or same orientation
if (
isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
isSame(attachOrientation, targetOrientation)
) {
return 'v';
}
// fallback
return 'h';
}
}
// attached to either top-right, top-left, bottom-right or bottom-left corner
// orientation is right, left
// or same vertical orientation but also right or left
if (isHorizontalOrientation(targetOrientation) ||
(isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
getHorizontalOrientation(targetOrientation))) {
return 'h';
} else {
return 'v';
}
}
function dockingToPoint(docking) {
// use the dockings actual point and
// retain the original docking
return assign({ original: docking.point.original || docking.point }, docking.actual);
}
/**
* A {@link ConnectionDocking} that crops connection waypoints based on
* the path(s) of the connection source and target.
*
* @param {djs.core.ElementRegistry} elementRegistry
*/
function CroppingConnectionDocking(elementRegistry, graphicsFactory) {
this._elementRegistry = elementRegistry;
this._graphicsFactory = graphicsFactory;
}
CroppingConnectionDocking.$inject = [ 'elementRegistry', 'graphicsFactory' ];
/**
* @inheritDoc ConnectionDocking#getCroppedWaypoints
*/
CroppingConnectionDocking.prototype.getCroppedWaypoints = function(connection, source, target) {
source = source || connection.source;
target = target || connection.target;
var sourceDocking = this.getDockingPoint(connection, source, true),
targetDocking = this.getDockingPoint(connection, target);
var croppedWaypoints = connection.waypoints.slice(sourceDocking.idx + 1, targetDocking.idx);
croppedWaypoints.unshift(dockingToPoint(sourceDocking));
croppedWaypoints.push(dockingToPoint(targetDocking));
return croppedWaypoints;
};
/**
* Return the connection docking point on the specified shape
*
* @inheritDoc ConnectionDocking#getDockingPoint
*/
CroppingConnectionDocking.prototype.getDockingPoint = function(connection, shape, dockStart) {
var waypoints = connection.waypoints,
dockingIdx,
dockingPoint,
croppedPoint;
dockingIdx = dockStart ? 0 : waypoints.length - 1;
dockingPoint = waypoints[dockingIdx];
croppedPoint = this._getIntersection(shape, connection, dockStart);
return {
point: dockingPoint,
actual: croppedPoint || dockingPoint,
idx: dockingIdx
};
};
// helpers //////////////////////
CroppingConnectionDocking.prototype._getIntersection = function(shape, connection, takeFirst) {
var shapePath = this._getShapePath(shape),
connectionPath = this._getConnectionPath(connection);
return getElementLineIntersection(shapePath, connectionPath, takeFirst);
};
CroppingConnectionDocking.prototype._getConnectionPath = function(connection) {
return this._graphicsFactory.getConnectionPath(connection);
};
CroppingConnectionDocking.prototype._getShapePath = function(shape) {
return this._graphicsFactory.getShapePath(shape);
};
CroppingConnectionDocking.prototype._getGfx = function(element) {
return this._elementRegistry.getGraphics(element);
};
var ModelingModule = {
__init__: [
'modeling',
'bpmnUpdater'
],
__depends__: [
BehaviorModule,
RulesModule,
DiOrderingModule,
OrderingModule,
ReplaceModule,
CommandModule,
TooltipsModule,
LabelSupportModule,
AttachSupportModule,
SelectionModule,
ChangeSupportModule,
SpaceToolModule
],
bpmnFactory: [ 'type', BpmnFactory ],
bpmnUpdater: [ 'type', BpmnUpdater ],
elementFactory: [ 'type', ElementFactory ],
modeling: [ 'type', Modeling ],
layouter: [ 'type', BpmnLayouter ],
connectionDocking: [ 'type', CroppingConnectionDocking ]
};
var LOW_PRIORITY$2 = 500,
MEDIUM_PRIORITY = 1250,
HIGH_PRIORITY$2 = 1500;
var round = Math.round;
function mid(element) {
return {
x: element.x + round(element.width / 2),
y: element.y + round(element.height / 2)
};
}
/**
* A plugin that makes shapes draggable / droppable.
*
* @param {EventBus} eventBus
* @param {Dragging} dragging
* @param {Modeling} modeling
* @param {Selection} selection
* @param {Rules} rules
*/
function MoveEvents(
eventBus, dragging, modeling,
selection, rules) {
// rules
function canMove(shapes, delta, position, target) {
return rules.allowed('elements.move', {
shapes: shapes,
delta: delta,
position: position,
target: target
});
}
// move events
// assign a high priority to this handler to setup the environment
// others may hook up later, e.g. at default priority and modify
// the move environment.
//
// This sets up the context with
//
// * shape: the primary shape being moved
// * shapes: a list of shapes to be moved
// * validatedShapes: a list of shapes that are being checked
// against the rules before and during move
//
eventBus.on('shape.move.start', HIGH_PRIORITY$2, function(event) {
var context = event.context,
shape = event.shape,
shapes = selection.get().slice();
// move only single shape if the dragged element
// is not part of the current selection
if (shapes.indexOf(shape) === -1) {
shapes = [ shape ];
}
// ensure we remove nested elements in the collection
// and add attachers for a proper dragger
shapes = removeNested(shapes);
// attach shapes to drag context
assign(context, {
shapes: shapes,
validatedShapes: shapes,
shape: shape
});
});
// assign a high priority to this handler to setup the environment
// others may hook up later, e.g. at default priority and modify
// the move environment
//
eventBus.on('shape.move.start', MEDIUM_PRIORITY, function(event) {
var context = event.context,
validatedShapes = context.validatedShapes,
canExecute;
canExecute = context.canExecute = canMove(validatedShapes);
// check if we can move the elements
if (!canExecute) {
return false;
}
});
// assign a low priority to this handler
// to let others modify the move event before we update
// the context
//
eventBus.on('shape.move.move', LOW_PRIORITY$2, function(event) {
var context = event.context,
validatedShapes = context.validatedShapes,
hover = event.hover,
delta = { x: event.dx, y: event.dy },
position = { x: event.x, y: event.y },
canExecute;
// check if we can move the elements
canExecute = canMove(validatedShapes, delta, position, hover);
context.delta = delta;
context.canExecute = canExecute;
// simply ignore move over
if (canExecute === null) {
context.target = null;
return;
}
context.target = hover;
});
eventBus.on('shape.move.end', function(event) {
var context = event.context;
var delta = context.delta,
canExecute = context.canExecute,
isAttach = canExecute === 'attach',
shapes = context.shapes;
if (canExecute === false) {
return false;
}
// ensure we have actual pixel values deltas
// (important when zoom level was > 1 during move)
delta.x = round(delta.x);
delta.y = round(delta.y);
if (delta.x === 0 && delta.y === 0) {
// didn't move
return;
}
modeling.moveElements(shapes, delta, context.target, {
primaryShape: context.shape,
attach: isAttach
});
});
// move activation
eventBus.on('element.mousedown', function(event) {
if (!isPrimaryButton(event)) {
return;
}
var originalEvent = getOriginal$1(event);
if (!originalEvent) {
throw new Error('must supply DOM mousedown event');
}
return start(originalEvent, event.element);
});
/**
* Start move.
*
* @param {MouseEvent} event
* @param {djs.model.Shape} shape
* @param {boolean} [activate]
* @param {Object} [context]
*/
function start(event, element, activate, context) {
if (isObject(activate)) {
context = activate;
activate = false;
}
// do not move connections or the root element
if (element.waypoints || !element.parent) {
return;
}
// ignore non-draggable hits
if (classes(event.target).has('djs-hit-no-move')) {
return;
}
var referencePoint = mid(element);
dragging.init(event, referencePoint, 'shape.move', {
cursor: 'grabbing',
autoActivate: activate,
data: {
shape: element,
context: context || {}
}
});
// we've handled the event
return true;
}
// API
this.start = start;
}
MoveEvents.$inject = [
'eventBus',
'dragging',
'modeling',
'selection',
'rules'
];
/**
* Return a filtered list of elements that do not contain
* those nested into others.
*
* @param {Array<djs.model.Base>} elements
*
* @return {Array<djs.model.Base>} filtered
*/
function removeNested(elements) {
var ids = groupBy(elements, 'id');
return filter(elements, function(element) {
while ((element = element.parent)) {
// parent in selection
if (ids[element.id]) {
return false;
}
}
return true;
});
}
var LOW_PRIORITY$1 = 499;
var MARKER_DRAGGING = 'djs-dragging',
MARKER_OK$1 = 'drop-ok',
MARKER_NOT_OK$1 = 'drop-not-ok',
MARKER_NEW_PARENT = 'new-parent',
MARKER_ATTACH = 'attach-ok';
/**
* Provides previews for moving shapes when moving.
*
* @param {EventBus} eventBus
* @param {ElementRegistry} elementRegistry
* @param {Canvas} canvas
* @param {Styles} styles
*/
function MovePreview(
eventBus, canvas, styles, previewSupport) {
function getVisualDragShapes(shapes) {
var elements = getAllDraggedElements(shapes);
var filteredElements = removeEdges(elements);
return filteredElements;
}
function getAllDraggedElements(shapes) {
var allShapes = selfAndAllChildren(shapes, true);
var allConnections = map(allShapes, function(shape) {
return (shape.incoming || []).concat(shape.outgoing || []);
});
return flatten(allShapes.concat(allConnections));
}
/**
* Sets drop marker on an element.
*/
function setMarker(element, marker) {
[ MARKER_ATTACH, MARKER_OK$1, MARKER_NOT_OK$1, MARKER_NEW_PARENT ].forEach(function(m) {
if (m === marker) {
canvas.addMarker(element, m);
} else {
canvas.removeMarker(element, m);
}
});
}
/**
* Make an element draggable.
*
* @param {Object} context
* @param {djs.model.Base} element
* @param {boolean} addMarker
*/
function makeDraggable(context, element, addMarker) {
previewSupport.addDragger(element, context.dragGroup);
if (addMarker) {
canvas.addMarker(element, MARKER_DRAGGING);
}
if (context.allDraggedElements) {
context.allDraggedElements.push(element);
} else {
context.allDraggedElements = [ element ];
}
}
// assign a low priority to this handler
// to let others modify the move context before
// we draw things
eventBus.on('shape.move.start', LOW_PRIORITY$1, function(event) {
var context = event.context,
dragShapes = context.shapes,
allDraggedElements = context.allDraggedElements;
var visuallyDraggedShapes = getVisualDragShapes(dragShapes);
if (!context.dragGroup) {
var dragGroup = create$1('g');
attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ]));
var activeLayer = canvas.getActiveLayer();
append(activeLayer, dragGroup);
context.dragGroup = dragGroup;
}
// add previews
visuallyDraggedShapes.forEach(function(shape) {
previewSupport.addDragger(shape, context.dragGroup);
});
// cache all dragged elements / gfx
// so that we can quickly undo their state changes later
if (!allDraggedElements) {
allDraggedElements = getAllDraggedElements(dragShapes);
} else {
allDraggedElements = flatten([
allDraggedElements,
getAllDraggedElements(dragShapes)
]);
}
// add dragging marker
forEach$1(allDraggedElements, function(e) {
canvas.addMarker(e, MARKER_DRAGGING);
});
context.allDraggedElements = allDraggedElements;
// determine, if any of the dragged elements have different parents
context.differentParents = haveDifferentParents(dragShapes);
});
// update previews
eventBus.on('shape.move.move', LOW_PRIORITY$1, function(event) {
var context = event.context,
dragGroup = context.dragGroup,
target = context.target,
parent = context.shape.parent,
canExecute = context.canExecute;
if (target) {
if (canExecute === 'attach') {
setMarker(target, MARKER_ATTACH);
} else if (context.canExecute && target && target.id !== parent.id) {
setMarker(target, MARKER_NEW_PARENT);
} else {
setMarker(target, context.canExecute ? MARKER_OK$1 : MARKER_NOT_OK$1);
}
}
translate$2(dragGroup, event.dx, event.dy);
});
eventBus.on([ 'shape.move.out', 'shape.move.cleanup' ], function(event) {
var context = event.context,
target = context.target;
if (target) {
setMarker(target, null);
}
});
// remove previews
eventBus.on('shape.move.cleanup', function(event) {
var context = event.context,
allDraggedElements = context.allDraggedElements,
dragGroup = context.dragGroup;
// remove dragging marker
forEach$1(allDraggedElements, function(e) {
canvas.removeMarker(e, MARKER_DRAGGING);
});
if (dragGroup) {
remove$1(dragGroup);
}
});
// API //////////////////////
/**
* Make an element draggable.
*
* @param {Object} context
* @param {djs.model.Base} element
* @param {boolean} addMarker
*/
this.makeDraggable = makeDraggable;
}
MovePreview.$inject = [
'eventBus',
'canvas',
'styles',
'previewSupport'
];
// helpers //////////////////////
/**
* returns elements minus all connections
* where source or target is not elements
*/
function removeEdges(elements) {
var filteredElements = filter(elements, function(element) {
if (!isConnection$2(element)) {
return true;
} else {
return (
find(elements, matchPattern({ id: element.source.id })) &&
find(elements, matchPattern({ id: element.target.id }))
);
}
});
return filteredElements;
}
function haveDifferentParents(elements) {
return size(groupBy(elements, function(e) { return e.parent && e.parent.id; })) !== 1;
}
/**
* Checks if an element is a connection.
*/
function isConnection$2(element) {
return element.waypoints;
}
var MoveModule = {
__depends__: [
InteractionEventsModule$1,
SelectionModule,
OutlineModule,
RulesModule$1,
DraggingModule,
PreviewSupportModule
],
__init__: [
'move',
'movePreview'
],
move: [ 'type', MoveEvents ],
movePreview: [ 'type', MovePreview ]
};
var TOGGLE_SELECTOR = '.djs-palette-toggle',
ENTRY_SELECTOR = '.entry',
ELEMENT_SELECTOR = TOGGLE_SELECTOR + ', ' + ENTRY_SELECTOR;
var PALETTE_PREFIX = 'djs-palette-',
PALETTE_SHOWN_CLS = 'shown',
PALETTE_OPEN_CLS = 'open',
PALETTE_TWO_COLUMN_CLS = 'two-column';
var DEFAULT_PRIORITY = 1000;
/**
* A palette containing modeling elements.
*/
function Palette(eventBus, canvas) {
this._eventBus = eventBus;
this._canvas = canvas;
var self = this;
eventBus.on('tool-manager.update', function(event) {
var tool = event.tool;
self.updateToolHighlight(tool);
});
eventBus.on('i18n.changed', function() {
self._update();
});
eventBus.on('diagram.init', function() {
self._diagramInitialized = true;
self._rebuild();
});
}
Palette.$inject = [ 'eventBus', 'canvas' ];
/**
* Register a provider with the palette
*
* @param {number} [priority=1000]
* @param {PaletteProvider} provider
*
* @example
* const paletteProvider = {
* getPaletteEntries: function() {
* return function(entries) {
* return {
* ...entries,
* 'entry-1': {
* label: 'My Entry',
* action: function() { alert("I have been clicked!"); }
* }
* };
* }
* }
* };
*
* palette.registerProvider(800, paletteProvider);
*/
Palette.prototype.registerProvider = function(priority, provider) {
if (!provider) {
provider = priority;
priority = DEFAULT_PRIORITY;
}
this._eventBus.on('palette.getProviders', priority, function(event) {
event.providers.push(provider);
});
this._rebuild();
};
/**
* Returns the palette entries
*
* @return {Object<string, PaletteEntryDescriptor>} map of entries
*/
Palette.prototype.getEntries = function() {
var providers = this._getProviders();
return providers.reduce(addPaletteEntries, {});
};
Palette.prototype._rebuild = function() {
if (!this._diagramInitialized) {
return;
}
var providers = this._getProviders();
if (!providers.length) {
return;
}
if (!this._container) {
this._init();
}
this._update();
};
/**
* Initialize
*/
Palette.prototype._init = function() {
var self = this;
var eventBus = this._eventBus;
var parentContainer = this._getParentContainer();
var container = this._container = domify(Palette.HTML_MARKUP);
parentContainer.appendChild(container);
classes$1(parentContainer).add(PALETTE_PREFIX + PALETTE_SHOWN_CLS);
delegate.bind(container, ELEMENT_SELECTOR, 'click', function(event) {
var target = event.delegateTarget;
if (matchesSelector(target, TOGGLE_SELECTOR)) {
return self.toggle();
}
self.trigger('click', event);
});
// prevent drag propagation
componentEvent.bind(container, 'mousedown', function(event) {
event.stopPropagation();
});
// prevent drag propagation
delegate.bind(container, ENTRY_SELECTOR, 'dragstart', function(event) {
self.trigger('dragstart', event);
});
eventBus.on('canvas.resized', this._layoutChanged, this);
eventBus.fire('palette.create', {
container: container
});
};
Palette.prototype._getProviders = function(id) {
var event = this._eventBus.createEvent({
type: 'palette.getProviders',
providers: []
});
this._eventBus.fire(event);
return event.providers;
};
/**
* Update palette state.
*
* @param {Object} [state] { open, twoColumn }
*/
Palette.prototype._toggleState = function(state) {
state = state || {};
var parent = this._getParentContainer(),
container = this._container;
var eventBus = this._eventBus;
var twoColumn;
var cls = classes$1(container),
parentCls = classes$1(parent);
if ('twoColumn' in state) {
twoColumn = state.twoColumn;
} else {
twoColumn = this._needsCollapse(parent.clientHeight, this._entries || {});
}
// always update two column
cls.toggle(PALETTE_TWO_COLUMN_CLS, twoColumn);
parentCls.toggle(PALETTE_PREFIX + PALETTE_TWO_COLUMN_CLS, twoColumn);
if ('open' in state) {
cls.toggle(PALETTE_OPEN_CLS, state.open);
parentCls.toggle(PALETTE_PREFIX + PALETTE_OPEN_CLS, state.open);
}
eventBus.fire('palette.changed', {
twoColumn: twoColumn,
open: this.isOpen()
});
};
Palette.prototype._update = function() {
var entriesContainer = query('.djs-palette-entries', this._container),
entries = this._entries = this.getEntries();
clear$1(entriesContainer);
forEach$1(entries, function(entry, id) {
var grouping = entry.group || 'default';
var container = query('[data-group=' + cssEscape(grouping) + ']', entriesContainer);
if (!container) {
container = domify('<div class="group"></div>');
attr$1(container, 'data-group', grouping);
entriesContainer.appendChild(container);
}
var html = entry.html || (
entry.separator ?
'<hr class="separator" />' :
'<div class="entry" draggable="true"></div>');
var control = domify(html);
container.appendChild(control);
if (!entry.separator) {
attr$1(control, 'data-action', id);
if (entry.title) {
attr$1(control, 'title', entry.title);
}
if (entry.className) {
addClasses(control, entry.className);
}
if (entry.imageUrl) {
var image = domify('<img>');
attr$1(image, 'src', entry.imageUrl);
control.appendChild(image);
}
}
});
// open after update
this.open();
};
/**
* Trigger an action available on the palette
*
* @param {string} action
* @param {Event} event
*/
Palette.prototype.trigger = function(action, event, autoActivate) {
var entries = this._entries,
entry,
handler,
originalEvent,
button = event.delegateTarget || event.target;
if (!button) {
return event.preventDefault();
}
entry = entries[attr$1(button, 'data-action')];
// when user clicks on the palette and not on an action
if (!entry) {
return;
}
handler = entry.action;
originalEvent = event.originalEvent || event;
// simple action (via callback function)
if (isFunction(handler)) {
if (action === 'click') {
handler(originalEvent, autoActivate);
}
} else {
if (handler[action]) {
handler[action](originalEvent, autoActivate);
}
}
// silence other actions
event.preventDefault();
};
Palette.prototype._layoutChanged = function() {
this._toggleState({});
};
/**
* Do we need to collapse to two columns?
*
* @param {number} availableHeight
* @param {Object} entries
*
* @return {boolean}
*/
Palette.prototype._needsCollapse = function(availableHeight, entries) {
// top margin + bottom toggle + bottom margin
// implementors must override this method if they
// change the palette styles
var margin = 20 + 10 + 20;
var entriesHeight = Object.keys(entries).length * 46;
return availableHeight < entriesHeight + margin;
};
/**
* Close the palette
*/
Palette.prototype.close = function() {
this._toggleState({
open: false,
twoColumn: false
});
};
/**
* Open the palette
*/
Palette.prototype.open = function() {
this._toggleState({ open: true });
};
Palette.prototype.toggle = function(open) {
if (this.isOpen()) {
this.close();
} else {
this.open();
}
};
Palette.prototype.isActiveTool = function(tool) {
return tool && this._activeTool === tool;
};
Palette.prototype.updateToolHighlight = function(name) {
var entriesContainer,
toolsContainer;
if (!this._toolsContainer) {
entriesContainer = query('.djs-palette-entries', this._container);
this._toolsContainer = query('[data-group=tools]', entriesContainer);
}
toolsContainer = this._toolsContainer;
forEach$1(toolsContainer.children, function(tool) {
var actionName = tool.getAttribute('data-action');
if (!actionName) {
return;
}
var toolClasses = classes$1(tool);
actionName = actionName.replace('-tool', '');
if (toolClasses.contains('entry') && actionName === name) {
toolClasses.add('highlighted-entry');
} else {
toolClasses.remove('highlighted-entry');
}
});
};
/**
* Return true if the palette is opened.
*
* @example
*
* palette.open();
*
* if (palette.isOpen()) {
* // yes, we are open
* }
*
* @return {boolean} true if palette is opened
*/
Palette.prototype.isOpen = function() {
return classes$1(this._container).has(PALETTE_OPEN_CLS);
};
/**
* Get container the palette lives in.
*
* @return {Element}
*/
Palette.prototype._getParentContainer = function() {
return this._canvas.getContainer();
};
/* markup definition */
Palette.HTML_MARKUP =
'<div class="djs-palette">' +
'<div class="djs-palette-entries"></div>' +
'<div class="djs-palette-toggle"></div>' +
'</div>';
// helpers //////////////////////
function addClasses(element, classNames) {
var classes = classes$1(element);
var actualClassNames = isArray$3(classNames) ? classNames : classNames.split(/\s+/g);
actualClassNames.forEach(function(cls) {
classes.add(cls);
});
}
function addPaletteEntries(entries, provider) {
var entriesOrUpdater = provider.getPaletteEntries();
if (isFunction(entriesOrUpdater)) {
return entriesOrUpdater(entries);
}
forEach$1(entriesOrUpdater, function(entry, id) {
entries[id] = entry;
});
return entries;
}
var PaletteModule$1 = {
__init__: [ 'palette' ],
palette: [ 'type', Palette ]
};
var LASSO_TOOL_CURSOR = 'crosshair';
function LassoTool(
eventBus, canvas, dragging,
elementRegistry, selection, toolManager,
mouse) {
this._selection = selection;
this._dragging = dragging;
this._mouse = mouse;
var self = this;
// lasso visuals implementation
/**
* A helper that realizes the selection box visual
*/
var visuals = {
create: function(context) {
var container = canvas.getActiveLayer(),
frame;
frame = context.frame = create$1('rect');
attr(frame, {
class: 'djs-lasso-overlay',
width: 1,
height: 1,
x: 0,
y: 0
});
append(container, frame);
},
update: function(context) {
var frame = context.frame,
bbox = context.bbox;
attr(frame, {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height
});
},
remove: function(context) {
if (context.frame) {
remove$1(context.frame);
}
}
};
toolManager.registerTool('lasso', {
tool: 'lasso.selection',
dragging: 'lasso'
});
eventBus.on('lasso.selection.end', function(event) {
var target = event.originalEvent.target;
// only reactive on diagram click
// on some occasions, event.hover is not set and we have to check if the target is an svg
if (!event.hover && !(target instanceof SVGElement)) {
return;
}
eventBus.once('lasso.selection.ended', function() {
self.activateLasso(event.originalEvent, true);
});
});
// lasso interaction implementation
eventBus.on('lasso.end', function(event) {
var bbox = toBBox(event);
var elements = elementRegistry.filter(function(element) {
return element;
});
self.select(elements, bbox);
});
eventBus.on('lasso.start', function(event) {
var context = event.context;
context.bbox = toBBox(event);
visuals.create(context);
});
eventBus.on('lasso.move', function(event) {
var context = event.context;
context.bbox = toBBox(event);
visuals.update(context);
});
eventBus.on('lasso.cleanup', function(event) {
var context = event.context;
visuals.remove(context);
});
// event integration
eventBus.on('element.mousedown', 1500, function(event) {
if (!hasSecondaryModifier(event)) {
return;
}
self.activateLasso(event.originalEvent);
// we've handled the event
return true;
});
}
LassoTool.$inject = [
'eventBus',
'canvas',
'dragging',
'elementRegistry',
'selection',
'toolManager',
'mouse'
];
LassoTool.prototype.activateLasso = function(event, autoActivate) {
this._dragging.init(event, 'lasso', {
autoActivate: autoActivate,
cursor: LASSO_TOOL_CURSOR,
data: {
context: {}
}
});
};
LassoTool.prototype.activateSelection = function(event, autoActivate) {
this._dragging.init(event, 'lasso.selection', {
trapClick: false,
autoActivate: autoActivate,
cursor: LASSO_TOOL_CURSOR,
data: {
context: {}
}
});
};
LassoTool.prototype.select = function(elements, bbox) {
var selectedElements = getEnclosedElements(elements, bbox);
this._selection.select(values(selectedElements));
};
LassoTool.prototype.toggle = function() {
if (this.isActive()) {
return this._dragging.cancel();
}
var mouseEvent = this._mouse.getLastMoveEvent();
this.activateSelection(mouseEvent, !!mouseEvent);
};
LassoTool.prototype.isActive = function() {
var context = this._dragging.context();
return context && /^lasso/.test(context.prefix);
};
function toBBox(event) {
var start = {
x: event.x - event.dx,
y: event.y - event.dy
};
var end = {
x: event.x,
y: event.y
};
var bbox;
if ((start.x <= end.x && start.y < end.y) ||
(start.x < end.x && start.y <= end.y)) {
bbox = {
x: start.x,
y: start.y,
width: end.x - start.x,
height: end.y - start.y
};
} else if ((start.x >= end.x && start.y < end.y) ||
(start.x > end.x && start.y <= end.y)) {
bbox = {
x: end.x,
y: start.y,
width: start.x - end.x,
height: end.y - start.y
};
} else if ((start.x <= end.x && start.y > end.y) ||
(start.x < end.x && start.y >= end.y)) {
bbox = {
x: start.x,
y: end.y,
width: end.x - start.x,
height: start.y - end.y
};
} else if ((start.x >= end.x && start.y > end.y) ||
(start.x > end.x && start.y >= end.y)) {
bbox = {
x: end.x,
y: end.y,
width: start.x - end.x,
height: start.y - end.y
};
} else {
bbox = {
x: end.x,
y: end.y,
width: 0,
height: 0
};
}
return bbox;
}
var LassoToolModule = {
__depends__: [
ToolManagerModule,
MouseModule
],
__init__: [ 'lassoTool' ],
lassoTool: [ 'type', LassoTool ]
};
var HIGH_PRIORITY$1 = 1500;
var HAND_CURSOR = 'grab';
function HandTool(
eventBus, canvas, dragging,
injector, toolManager, mouse) {
this._dragging = dragging;
this._mouse = mouse;
var self = this,
keyboard = injector.get('keyboard', false);
toolManager.registerTool('hand', {
tool: 'hand',
dragging: 'hand.move'
});
eventBus.on('element.mousedown', HIGH_PRIORITY$1, function(event) {
if (!hasPrimaryModifier(event)) {
return;
}
self.activateMove(event.originalEvent, true);
return false;
});
keyboard && keyboard.addListener(HIGH_PRIORITY$1, function(e) {
if (!isSpace(e.keyEvent) || self.isActive()) {
return;
}
var mouseEvent = self._mouse.getLastMoveEvent();
self.activateMove(mouseEvent, !!mouseEvent);
}, 'keyboard.keydown');
keyboard && keyboard.addListener(HIGH_PRIORITY$1, function(e) {
if (!isSpace(e.keyEvent) || !self.isActive()) {
return;
}
self.toggle();
}, 'keyboard.keyup');
eventBus.on('hand.end', function(event) {
var target = event.originalEvent.target;
// only reactive on diagram click
// on some occasions, event.hover is not set and we have to check if the target is an svg
if (!event.hover && !(target instanceof SVGElement)) {
return false;
}
eventBus.once('hand.ended', function() {
self.activateMove(event.originalEvent, { reactivate: true });
});
});
eventBus.on('hand.move.move', function(event) {
var scale = canvas.viewbox().scale;
canvas.scroll({
dx: event.dx * scale,
dy: event.dy * scale
});
});
eventBus.on('hand.move.end', function(event) {
var context = event.context,
reactivate = context.reactivate;
// Don't reactivate if the user is using the keyboard keybinding
if (!hasPrimaryModifier(event) && reactivate) {
eventBus.once('hand.move.ended', function(event) {
self.activateHand(event.originalEvent, true, true);
});
}
return false;
});
}
HandTool.$inject = [
'eventBus',
'canvas',
'dragging',
'injector',
'toolManager',
'mouse'
];
HandTool.prototype.activateMove = function(event, autoActivate, context) {
if (typeof autoActivate === 'object') {
context = autoActivate;
autoActivate = false;
}
this._dragging.init(event, 'hand.move', {
autoActivate: autoActivate,
cursor: HAND_CURSOR,
data: {
context: context || {}
}
});
};
HandTool.prototype.activateHand = function(event, autoActivate, reactivate) {
this._dragging.init(event, 'hand', {
trapClick: false,
autoActivate: autoActivate,
cursor: HAND_CURSOR,
data: {
context: {
reactivate: reactivate
}
}
});
};
HandTool.prototype.toggle = function() {
if (this.isActive()) {
return this._dragging.cancel();
}
var mouseEvent = this._mouse.getLastMoveEvent();
this.activateHand(mouseEvent, !!mouseEvent);
};
HandTool.prototype.isActive = function() {
var context = this._dragging.context();
if (context) {
return /^(hand|hand\.move)$/.test(context.prefix);
}
return false;
};
// helpers //////////
function isSpace(keyEvent) {
return isKey(' ', keyEvent);
}
var HandToolModule = {
__depends__: [
ToolManagerModule,
MouseModule
],
__init__: [ 'handTool' ],
handTool: [ 'type', HandTool ]
};
var MARKER_OK = 'connect-ok',
MARKER_NOT_OK = 'connect-not-ok';
/**
* @class
* @constructor
*
* @param {EventBus} eventBus
* @param {Dragging} dragging
* @param {Connect} connect
* @param {Canvas} canvas
* @param {ToolManager} toolManager
* @param {Rules} rules
* @param {Mouse} mouse
*/
function GlobalConnect(
eventBus, dragging, connect,
canvas, toolManager, rules,
mouse) {
var self = this;
this._dragging = dragging;
this._rules = rules;
this._mouse = mouse;
toolManager.registerTool('global-connect', {
tool: 'global-connect',
dragging: 'global-connect.drag'
});
eventBus.on('global-connect.hover', function(event) {
var context = event.context,
startTarget = event.hover;
var canStartConnect = context.canStartConnect = self.canStartConnect(startTarget);
// simply ignore hover
if (canStartConnect === null) {
return;
}
context.startTarget = startTarget;
canvas.addMarker(startTarget, canStartConnect ? MARKER_OK : MARKER_NOT_OK);
});
eventBus.on([ 'global-connect.out', 'global-connect.cleanup' ], function(event) {
var startTarget = event.context.startTarget,
canStartConnect = event.context.canStartConnect;
if (startTarget) {
canvas.removeMarker(startTarget, canStartConnect ? MARKER_OK : MARKER_NOT_OK);
}
});
eventBus.on([ 'global-connect.ended' ], function(event) {
var context = event.context,
startTarget = context.startTarget,
startPosition = {
x: event.x,
y: event.y
};
var canStartConnect = self.canStartConnect(startTarget);
if (!canStartConnect) {
return;
}
eventBus.once('element.out', function() {
eventBus.once([ 'connect.ended', 'connect.canceled' ], function() {
eventBus.fire('global-connect.drag.ended');
});
connect.start(null, startTarget, startPosition);
});
return false;
});
}
GlobalConnect.$inject = [
'eventBus',
'dragging',
'connect',
'canvas',
'toolManager',
'rules',
'mouse'
];
/**
* Initiates tool activity.
*/
GlobalConnect.prototype.start = function(event, autoActivate) {
this._dragging.init(event, 'global-connect', {
autoActivate: autoActivate,
trapClick: false,
data: {
context: {}
}
});
};
GlobalConnect.prototype.toggle = function() {
if (this.isActive()) {
return this._dragging.cancel();
}
var mouseEvent = this._mouse.getLastMoveEvent();
return this.start(mouseEvent, !!mouseEvent);
};
GlobalConnect.prototype.isActive = function() {
var context = this._dragging.context();
return context && /^global-connect/.test(context.prefix);
};
/**
* Check if source shape can initiate connection.
*
* @param {Shape} startTarget
* @return {boolean}
*/
GlobalConnect.prototype.canStartConnect = function(startTarget) {
return this._rules.allowed('connection.start', { source: startTarget });
};
var GlobalConnectModule = {
__depends__: [
ConnectModule,
RulesModule$1,
DraggingModule,
ToolManagerModule,
MouseModule
],
globalConnect: [ 'type', GlobalConnect ]
};
/**
* A palette provider for BPMN 2.0 elements.
*/
function PaletteProvider(
palette, create, elementFactory,
spaceTool, lassoTool, handTool,
globalConnect, translate) {
this._palette = palette;
this._create = create;
this._elementFactory = elementFactory;
this._spaceTool = spaceTool;
this._lassoTool = lassoTool;
this._handTool = handTool;
this._globalConnect = globalConnect;
this._translate = translate;
palette.registerProvider(this);
}
PaletteProvider.$inject = [
'palette',
'create',
'elementFactory',
'spaceTool',
'lassoTool',
'handTool',
'globalConnect',
'translate'
];
PaletteProvider.prototype.getPaletteEntries = function(element) {
var actions = {},
create = this._create,
elementFactory = this._elementFactory,
spaceTool = this._spaceTool,
lassoTool = this._lassoTool,
handTool = this._handTool,
globalConnect = this._globalConnect,
translate = this._translate;
function createAction(type, group, className, title, options) {
function createListener(event) {
var shape = elementFactory.createShape(assign({ type: type }, options));
if (options) {
var di = getDi(shape);
di.isExpanded = options.isExpanded;
}
create.start(event, shape);
}
var shortType = type.replace(/^bpmn:/, '');
return {
group: group,
className: className,
title: title || translate('Create {type}', { type: shortType }),
action: {
dragstart: createListener,
click: createListener
}
};
}
function createSubprocess(event) {
var subProcess = elementFactory.createShape({
type: 'bpmn:SubProcess',
x: 0,
y: 0,
isExpanded: true
});
var startEvent = elementFactory.createShape({
type: 'bpmn:StartEvent',
x: 40,
y: 82,
parent: subProcess
});
create.start(event, [ subProcess, startEvent ], {
hints: {
autoSelect: [ subProcess ]
}
});
}
function createParticipant(event) {
create.start(event, elementFactory.createParticipantShape());
}
assign(actions, {
'hand-tool': {
group: 'tools',
className: 'bpmn-icon-hand-tool',
title: translate('Activate the hand tool'),
action: {
click: function(event) {
handTool.activateHand(event);
}
}
},
'lasso-tool': {
group: 'tools',
className: 'bpmn-icon-lasso-tool',
title: translate('Activate the lasso tool'),
action: {
click: function(event) {
lassoTool.activateSelection(event);
}
}
},
'space-tool': {
group: 'tools',
className: 'bpmn-icon-space-tool',
title: translate('Activate the create/remove space tool'),
action: {
click: function(event) {
spaceTool.activateSelection(event);
}
}
},
'global-connect-tool': {
group: 'tools',
className: 'bpmn-icon-connection-multi',
title: translate('Activate the global connect tool'),
action: {
click: function(event) {
globalConnect.start(event);
}
}
},
'tool-separator': {
group: 'tools',
separator: true
},
'create.start-event': createAction(
'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none',
translate('Create StartEvent')
),
'create.intermediate-event': createAction(
'bpmn:IntermediateThrowEvent', 'event', 'bpmn-icon-intermediate-event-none',
translate('Create Intermediate/Boundary Event')
),
'create.end-event': createAction(
'bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none',
translate('Create EndEvent')
),
'create.exclusive-gateway': createAction(
'bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none',
translate('Create Gateway')
),
'create.task': createAction(
'bpmn:Task', 'activity', 'bpmn-icon-task',
translate('Create Task')
),
'create.data-object': createAction(
'bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object',
translate('Create DataObjectReference')
),
'create.data-store': createAction(
'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store',
translate('Create DataStoreReference')
),
'create.subprocess-expanded': {
group: 'activity',
className: 'bpmn-icon-subprocess-expanded',
title: translate('Create expanded SubProcess'),
action: {
dragstart: createSubprocess,
click: createSubprocess
}
},
'create.participant-expanded': {
group: 'collaboration',
className: 'bpmn-icon-participant',
title: translate('Create Pool/Participant'),
action: {
dragstart: createParticipant,
click: createParticipant
}
},
'create.group': createAction(
'bpmn:Group', 'artifact', 'bpmn-icon-group',
translate('Create Group')
),
});
return actions;
};
var PaletteModule = {
__depends__: [
PaletteModule$1,
CreateModule,
SpaceToolModule,
LassoToolModule,
HandToolModule,
GlobalConnectModule,
translate
],
__init__: [ 'paletteProvider' ],
paletteProvider: [ 'type', PaletteProvider ]
};
var LOW_PRIORITY = 250;
function BpmnReplacePreview(
eventBus, elementRegistry, elementFactory,
canvas, previewSupport) {
CommandInterceptor.call(this, eventBus);
/**
* Replace the visuals of all elements in the context which can be replaced
*
* @param {Object} context
*/
function replaceVisual(context) {
var replacements = context.canExecute.replacements;
forEach$1(replacements, function(replacement) {
var id = replacement.oldElementId;
var newElement = {
type: replacement.newElementType
};
// if the visual of the element is already replaced
if (context.visualReplacements[id]) {
return;
}
var element = elementRegistry.get(id);
assign(newElement, { x: element.x, y: element.y });
// create a temporary shape
var tempShape = elementFactory.createShape(newElement);
canvas.addShape(tempShape, element.parent);
// select the original SVG element related to the element and hide it
var gfx = query('[data-element-id="' + cssEscape(element.id) + '"]', context.dragGroup);
if (gfx) {
attr(gfx, { display: 'none' });
}
// clone the gfx of the temporary shape and add it to the drag group
var dragger = previewSupport.addDragger(tempShape, context.dragGroup);
context.visualReplacements[id] = dragger;
canvas.removeShape(tempShape);
});
}
/**
* Restore the original visuals of the previously replaced elements
*
* @param {Object} context
*/
function restoreVisual(context) {
var visualReplacements = context.visualReplacements;
forEach$1(visualReplacements, function(dragger, id) {
var originalGfx = query('[data-element-id="' + cssEscape(id) + '"]', context.dragGroup);
if (originalGfx) {
attr(originalGfx, { display: 'inline' });
}
dragger.remove();
if (visualReplacements[id]) {
delete visualReplacements[id];
}
});
}
eventBus.on('shape.move.move', LOW_PRIORITY, function(event) {
var context = event.context,
canExecute = context.canExecute;
if (!context.visualReplacements) {
context.visualReplacements = {};
}
if (canExecute && canExecute.replacements) {
replaceVisual(context);
} else {
restoreVisual(context);
}
});
}
BpmnReplacePreview.$inject = [
'eventBus',
'elementRegistry',
'elementFactory',
'canvas',
'previewSupport'
];
e(BpmnReplacePreview, CommandInterceptor);
var ReplacePreviewModule = {
__depends__: [
PreviewSupportModule
],
__init__: [ 'bpmnReplacePreview' ],
bpmnReplacePreview: [ 'type', BpmnReplacePreview ]
};
var HIGHER_PRIORITY$2 = 1250;
var BOUNDARY_TO_HOST_THRESHOLD = 40;
var TARGET_BOUNDS_PADDING = 20,
TASK_BOUNDS_PADDING = 10;
var TARGET_CENTER_PADDING = 20;
var AXES = [ 'x', 'y' ];
var abs = Math.abs;
/**
* Snap during connect.
*
* @param {EventBus} eventBus
*/
function BpmnConnectSnapping(eventBus) {
eventBus.on([
'connect.hover',
'connect.move',
'connect.end',
], HIGHER_PRIORITY$2, function(event) {
var context = event.context,
canExecute = context.canExecute,
start = context.start,
hover = context.hover,
source = context.source,
target = context.target;
// do NOT snap on CMD
if (event.originalEvent && isCmd(event.originalEvent)) {
return;
}
if (!context.initialConnectionStart) {
context.initialConnectionStart = context.connectionStart;
}
// snap hover
if (canExecute && hover) {
snapToShape(event, hover, getTargetBoundsPadding(hover));
}
if (hover && isAnyType(canExecute, [
'bpmn:Association',
'bpmn:DataInputAssociation',
'bpmn:DataOutputAssociation',
'bpmn:SequenceFlow'
])) {
context.connectionStart = mid$2(start);
// snap hover
if (isAny(hover, [ 'bpmn:Event', 'bpmn:Gateway' ])) {
snapToPosition(event, mid$2(hover));
}
// snap hover
if (isAny(hover, [ 'bpmn:Task', 'bpmn:SubProcess' ])) {
snapToTargetMid(event, hover);
}
// snap source and target
if (is$1(source, 'bpmn:BoundaryEvent') && target === source.host) {
snapBoundaryEventLoop(event);
}
} else if (isType(canExecute, 'bpmn:MessageFlow')) {
if (is$1(start, 'bpmn:Event')) {
// snap start
context.connectionStart = mid$2(start);
}
if (is$1(hover, 'bpmn:Event')) {
// snap hover
snapToPosition(event, mid$2(hover));
}
} else {
// un-snap source
context.connectionStart = context.initialConnectionStart;
}
});
}
BpmnConnectSnapping.$inject = [ 'eventBus' ];
// helpers //////////
// snap to target if event in target
function snapToShape(event, target, padding) {
AXES.forEach(function(axis) {
var dimensionForAxis = getDimensionForAxis(axis, target);
if (event[ axis ] < target[ axis ] + padding) {
setSnapped(event, axis, target[ axis ] + padding);
} else if (event[ axis ] > target[ axis ] + dimensionForAxis - padding) {
setSnapped(event, axis, target[ axis ] + dimensionForAxis - padding);
}
});
}
// snap to target mid if event in target mid
function snapToTargetMid(event, target) {
var targetMid = mid$2(target);
AXES.forEach(function(axis) {
if (isMid(event, target, axis)) {
setSnapped(event, axis, targetMid[ axis ]);
}
});
}
// snap to prevent loop overlapping boundary event
function snapBoundaryEventLoop(event) {
var context = event.context,
source = context.source,
target = context.target;
if (isReverse(context)) {
return;
}
var sourceMid = mid$2(source),
orientation = getOrientation(sourceMid, target, -10),
axes = [];
if (/top|bottom/.test(orientation)) {
axes.push('x');
}
if (/left|right/.test(orientation)) {
axes.push('y');
}
axes.forEach(function(axis) {
var coordinate = event[ axis ], newCoordinate;
if (abs(coordinate - sourceMid[ axis ]) < BOUNDARY_TO_HOST_THRESHOLD) {
if (coordinate > sourceMid[ axis ]) {
newCoordinate = sourceMid[ axis ] + BOUNDARY_TO_HOST_THRESHOLD;
}
else {
newCoordinate = sourceMid[ axis ] - BOUNDARY_TO_HOST_THRESHOLD;
}
setSnapped(event, axis, newCoordinate);
}
});
}
function snapToPosition(event, position) {
setSnapped(event, 'x', position.x);
setSnapped(event, 'y', position.y);
}
function isType(attrs, type) {
return attrs && attrs.type === type;
}
function isAnyType(attrs, types) {
return some(types, function(type) {
return isType(attrs, type);
});
}
function getDimensionForAxis(axis, element) {
return axis === 'x' ? element.width : element.height;
}
function getTargetBoundsPadding(target) {
if (is$1(target, 'bpmn:Task')) {
return TASK_BOUNDS_PADDING;
} else {
return TARGET_BOUNDS_PADDING;
}
}
function isMid(event, target, axis) {
return event[ axis ] > target[ axis ] + TARGET_CENTER_PADDING
&& event[ axis ] < target[ axis ] + getDimensionForAxis(axis, target) - TARGET_CENTER_PADDING;
}
function isReverse(context) {
var hover = context.hover,
source = context.source;
return hover && source && hover === source;
}
/**
* A snap context, containing the (possibly incomplete)
* mappings of drop targets (to identify the snapping)
* to computed snap points.
*/
function SnapContext() {
/**
* Map<String, SnapPoints> mapping drop targets to
* a list of possible snappings.
*
* @type {Object}
*/
this._targets = {};
/**
* Map<String, Point> initial positioning of element
* regarding various snap directions.
*
* @type {Object}
*/
this._snapOrigins = {};
/**
* List of snap locations
*
* @type {Array<string>}
*/
this._snapLocations = [];
/**
* Map<String, Array<Point>> of default snapping locations
*
* @type {Object}
*/
this._defaultSnaps = {};
}
SnapContext.prototype.getSnapOrigin = function(snapLocation) {
return this._snapOrigins[snapLocation];
};
SnapContext.prototype.setSnapOrigin = function(snapLocation, initialValue) {
this._snapOrigins[snapLocation] = initialValue;
if (this._snapLocations.indexOf(snapLocation) === -1) {
this._snapLocations.push(snapLocation);
}
};
SnapContext.prototype.addDefaultSnap = function(type, point) {
var snapValues = this._defaultSnaps[type];
if (!snapValues) {
snapValues = this._defaultSnaps[type] = [];
}
snapValues.push(point);
};
/**
* Return a number of initialized snaps, i.e. snap locations such as
* top-left, mid, bottom-right and so forth.
*
* @return {Array<string>} snapLocations
*/
SnapContext.prototype.getSnapLocations = function() {
return this._snapLocations;
};
/**
* Set the snap locations for this context.
*
* The order of locations determines precedence.
*
* @param {Array<string>} snapLocations
*/
SnapContext.prototype.setSnapLocations = function(snapLocations) {
this._snapLocations = snapLocations;
};
/**
* Get snap points for a given target
*
* @param {Element|string} target
*/
SnapContext.prototype.pointsForTarget = function(target) {
var targetId = target.id || target;
var snapPoints = this._targets[targetId];
if (!snapPoints) {
snapPoints = this._targets[targetId] = new SnapPoints();
snapPoints.initDefaults(this._defaultSnaps);
}
return snapPoints;
};
/**
* Creates the snap points and initializes them with the
* given default values.
*
* @param {Object<string, Array<Point>>} [defaultPoints]
*/
function SnapPoints(defaultSnaps) {
/**
* Map<String, Map<(x|y), Array<number>>> mapping snap locations,
* i.e. top-left, bottom-right, center to actual snap values.
*
* @type {Object}
*/
this._snapValues = {};
}
SnapPoints.prototype.add = function(snapLocation, point) {
var snapValues = this._snapValues[snapLocation];
if (!snapValues) {
snapValues = this._snapValues[snapLocation] = { x: [], y: [] };
}
if (snapValues.x.indexOf(point.x) === -1) {
snapValues.x.push(point.x);
}
if (snapValues.y.indexOf(point.y) === -1) {
snapValues.y.push(point.y);
}
};
SnapPoints.prototype.snap = function(point, snapLocation, axis, tolerance) {
var snappingValues = this._snapValues[snapLocation];
return snappingValues && snapTo(point[axis], snappingValues[axis], tolerance);
};
/**
* Initialize a number of default snapping points.
*
* @param {Object} defaultSnaps
*/
SnapPoints.prototype.initDefaults = function(defaultSnaps) {
var self = this;
forEach$1(defaultSnaps || {}, function(snapPoints, snapLocation) {
forEach$1(snapPoints, function(point) {
self.add(snapLocation, point);
});
});
};
var HIGHER_PRIORITY$1 = 1250;
/**
* Snap during create and move.
*
* @param {EventBus} elementRegistry
* @param {EventBus} eventBus
* @param {Snapping} snapping
*/
function CreateMoveSnapping(elementRegistry, eventBus, snapping) {
var self = this;
this._elementRegistry = elementRegistry;
eventBus.on([
'create.start',
'shape.move.start'
], function(event) {
self.initSnap(event);
});
eventBus.on([
'create.move',
'create.end',
'shape.move.move',
'shape.move.end'
], HIGHER_PRIORITY$1, function(event) {
var context = event.context,
shape = context.shape,
snapContext = context.snapContext,
target = context.target;
if (event.originalEvent && isCmd(event.originalEvent)) {
return;
}
if (isSnapped(event) || !target) {
return;
}
var snapPoints = snapContext.pointsForTarget(target);
if (!snapPoints.initialized) {
snapPoints = self.addSnapTargetPoints(snapPoints, shape, target);
snapPoints.initialized = true;
}
snapping.snap(event, snapPoints);
});
eventBus.on([
'create.cleanup',
'shape.move.cleanup'
], function() {
snapping.hide();
});
}
CreateMoveSnapping.$inject = [
'elementRegistry',
'eventBus',
'snapping'
];
CreateMoveSnapping.prototype.initSnap = function(event) {
var elementRegistry = this._elementRegistry;
var context = event.context,
shape = context.shape,
snapContext = context.snapContext;
if (!snapContext) {
snapContext = context.snapContext = new SnapContext();
}
var shapeMid;
if (elementRegistry.get(shape.id)) {
// move
shapeMid = mid$2(shape, event);
} else {
// create
shapeMid = {
x: event.x + mid$2(shape).x,
y: event.y + mid$2(shape).y
};
}
var shapeTopLeft = {
x: shapeMid.x - shape.width / 2,
y: shapeMid.y - shape.height / 2
},
shapeBottomRight = {
x: shapeMid.x + shape.width / 2,
y: shapeMid.y + shape.height / 2
};
snapContext.setSnapOrigin('mid', {
x: shapeMid.x - event.x,
y: shapeMid.y - event.y
});
// snap labels to mid only
if (isLabel$1(shape)) {
return snapContext;
}
snapContext.setSnapOrigin('top-left', {
x: shapeTopLeft.x - event.x,
y: shapeTopLeft.y - event.y
});
snapContext.setSnapOrigin('bottom-right', {
x: shapeBottomRight.x - event.x,
y: shapeBottomRight.y - event.y
});
return snapContext;
};
CreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
var snapTargets = this.getSnapTargets(shape, target);
forEach$1(snapTargets, function(snapTarget) {
// handle labels
if (isLabel$1(snapTarget)) {
if (isLabel$1(shape)) {
snapPoints.add('mid', mid$2(snapTarget));
}
return;
}
// handle connections
if (isConnection$1(snapTarget)) {
// ignore single segment connections
if (snapTarget.waypoints.length < 3) {
return;
}
// ignore first and last waypoint
var waypoints = snapTarget.waypoints.slice(1, -1);
forEach$1(waypoints, function(waypoint) {
snapPoints.add('mid', waypoint);
});
return;
}
// handle shapes
snapPoints.add('mid', mid$2(snapTarget));
});
if (!isNumber(shape.x) || !isNumber(shape.y)) {
return snapPoints;
}
// snap to original position when moving
if (this._elementRegistry.get(shape.id)) {
snapPoints.add('mid', mid$2(shape));
}
return snapPoints;
};
CreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
return getChildren(target).filter(function(child) {
return !isHidden$1(child);
});
};
// helpers //////////
function isConnection$1(element) {
return !!element.waypoints;
}
function isHidden$1(element) {
return !!element.hidden;
}
function isLabel$1(element) {
return !!element.labelTarget;
}
var HIGH_PRIORITY = 1500;
/**
* Snap during create and move.
*
* @param {EventBus} eventBus
* @param {Injector} injector
*/
function BpmnCreateMoveSnapping(eventBus, injector) {
injector.invoke(CreateMoveSnapping, this);
// creating first participant
eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);
// snap boundary events
eventBus.on([
'create.move',
'create.end',
'shape.move.move',
'shape.move.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
canExecute = context.canExecute,
target = context.target;
var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach);
if (canAttach && !isSnapped(event)) {
snapBoundaryEvent(event, target);
}
});
}
e(BpmnCreateMoveSnapping, CreateMoveSnapping);
BpmnCreateMoveSnapping.$inject = [
'eventBus',
'injector'
];
BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);
var shape = event.shape;
var isMove = !!this._elementRegistry.get(shape.id);
// snap to docking points
forEach$1(shape.outgoing, function(connection) {
var docking = connection.waypoints[0];
docking = docking.original || docking;
snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
});
forEach$1(shape.incoming, function(connection) {
var docking = connection.waypoints[connection.waypoints.length - 1];
docking = docking.original || docking;
snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
});
if (is$1(shape, 'bpmn:Participant')) {
// snap to borders with higher priority
snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
}
return snapContext;
};
BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);
var snapTargets = this.getSnapTargets(shape, target);
forEach$1(snapTargets, function(snapTarget) {
// handle TRBL alignment
//
// * with container elements
// * with text annotations
if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) {
snapPoints.add('top-left', topLeft(snapTarget));
snapPoints.add('bottom-right', bottomRight(snapTarget));
}
});
var elementRegistry = this._elementRegistry;
// snap to docking points if not create mode
forEach$1(shape.incoming, function(connection) {
if (elementRegistry.get(shape.id)) {
if (!includes(snapTargets, connection.source)) {
snapPoints.add('mid', getMid(connection.source));
}
var docking = connection.waypoints[0];
snapPoints.add(connection.id + '-docking', docking.original || docking);
}
});
forEach$1(shape.outgoing, function(connection) {
if (elementRegistry.get(shape.id)) {
if (!includes(snapTargets, connection.target)) {
snapPoints.add('mid', getMid(connection.target));
}
var docking = connection.waypoints[ connection.waypoints.length - 1 ];
snapPoints.add(connection.id + '-docking', docking.original || docking);
}
});
// add sequence flow parents as snap targets
if (is$1(target, 'bpmn:SequenceFlow')) {
snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
}
return snapPoints;
};
BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
.filter(function(snapTarget) {
// do not snap to lanes
return !is$1(snapTarget, 'bpmn:Lane');
});
};
// helpers //////////
function snapBoundaryEvent(event, target) {
var targetTRBL = asTRBL(target);
var direction = getBoundaryAttachment(event, target);
var context = event.context,
shape = context.shape;
var offset;
if (shape.parent) {
offset = { x: 0, y: 0 };
} else {
offset = getMid(shape);
}
if (/top/.test(direction)) {
setSnapped(event, 'y', targetTRBL.top - offset.y);
} else if (/bottom/.test(direction)) {
setSnapped(event, 'y', targetTRBL.bottom - offset.y);
}
if (/left/.test(direction)) {
setSnapped(event, 'x', targetTRBL.left - offset.x);
} else if (/right/.test(direction)) {
setSnapped(event, 'x', targetTRBL.right - offset.x);
}
}
function areAll(elements, type) {
return elements.every(function(el) {
return is$1(el, type);
});
}
function isContainer(element) {
if (is$1(element, 'bpmn:SubProcess') && isExpanded(element)) {
return true;
}
return is$1(element, 'bpmn:Participant');
}
function setSnappedIfConstrained(event) {
var context = event.context,
createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
var top = createConstraints.top,
right = createConstraints.right,
bottom = createConstraints.bottom,
left = createConstraints.left;
if ((left && left >= event.x) || (right && right <= event.x)) {
setSnapped(event, 'x', event.x);
}
if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
setSnapped(event, 'y', event.y);
}
}
function includes(array, value) {
return array.indexOf(value) !== -1;
}
function getDockingSnapOrigin(docking, isMove, event) {
return isMove ? (
{
x: docking.x - event.x,
y: docking.y - event.y
}
) : {
x: docking.x,
y: docking.y
};
}
var HIGHER_PRIORITY = 1250;
/**
* Snap during resize.
*
* @param {EventBus} eventBus
* @param {Snapping} snapping
*/
function ResizeSnapping(eventBus, snapping) {
var self = this;
eventBus.on([ 'resize.start' ], function(event) {
self.initSnap(event);
});
eventBus.on([
'resize.move',
'resize.end',
], HIGHER_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
parent = shape.parent,
direction = context.direction,
snapContext = context.snapContext;
if (event.originalEvent && isCmd(event.originalEvent)) {
return;
}
if (isSnapped(event)) {
return;
}
var snapPoints = snapContext.pointsForTarget(parent);
if (!snapPoints.initialized) {
snapPoints = self.addSnapTargetPoints(snapPoints, shape, parent, direction);
snapPoints.initialized = true;
}
if (isHorizontal(direction)) {
setSnapped(event, 'x', event.x);
}
if (isVertical(direction)) {
setSnapped(event, 'y', event.y);
}
snapping.snap(event, snapPoints);
});
eventBus.on([ 'resize.cleanup' ], function() {
snapping.hide();
});
}
ResizeSnapping.prototype.initSnap = function(event) {
var context = event.context,
shape = context.shape,
direction = context.direction,
snapContext = context.snapContext;
if (!snapContext) {
snapContext = context.snapContext = new SnapContext();
}
var snapOrigin = getSnapOrigin(shape, direction);
snapContext.setSnapOrigin('corner', {
x: snapOrigin.x - event.x,
y: snapOrigin.y - event.y
});
return snapContext;
};
ResizeSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target, direction) {
var snapTargets = this.getSnapTargets(shape, target);
forEach$1(snapTargets, function(snapTarget) {
snapPoints.add('corner', bottomRight(snapTarget));
snapPoints.add('corner', topLeft(snapTarget));
});
snapPoints.add('corner', getSnapOrigin(shape, direction));
return snapPoints;
};
ResizeSnapping.$inject = [
'eventBus',
'snapping'
];
ResizeSnapping.prototype.getSnapTargets = function(shape, target) {
return getChildren(target).filter(function(child) {
return !isAttached(child, shape)
&& !isConnection(child)
&& !isHidden(child)
&& !isLabel(child);
});
};
// helpers //////////
function getSnapOrigin(shape, direction) {
var mid = getMid(shape),
trbl = asTRBL(shape);
var snapOrigin = {
x: mid.x,
y: mid.y
};
if (direction.indexOf('n') !== -1) {
snapOrigin.y = trbl.top;
} else if (direction.indexOf('s') !== -1) {
snapOrigin.y = trbl.bottom;
}
if (direction.indexOf('e') !== -1) {
snapOrigin.x = trbl.right;
} else if (direction.indexOf('w') !== -1) {
snapOrigin.x = trbl.left;
}
return snapOrigin;
}
function isAttached(element, host) {
return element.host === host;
}
function isConnection(element) {
return !!element.waypoints;
}
function isHidden(element) {
return !!element.hidden;
}
function isLabel(element) {
return !!element.labelTarget;
}
function isHorizontal(direction) {
return direction === 'n' || direction === 's';
}
function isVertical(direction) {
return direction === 'e' || direction === 'w';
}
var SNAP_TOLERANCE = 7;
var SNAP_LINE_HIDE_DELAY = 1000;
/**
* Generic snapping feature.
*
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function Snapping(canvas) {
this._canvas = canvas;
// delay hide by 1000 seconds since last snap
this._asyncHide = debounce(bind(this.hide, this), SNAP_LINE_HIDE_DELAY);
}
Snapping.$inject = [ 'canvas' ];
/**
* Snap an event to given snap points.
*
* @param {Event} event
* @param {SnapPoints} snapPoints
*/
Snapping.prototype.snap = function(event, snapPoints) {
var context = event.context,
snapContext = context.snapContext,
snapLocations = snapContext.getSnapLocations();
var snapping = {
x: isSnapped(event, 'x'),
y: isSnapped(event, 'y')
};
forEach$1(snapLocations, function(location) {
var snapOrigin = snapContext.getSnapOrigin(location);
var snapCurrent = {
x: event.x + snapOrigin.x,
y: event.y + snapOrigin.y
};
// snap both axis if not snapped already
forEach$1([ 'x', 'y' ], function(axis) {
var locationSnapping;
if (!snapping[axis]) {
locationSnapping = snapPoints.snap(snapCurrent, location, axis, SNAP_TOLERANCE);
if (locationSnapping !== undefined) {
snapping[axis] = {
value: locationSnapping,
originValue: locationSnapping - snapOrigin[axis]
};
}
}
});
// no need to continue snapping
if (snapping.x && snapping.y) {
return false;
}
});
// show snap lines
this.showSnapLine('vertical', snapping.x && snapping.x.value);
this.showSnapLine('horizontal', snapping.y && snapping.y.value);
// snap event
forEach$1([ 'x', 'y' ], function(axis) {
var axisSnapping = snapping[axis];
if (isObject(axisSnapping)) {
setSnapped(event, axis, axisSnapping.originValue);
}
});
};
Snapping.prototype._createLine = function(orientation) {
var root = this._canvas.getLayer('snap');
var line = create$1('path');
attr(line, { d: 'M0,0 L0,0' });
classes(line).add('djs-snap-line');
append(root, line);
return {
update: function(position) {
if (!isNumber(position)) {
attr(line, { display: 'none' });
} else {
if (orientation === 'horizontal') {
attr(line, {
d: 'M-100000,' + position + ' L+100000,' + position,
display: ''
});
} else {
attr(line, {
d: 'M ' + position + ',-100000 L ' + position + ', +100000',
display: ''
});
}
}
}
};
};
Snapping.prototype._createSnapLines = function() {
this._snapLines = {
horizontal: this._createLine('horizontal'),
vertical: this._createLine('vertical')
};
};
Snapping.prototype.showSnapLine = function(orientation, position) {
var line = this.getSnapLine(orientation);
if (line) {
line.update(position);
}
this._asyncHide();
};
Snapping.prototype.getSnapLine = function(orientation) {
if (!this._snapLines) {
this._createSnapLines();
}
return this._snapLines[orientation];
};
Snapping.prototype.hide = function() {
forEach$1(this._snapLines, function(snapLine) {
snapLine.update();
});
};
var SnappingModule$1 = {
__init__: [
'createMoveSnapping',
'resizeSnapping',
'snapping'
],
createMoveSnapping: [ 'type', CreateMoveSnapping ],
resizeSnapping: [ 'type', ResizeSnapping ],
snapping: [ 'type', Snapping ]
};
var SnappingModule = {
__depends__: [ SnappingModule$1 ],
__init__: [
'connectSnapping',
'createMoveSnapping'
],
connectSnapping: [ 'type', BpmnConnectSnapping ],
createMoveSnapping: [ 'type', BpmnCreateMoveSnapping ]
};
/**
* Provides searching infrastructure
*/
function SearchPad(canvas, eventBus, overlays, selection) {
this._open = false;
this._results = [];
this._eventMaps = [];
this._canvas = canvas;
this._eventBus = eventBus;
this._overlays = overlays;
this._selection = selection;
// setup elements
this._container = domify(SearchPad.BOX_HTML);
this._searchInput = query(SearchPad.INPUT_SELECTOR, this._container);
this._resultsContainer = query(SearchPad.RESULTS_CONTAINER_SELECTOR, this._container);
// attach search pad
this._canvas.getContainer().appendChild(this._container);
// cleanup on destroy
eventBus.on([ 'canvas.destroy', 'diagram.destroy' ], this.close, this);
}
SearchPad.$inject = [
'canvas',
'eventBus',
'overlays',
'selection'
];
/**
* Binds and keeps track of all event listereners
*/
SearchPad.prototype._bindEvents = function() {
var self = this;
function listen(el, selector, type, fn) {
self._eventMaps.push({
el: el,
type: type,
listener: delegate.bind(el, selector, type, fn)
});
}
// close search on clicking anywhere outside
listen(document, 'html', 'click', function(e) {
self.close();
});
// stop event from propagating and closing search
// focus on input
listen(this._container, SearchPad.INPUT_SELECTOR, 'click', function(e) {
e.stopPropagation();
e.delegateTarget.focus();
});
// preselect result on hover
listen(this._container, SearchPad.RESULT_SELECTOR, 'mouseover', function(e) {
e.stopPropagation();
self._scrollToNode(e.delegateTarget);
self._preselect(e.delegateTarget);
});
// selects desired result on mouse click
listen(this._container, SearchPad.RESULT_SELECTOR, 'click', function(e) {
e.stopPropagation();
self._select(e.delegateTarget);
});
// prevent cursor in input from going left and right when using up/down to
// navigate results
listen(this._container, SearchPad.INPUT_SELECTOR, 'keydown', function(e) {
// up
if (e.keyCode === 38) {
e.preventDefault();
}
// down
if (e.keyCode === 40) {
e.preventDefault();
}
});
// handle keyboard input
listen(this._container, SearchPad.INPUT_SELECTOR, 'keyup', function(e) {
// escape
if (e.keyCode === 27) {
return self.close();
}
// enter
if (e.keyCode === 13) {
var selected = self._getCurrentResult();
return selected ? self._select(selected) : self.close();
}
// up
if (e.keyCode === 38) {
return self._scrollToDirection(true);
}
// down
if (e.keyCode === 40) {
return self._scrollToDirection();
}
// left && right
// do not search while navigating text input
if (e.keyCode === 37 || e.keyCode === 39) {
return;
}
// anything else
self._search(e.delegateTarget.value);
});
};
/**
* Unbinds all previously established listeners
*/
SearchPad.prototype._unbindEvents = function() {
this._eventMaps.forEach(function(m) {
delegate.unbind(m.el, m.type, m.listener);
});
};
/**
* Performs a search for the given pattern.
*
* @param {string} pattern
*/
SearchPad.prototype._search = function(pattern) {
var self = this;
this._clearResults();
// do not search on empty query
if (!pattern || pattern === '') {
return;
}
var searchResults = this._searchProvider.find(pattern);
if (!searchResults.length) {
return;
}
// append new results
searchResults.forEach(function(result) {
var id = result.element.id;
var node = self._createResultNode(result, id);
self._results[id] = {
element: result.element,
node: node
};
});
// preselect first result
var node = query(SearchPad.RESULT_SELECTOR, this._resultsContainer);
this._scrollToNode(node);
this._preselect(node);
};
/**
* Navigate to the previous/next result. Defaults to next result.
* @param {boolean} previous
*/
SearchPad.prototype._scrollToDirection = function(previous) {
var selected = this._getCurrentResult();
if (!selected) {
return;
}
var node = previous ? selected.previousElementSibling : selected.nextElementSibling;
if (node) {
this._scrollToNode(node);
this._preselect(node);
}
};
/**
* Scroll to the node if it is not visible.
*
* @param {Element} node
*/
SearchPad.prototype._scrollToNode = function(node) {
if (!node || node === this._getCurrentResult()) {
return;
}
var nodeOffset = node.offsetTop;
var containerScroll = this._resultsContainer.scrollTop;
var bottomScroll = nodeOffset - this._resultsContainer.clientHeight + node.clientHeight;
if (nodeOffset < containerScroll) {
this._resultsContainer.scrollTop = nodeOffset;
} else if (containerScroll < bottomScroll) {
this._resultsContainer.scrollTop = bottomScroll;
}
};
/**
* Clears all results data.
*/
SearchPad.prototype._clearResults = function() {
clear$1(this._resultsContainer);
this._results = [];
this._resetOverlay();
this._eventBus.fire('searchPad.cleared');
};
/**
* Get currently selected result.
*
* @return {Element}
*/
SearchPad.prototype._getCurrentResult = function() {
return query(SearchPad.RESULT_SELECTED_SELECTOR, this._resultsContainer);
};
/**
* Create result DOM element within results container
* that corresponds to a search result.
*
* 'result' : one of the elements returned by SearchProvider
* 'id' : id attribute value to assign to the new DOM node
* return : created DOM element
*
* @param {SearchResult} result
* @param {string} id
* @return {Element}
*/
SearchPad.prototype._createResultNode = function(result, id) {
var node = domify(SearchPad.RESULT_HTML);
// create only if available
if (result.primaryTokens.length > 0) {
createInnerTextNode(node, result.primaryTokens, SearchPad.RESULT_PRIMARY_HTML);
}
// secondary tokens (represent element ID) are allways available
createInnerTextNode(node, result.secondaryTokens, SearchPad.RESULT_SECONDARY_HTML);
attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE, id);
this._resultsContainer.appendChild(node);
return node;
};
/**
* Register search element provider.
*
* SearchProvider.find - provides search function over own elements
* (pattern) => [{ text: <String>, element: <Element>}, ...]
*
* @param {SearchProvider} provider
*/
SearchPad.prototype.registerProvider = function(provider) {
this._searchProvider = provider;
};
/**
* Open search pad.
*/
SearchPad.prototype.open = function() {
if (!this._searchProvider) {
throw new Error('no search provider registered');
}
if (this.isOpen()) {
return;
}
this._bindEvents();
this._open = true;
classes$1(this._container).add('open');
this._searchInput.focus();
this._eventBus.fire('searchPad.opened');
};
/**
* Close search pad.
*/
SearchPad.prototype.close = function() {
if (!this.isOpen()) {
return;
}
this._unbindEvents();
this._open = false;
classes$1(this._container).remove('open');
this._clearResults();
this._searchInput.value = '';
this._searchInput.blur();
this._resetOverlay();
this._eventBus.fire('searchPad.closed');
};
/**
* Toggles search pad on/off.
*/
SearchPad.prototype.toggle = function() {
this.isOpen() ? this.close() : this.open();
};
/**
* Report state of search pad.
*/
SearchPad.prototype.isOpen = function() {
return this._open;
};
/**
* Preselect result entry.
*
* @param {Element} element
*/
SearchPad.prototype._preselect = function(node) {
var selectedNode = this._getCurrentResult();
// already selected
if (node === selectedNode) {
return;
}
// removing preselection from current node
if (selectedNode) {
classes$1(selectedNode).remove(SearchPad.RESULT_SELECTED_CLASS);
}
var id = attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE);
var element = this._results[id].element;
classes$1(node).add(SearchPad.RESULT_SELECTED_CLASS);
this._resetOverlay(element);
this._canvas.scrollToElement(element, { top: 400 });
this._selection.select(element);
this._eventBus.fire('searchPad.preselected', element);
};
/**
* Select result node.
*
* @param {Element} element
*/
SearchPad.prototype._select = function(node) {
var id = attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE);
var element = this._results[id].element;
this.close();
this._resetOverlay();
this._canvas.scrollToElement(element, { top: 400 });
this._selection.select(element);
this._eventBus.fire('searchPad.selected', element);
};
/**
* Reset overlay removes and, optionally, set
* overlay to a new element.
*
* @param {Element} element
*/
SearchPad.prototype._resetOverlay = function(element) {
if (this._overlayId) {
this._overlays.remove(this._overlayId);
}
if (element) {
var box = getBBox(element);
var overlay = constructOverlay(box);
this._overlayId = this._overlays.add(element, overlay);
}
};
/**
* Construct overlay object for the given bounding box.
*
* @param {BoundingBox} box
* @return {Object}
*/
function constructOverlay(box) {
var offset = 6;
var w = box.width + offset * 2;
var h = box.height + offset * 2;
var styles = {
width: w + 'px',
height: h + 'px'
};
var html = domify('<div class="' + SearchPad.OVERLAY_CLASS + '"></div>');
assign$1(html, styles);
return {
position: {
bottom: h - offset,
right: w - offset
},
show: true,
html: html
};
}
/**
* Creates and appends child node from result tokens and HTML template.
*
* @param {Element} node
* @param {Array<Object>} tokens
* @param {string} template
*/
function createInnerTextNode(parentNode, tokens, template) {
var text = createHtmlText(tokens);
var childNode = domify(template);
childNode.innerHTML = text;
parentNode.appendChild(childNode);
}
/**
* Create internal HTML markup from result tokens.
* Caters for highlighting pattern matched tokens.
*
* @param {Array<Object>} tokens
* @return {string}
*/
function createHtmlText(tokens) {
var htmlText = '';
tokens.forEach(function(t) {
if (t.matched) {
htmlText += '<strong class="' + SearchPad.RESULT_HIGHLIGHT_CLASS + '">' + escapeHTML(t.matched) + '</strong>';
} else {
htmlText += escapeHTML(t.normal);
}
});
return htmlText !== '' ? htmlText : null;
}
/**
* CONSTANTS
*/
SearchPad.CONTAINER_SELECTOR = '.djs-search-container';
SearchPad.INPUT_SELECTOR = '.djs-search-input input';
SearchPad.RESULTS_CONTAINER_SELECTOR = '.djs-search-results';
SearchPad.RESULT_SELECTOR = '.djs-search-result';
SearchPad.RESULT_SELECTED_CLASS = 'djs-search-result-selected';
SearchPad.RESULT_SELECTED_SELECTOR = '.' + SearchPad.RESULT_SELECTED_CLASS;
SearchPad.RESULT_ID_ATTRIBUTE = 'data-result-id';
SearchPad.RESULT_HIGHLIGHT_CLASS = 'djs-search-highlight';
SearchPad.OVERLAY_CLASS = 'djs-search-overlay';
SearchPad.BOX_HTML =
'<div class="djs-search-container djs-draggable djs-scrollable">' +
'<div class="djs-search-input">' +
'<input type="text"/>' +
'</div>' +
'<div class="djs-search-results"></div>' +
'</div>';
SearchPad.RESULT_HTML =
'<div class="djs-search-result"></div>';
SearchPad.RESULT_PRIMARY_HTML =
'<div class="djs-search-result-primary"></div>';
SearchPad.RESULT_SECONDARY_HTML =
'<p class="djs-search-result-secondary"></p>';
var SearchPadModule = {
__depends__: [
OverlaysModule,
SelectionModule
],
searchPad: [ 'type', SearchPad ]
};
/**
* Provides ability to search through BPMN elements
*/
function BpmnSearchProvider(elementRegistry, searchPad, canvas) {
this._elementRegistry = elementRegistry;
this._canvas = canvas;
searchPad.registerProvider(this);
}
BpmnSearchProvider.$inject = [
'elementRegistry',
'searchPad',
'canvas'
];
/**
* Finds all elements that match given pattern
*
* <Result> :
* {
* primaryTokens: <Array<Token>>,
* secondaryTokens: <Array<Token>>,
* element: <Element>
* }
*
* <Token> :
* {
* normal|matched: <string>
* }
*
* @param {string} pattern
* @return {Array<Result>}
*/
BpmnSearchProvider.prototype.find = function(pattern) {
var rootElement = this._canvas.getRootElement();
var elements = this._elementRegistry.filter(function(element) {
if (element.labelTarget) {
return false;
}
return true;
});
// do not include root element
elements = filter(elements, function(element) {
return element !== rootElement;
});
elements = map(elements, function(element) {
return {
primaryTokens: matchAndSplit(getLabel(element), pattern),
secondaryTokens: matchAndSplit(element.id, pattern),
element: element
};
});
// exclude non-matched elements
elements = filter(elements, function(element) {
return hasMatched(element.primaryTokens) || hasMatched(element.secondaryTokens);
});
elements = sortBy(elements, function(element) {
return getLabel(element.element) + element.element.id;
});
return elements;
};
function hasMatched(tokens) {
var matched = filter(tokens, function(t) {
return !!t.matched;
});
return matched.length > 0;
}
function matchAndSplit(text, pattern) {
var tokens = [],
originalText = text;
if (!text) {
return tokens;
}
text = text.toLowerCase();
pattern = pattern.toLowerCase();
var i = text.indexOf(pattern);
if (i > -1) {
if (i !== 0) {
tokens.push({
normal: originalText.substr(0, i)
});
}
tokens.push({
matched: originalText.substr(i, pattern.length)
});
if (pattern.length + i < text.length) {
tokens.push({
normal: originalText.substr(pattern.length + i, text.length)
});
}
} else {
tokens.push({
normal: originalText
});
}
return tokens;
}
var SearchModule = {
__depends__: [
SearchPadModule
],
__init__: [ 'bpmnSearch' ],
bpmnSearch: [ 'type', BpmnSearchProvider ]
};
var initialDiagram =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" ' +
'xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" ' +
'xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" ' +
'targetNamespace="http://bpmn.io/schema/bpmn" ' +
'id="Definitions_1">' +
'<bpmn:process id="Process_1" isExecutable="false">' +
'<bpmn:startEvent id="StartEvent_1"/>' +
'</bpmn:process>' +
'<bpmndi:BPMNDiagram id="BPMNDiagram_1">' +
'<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">' +
'<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">' +
'<dc:Bounds height="36.0" width="36.0" x="173.0" y="102.0"/>' +
'</bpmndi:BPMNShape>' +
'</bpmndi:BPMNPlane>' +
'</bpmndi:BPMNDiagram>' +
'</bpmn:definitions>';
/**
* A modeler for BPMN 2.0 diagrams.
*
*
* ## Extending the Modeler
*
* In order to extend the viewer pass extension modules to bootstrap via the
* `additionalModules` option. An extension module is an object that exposes
* named services.
*
* The following example depicts the integration of a simple
* logging component that integrates with interaction events:
*
*
* ```javascript
*
* // logging component
* function InteractionLogger(eventBus) {
* eventBus.on('element.hover', function(event) {
* console.log()
* })
* }
*
* InteractionLogger.$inject = [ 'eventBus' ]; // minification save
*
* // extension module
* var extensionModule = {
* __init__: [ 'interactionLogger' ],
* interactionLogger: [ 'type', InteractionLogger ]
* };
*
* // extend the viewer
* var bpmnModeler = new Modeler({ additionalModules: [ extensionModule ] });
* bpmnModeler.importXML(...);
* ```
*
*
* ## Customizing / Replacing Components
*
* You can replace individual diagram components by redefining them in override modules.
* This works for all components, including those defined in the core.
*
* Pass in override modules via the `options.additionalModules` flag like this:
*
* ```javascript
* function CustomContextPadProvider(contextPad) {
*
* contextPad.registerProvider(this);
*
* this.getContextPadEntries = function(element) {
* // no entries, effectively disable the context pad
* return {};
* };
* }
*
* CustomContextPadProvider.$inject = [ 'contextPad' ];
*
* var overrideModule = {
* contextPadProvider: [ 'type', CustomContextPadProvider ]
* };
*
* var bpmnModeler = new Modeler({ additionalModules: [ overrideModule ]});
* ```
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {string|number} [options.width] the width of the viewer
* @param {string|number} [options.height] the height of the viewer
* @param {Object} [options.moddleExtensions] extension packages to provide
* @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
* @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
*/
function Modeler(options) {
BaseModeler.call(this, options);
}
e(Modeler, BaseModeler);
Modeler.Viewer = Viewer;
Modeler.NavigatedViewer = NavigatedViewer;
/**
* The createDiagram result.
*
* @typedef {Object} CreateDiagramResult
*
* @property {Array<string>} warnings
*/
/**
* The createDiagram error.
*
* @typedef {Error} CreateDiagramError
*
* @property {Array<string>} warnings
*/
/**
* Create a new diagram to start modeling.
*
* Returns {Promise<CreateDiagramResult, CreateDiagramError>}
*/
Modeler.prototype.createDiagram = wrapForCompatibility(function createDiagram() {
return this.importXML(initialDiagram);
});
Modeler.prototype._interactionModules = [
// non-modeling components
KeyboardMoveModule,
MoveCanvasModule,
TouchModule,
ZoomScrollModule
];
Modeler.prototype._modelingModules = [
// modeling components
AlignElementsModule,
AutoPlaceModule,
AutoScrollModule,
AutoResizeModule,
BendpointsModule,
ConnectModule,
ConnectionPreviewModule,
ContextPadModule,
CopyPasteModule,
CreateModule,
DistributeElementsModule,
EditorActionsModule,
GridSnappingModule,
InteractionEventsModule,
KeyboardModule,
KeyboardMoveSelectionModule,
LabelEditingModule,
ModelingModule,
MoveModule,
PaletteModule,
ReplacePreviewModule,
ResizeModule,
SnappingModule,
SearchModule
];
// modules the modeler is composed of
//
// - viewer modules
// - interaction modules
// - modeling modules
Modeler.prototype._modules = [].concat(
Viewer.prototype._modules,
Modeler.prototype._interactionModules,
Modeler.prototype._modelingModules
);
return Modeler;
}));