1/*! 2 * bpmn-js - bpmn-modeler v8.8.2 3 * 4 * Copyright (c) 2014-present, camunda Services GmbH 5 * 6 * Released under the bpmn.io license 7 * http://bpmn.io/license 8 * 9 * Source Code: https://github.com/bpmn-io/bpmn-js 10 * 11 * Date: 2021-10-20 12 */ 13(function (global, factory) { 14 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 15 typeof define === 'function' && define.amd ? define(factory) : 16 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BpmnJS = factory()); 17}(this, (function () { 'use strict'; 18 19 var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 20 21 var inherits_browser = {exports: {}}; 22 23 if (typeof Object.create === 'function') { 24 // implementation from standard node.js 'util' module 25 inherits_browser.exports = function inherits(ctor, superCtor) { 26 if (superCtor) { 27 ctor.super_ = superCtor; 28 ctor.prototype = Object.create(superCtor.prototype, { 29 constructor: { 30 value: ctor, 31 enumerable: false, 32 writable: true, 33 configurable: true 34 } 35 }); 36 } 37 }; 38 } else { 39 // old school shim for old browsers 40 inherits_browser.exports = function inherits(ctor, superCtor) { 41 if (superCtor) { 42 ctor.super_ = superCtor; 43 var TempCtor = function () {}; 44 TempCtor.prototype = superCtor.prototype; 45 ctor.prototype = new TempCtor(); 46 ctor.prototype.constructor = ctor; 47 } 48 }; 49 } 50 51 var inherits$1 = inherits_browser.exports; 52 53 function createCommonjsModule(fn, module) { 54 return module = { exports: {} }, fn(module, module.exports), module.exports; 55 } 56 57 var hat_1 = createCommonjsModule(function (module) { 58 var hat = module.exports = function (bits, base) { 59 if (!base) base = 16; 60 if (bits === undefined) bits = 128; 61 if (bits <= 0) return '0'; 62 63 var digits = Math.log(Math.pow(2, bits)) / Math.log(base); 64 for (var i = 2; digits === Infinity; i *= 2) { 65 digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i; 66 } 67 68 var rem = digits - Math.floor(digits); 69 70 var res = ''; 71 72 for (var i = 0; i < Math.floor(digits); i++) { 73 var x = Math.floor(Math.random() * base).toString(base); 74 res = x + res; 75 } 76 77 if (rem) { 78 var b = Math.pow(base, rem); 79 var x = Math.floor(Math.random() * b).toString(base); 80 res = x + res; 81 } 82 83 var parsed = parseInt(res, base); 84 if (parsed !== Infinity && parsed >= Math.pow(2, bits)) { 85 return hat(bits, base) 86 } 87 else return res; 88 }; 89 90 hat.rack = function (bits, base, expandBy) { 91 var fn = function (data) { 92 var iters = 0; 93 do { 94 if (iters ++ > 10) { 95 if (expandBy) bits += expandBy; 96 else throw new Error('too many ID collisions, use more bits') 97 } 98 99 var id = hat(bits, base); 100 } while (Object.hasOwnProperty.call(hats, id)); 101 102 hats[id] = data; 103 return id; 104 }; 105 var hats = fn.hats = {}; 106 107 fn.get = function (id) { 108 return fn.hats[id]; 109 }; 110 111 fn.set = function (id, value) { 112 fn.hats[id] = value; 113 return fn; 114 }; 115 116 fn.bits = bits || 128; 117 fn.base = base || 16; 118 return fn; 119 }; 120 }); 121 122 /** 123 * Create a new id generator / cache instance. 124 * 125 * You may optionally provide a seed that is used internally. 126 * 127 * @param {Seed} seed 128 */ 129 130 function Ids(seed) { 131 if (!(this instanceof Ids)) { 132 return new Ids(seed); 133 } 134 135 seed = seed || [128, 36, 1]; 136 this._seed = seed.length ? hat_1.rack(seed[0], seed[1], seed[2]) : seed; 137 } 138 /** 139 * Generate a next id. 140 * 141 * @param {Object} [element] element to bind the id to 142 * 143 * @return {String} id 144 */ 145 146 Ids.prototype.next = function (element) { 147 return this._seed(element || true); 148 }; 149 /** 150 * Generate a next id with a given prefix. 151 * 152 * @param {Object} [element] element to bind the id to 153 * 154 * @return {String} id 155 */ 156 157 158 Ids.prototype.nextPrefixed = function (prefix, element) { 159 var id; 160 161 do { 162 id = prefix + this.next(true); 163 } while (this.assigned(id)); // claim {prefix}{random} 164 165 166 this.claim(id, element); // return 167 168 return id; 169 }; 170 /** 171 * Manually claim an existing id. 172 * 173 * @param {String} id 174 * @param {String} [element] element the id is claimed by 175 */ 176 177 178 Ids.prototype.claim = function (id, element) { 179 this._seed.set(id, element || true); 180 }; 181 /** 182 * Returns true if the given id has already been assigned. 183 * 184 * @param {String} id 185 * @return {Boolean} 186 */ 187 188 189 Ids.prototype.assigned = function (id) { 190 return this._seed.get(id) || false; 191 }; 192 /** 193 * Unclaim an id. 194 * 195 * @param {String} id the id to unclaim 196 */ 197 198 199 Ids.prototype.unclaim = function (id) { 200 delete this._seed.hats[id]; 201 }; 202 /** 203 * Clear all claimed ids. 204 */ 205 206 207 Ids.prototype.clear = function () { 208 var hats = this._seed.hats, 209 id; 210 211 for (id in hats) { 212 this.unclaim(id); 213 } 214 }; 215 216 /** 217 * Flatten array, one level deep. 218 * 219 * @param {Array<?>} arr 220 * 221 * @return {Array<?>} 222 */ 223 function flatten(arr) { 224 return Array.prototype.concat.apply([], arr); 225 } 226 227 var nativeToString = Object.prototype.toString; 228 var nativeHasOwnProperty = Object.prototype.hasOwnProperty; 229 function isUndefined$1(obj) { 230 return obj === undefined; 231 } 232 function isDefined(obj) { 233 return obj !== undefined; 234 } 235 function isNil(obj) { 236 return obj == null; 237 } 238 function isArray$2(obj) { 239 return nativeToString.call(obj) === '[object Array]'; 240 } 241 function isObject(obj) { 242 return nativeToString.call(obj) === '[object Object]'; 243 } 244 function isNumber(obj) { 245 return nativeToString.call(obj) === '[object Number]'; 246 } 247 function isFunction(obj) { 248 var tag = nativeToString.call(obj); 249 return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]'; 250 } 251 function isString(obj) { 252 return nativeToString.call(obj) === '[object String]'; 253 } 254 /** 255 * Ensure collection is an array. 256 * 257 * @param {Object} obj 258 */ 259 260 function ensureArray(obj) { 261 if (isArray$2(obj)) { 262 return; 263 } 264 265 throw new Error('must supply array'); 266 } 267 /** 268 * Return true, if target owns a property with the given key. 269 * 270 * @param {Object} target 271 * @param {String} key 272 * 273 * @return {Boolean} 274 */ 275 276 function has(target, key) { 277 return nativeHasOwnProperty.call(target, key); 278 } 279 280 /** 281 * Find element in collection. 282 * 283 * @param {Array|Object} collection 284 * @param {Function|Object} matcher 285 * 286 * @return {Object} 287 */ 288 289 function find(collection, matcher) { 290 matcher = toMatcher(matcher); 291 var match; 292 forEach(collection, function (val, key) { 293 if (matcher(val, key)) { 294 match = val; 295 return false; 296 } 297 }); 298 return match; 299 } 300 /** 301 * Find element index in collection. 302 * 303 * @param {Array|Object} collection 304 * @param {Function} matcher 305 * 306 * @return {Object} 307 */ 308 309 function findIndex(collection, matcher) { 310 matcher = toMatcher(matcher); 311 var idx = isArray$2(collection) ? -1 : undefined; 312 forEach(collection, function (val, key) { 313 if (matcher(val, key)) { 314 idx = key; 315 return false; 316 } 317 }); 318 return idx; 319 } 320 /** 321 * Find element in collection. 322 * 323 * @param {Array|Object} collection 324 * @param {Function} matcher 325 * 326 * @return {Array} result 327 */ 328 329 function filter(collection, matcher) { 330 var result = []; 331 forEach(collection, function (val, key) { 332 if (matcher(val, key)) { 333 result.push(val); 334 } 335 }); 336 return result; 337 } 338 /** 339 * Iterate over collection; returning something 340 * (non-undefined) will stop iteration. 341 * 342 * @param {Array|Object} collection 343 * @param {Function} iterator 344 * 345 * @return {Object} return result that stopped the iteration 346 */ 347 348 function forEach(collection, iterator) { 349 var val, result; 350 351 if (isUndefined$1(collection)) { 352 return; 353 } 354 355 var convertKey = isArray$2(collection) ? toNum : identity; 356 357 for (var key in collection) { 358 if (has(collection, key)) { 359 val = collection[key]; 360 result = iterator(val, convertKey(key)); 361 362 if (result === false) { 363 return val; 364 } 365 } 366 } 367 } 368 /** 369 * Return collection without element. 370 * 371 * @param {Array} arr 372 * @param {Function} matcher 373 * 374 * @return {Array} 375 */ 376 377 function without(arr, matcher) { 378 if (isUndefined$1(arr)) { 379 return []; 380 } 381 382 ensureArray(arr); 383 matcher = toMatcher(matcher); 384 return arr.filter(function (el, idx) { 385 return !matcher(el, idx); 386 }); 387 } 388 /** 389 * Reduce collection, returning a single result. 390 * 391 * @param {Object|Array} collection 392 * @param {Function} iterator 393 * @param {Any} result 394 * 395 * @return {Any} result returned from last iterator 396 */ 397 398 function reduce(collection, iterator, result) { 399 forEach(collection, function (value, idx) { 400 result = iterator(result, value, idx); 401 }); 402 return result; 403 } 404 /** 405 * Return true if every element in the collection 406 * matches the criteria. 407 * 408 * @param {Object|Array} collection 409 * @param {Function} matcher 410 * 411 * @return {Boolean} 412 */ 413 414 function every(collection, matcher) { 415 return !!reduce(collection, function (matches, val, key) { 416 return matches && matcher(val, key); 417 }, true); 418 } 419 /** 420 * Return true if some elements in the collection 421 * match the criteria. 422 * 423 * @param {Object|Array} collection 424 * @param {Function} matcher 425 * 426 * @return {Boolean} 427 */ 428 429 function some(collection, matcher) { 430 return !!find(collection, matcher); 431 } 432 /** 433 * Transform a collection into another collection 434 * by piping each member through the given fn. 435 * 436 * @param {Object|Array} collection 437 * @param {Function} fn 438 * 439 * @return {Array} transformed collection 440 */ 441 442 function map$1(collection, fn) { 443 var result = []; 444 forEach(collection, function (val, key) { 445 result.push(fn(val, key)); 446 }); 447 return result; 448 } 449 /** 450 * Get the collections keys. 451 * 452 * @param {Object|Array} collection 453 * 454 * @return {Array} 455 */ 456 457 function keys(collection) { 458 return collection && Object.keys(collection) || []; 459 } 460 /** 461 * Shorthand for `keys(o).length`. 462 * 463 * @param {Object|Array} collection 464 * 465 * @return {Number} 466 */ 467 468 function size(collection) { 469 return keys(collection).length; 470 } 471 /** 472 * Get the values in the collection. 473 * 474 * @param {Object|Array} collection 475 * 476 * @return {Array} 477 */ 478 479 function values(collection) { 480 return map$1(collection, function (val) { 481 return val; 482 }); 483 } 484 /** 485 * Group collection members by attribute. 486 * 487 * @param {Object|Array} collection 488 * @param {Function} extractor 489 * 490 * @return {Object} map with { attrValue => [ a, b, c ] } 491 */ 492 493 function groupBy(collection, extractor) { 494 var grouped = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 495 extractor = toExtractor(extractor); 496 forEach(collection, function (val) { 497 var discriminator = extractor(val) || '_'; 498 var group = grouped[discriminator]; 499 500 if (!group) { 501 group = grouped[discriminator] = []; 502 } 503 504 group.push(val); 505 }); 506 return grouped; 507 } 508 function uniqueBy(extractor) { 509 extractor = toExtractor(extractor); 510 var grouped = {}; 511 512 for (var _len = arguments.length, collections = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 513 collections[_key - 1] = arguments[_key]; 514 } 515 516 forEach(collections, function (c) { 517 return groupBy(c, extractor, grouped); 518 }); 519 var result = map$1(grouped, function (val, key) { 520 return val[0]; 521 }); 522 return result; 523 } 524 var unionBy = uniqueBy; 525 /** 526 * Sort collection by criteria. 527 * 528 * @param {Object|Array} collection 529 * @param {String|Function} extractor 530 * 531 * @return {Array} 532 */ 533 534 function sortBy(collection, extractor) { 535 extractor = toExtractor(extractor); 536 var sorted = []; 537 forEach(collection, function (value, key) { 538 var disc = extractor(value, key); 539 var entry = { 540 d: disc, 541 v: value 542 }; 543 544 for (var idx = 0; idx < sorted.length; idx++) { 545 var d = sorted[idx].d; 546 547 if (disc < d) { 548 sorted.splice(idx, 0, entry); 549 return; 550 } 551 } // not inserted, append (!) 552 553 554 sorted.push(entry); 555 }); 556 return map$1(sorted, function (e) { 557 return e.v; 558 }); 559 } 560 /** 561 * Create an object pattern matcher. 562 * 563 * @example 564 * 565 * const matcher = matchPattern({ id: 1 }); 566 * 567 * var element = find(elements, matcher); 568 * 569 * @param {Object} pattern 570 * 571 * @return {Function} matcherFn 572 */ 573 574 function matchPattern(pattern) { 575 return function (el) { 576 return every(pattern, function (val, key) { 577 return el[key] === val; 578 }); 579 }; 580 } 581 582 function toExtractor(extractor) { 583 return isFunction(extractor) ? extractor : function (e) { 584 return e[extractor]; 585 }; 586 } 587 588 function toMatcher(matcher) { 589 return isFunction(matcher) ? matcher : function (e) { 590 return e === matcher; 591 }; 592 } 593 594 function identity(arg) { 595 return arg; 596 } 597 598 function toNum(arg) { 599 return Number(arg); 600 } 601 602 /** 603 * Debounce fn, calling it only once if 604 * the given time elapsed between calls. 605 * 606 * @param {Function} fn 607 * @param {Number} timeout 608 * 609 * @return {Function} debounced function 610 */ 611 function debounce(fn, timeout) { 612 var timer; 613 var lastArgs; 614 var lastThis; 615 var lastNow; 616 617 function fire() { 618 var now = Date.now(); 619 var scheduledDiff = lastNow + timeout - now; 620 621 if (scheduledDiff > 0) { 622 return schedule(scheduledDiff); 623 } 624 625 fn.apply(lastThis, lastArgs); 626 timer = lastNow = lastArgs = lastThis = undefined; 627 } 628 629 function schedule(timeout) { 630 timer = setTimeout(fire, timeout); 631 } 632 633 return function () { 634 lastNow = Date.now(); 635 636 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 637 args[_key] = arguments[_key]; 638 } 639 640 lastArgs = args; 641 lastThis = this; // ensure an execution is scheduled 642 643 if (!timer) { 644 schedule(timeout); 645 } 646 }; 647 } 648 /** 649 * Bind function against target <this>. 650 * 651 * @param {Function} fn 652 * @param {Object} target 653 * 654 * @return {Function} bound function 655 */ 656 657 function bind$2(fn, target) { 658 return fn.bind(target); 659 } 660 661 function _extends() { 662 _extends = Object.assign || function (target) { 663 for (var i = 1; i < arguments.length; i++) { 664 var source = arguments[i]; 665 666 for (var key in source) { 667 if (Object.prototype.hasOwnProperty.call(source, key)) { 668 target[key] = source[key]; 669 } 670 } 671 } 672 673 return target; 674 }; 675 676 return _extends.apply(this, arguments); 677 } 678 679 /** 680 * Convenience wrapper for `Object.assign`. 681 * 682 * @param {Object} target 683 * @param {...Object} others 684 * 685 * @return {Object} the target 686 */ 687 688 function assign(target) { 689 for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 690 others[_key - 1] = arguments[_key]; 691 } 692 693 return _extends.apply(void 0, [target].concat(others)); 694 } 695 /** 696 * Pick given properties from the target object. 697 * 698 * @param {Object} target 699 * @param {Array} properties 700 * 701 * @return {Object} target 702 */ 703 704 function pick(target, properties) { 705 var result = {}; 706 var obj = Object(target); 707 forEach(properties, function (prop) { 708 if (prop in obj) { 709 result[prop] = target[prop]; 710 } 711 }); 712 return result; 713 } 714 /** 715 * Pick all target properties, excluding the given ones. 716 * 717 * @param {Object} target 718 * @param {Array} properties 719 * 720 * @return {Object} target 721 */ 722 723 function omit(target, properties) { 724 var result = {}; 725 var obj = Object(target); 726 forEach(obj, function (prop, key) { 727 if (properties.indexOf(key) === -1) { 728 result[key] = prop; 729 } 730 }); 731 return result; 732 } 733 734 /** 735 * Set attribute `name` to `val`, or get attr `name`. 736 * 737 * @param {Element} el 738 * @param {String} name 739 * @param {String} [val] 740 * @api public 741 */ 742 function attr$1(el, name, val) { 743 // get 744 if (arguments.length == 2) { 745 return el.getAttribute(name); 746 } 747 748 // remove 749 if (val === null) { 750 return el.removeAttribute(name); 751 } 752 753 // set 754 el.setAttribute(name, val); 755 756 return el; 757 } 758 759 var indexOf$1 = [].indexOf; 760 761 var indexof = function(arr, obj){ 762 if (indexOf$1) return arr.indexOf(obj); 763 for (var i = 0; i < arr.length; ++i) { 764 if (arr[i] === obj) return i; 765 } 766 return -1; 767 }; 768 769 /** 770 * Taken from https://github.com/component/classes 771 * 772 * Without the component bits. 773 */ 774 775 /** 776 * Whitespace regexp. 777 */ 778 779 var re$1 = /\s+/; 780 781 /** 782 * toString reference. 783 */ 784 785 var toString$1 = Object.prototype.toString; 786 787 /** 788 * Wrap `el` in a `ClassList`. 789 * 790 * @param {Element} el 791 * @return {ClassList} 792 * @api public 793 */ 794 795 function classes$1(el) { 796 return new ClassList$1(el); 797 } 798 799 /** 800 * Initialize a new ClassList for `el`. 801 * 802 * @param {Element} el 803 * @api private 804 */ 805 806 function ClassList$1(el) { 807 if (!el || !el.nodeType) { 808 throw new Error('A DOM element reference is required'); 809 } 810 this.el = el; 811 this.list = el.classList; 812 } 813 814 /** 815 * Add class `name` if not already present. 816 * 817 * @param {String} name 818 * @return {ClassList} 819 * @api public 820 */ 821 822 ClassList$1.prototype.add = function (name) { 823 // classList 824 if (this.list) { 825 this.list.add(name); 826 return this; 827 } 828 829 // fallback 830 var arr = this.array(); 831 var i = indexof(arr, name); 832 if (!~i) arr.push(name); 833 this.el.className = arr.join(' '); 834 return this; 835 }; 836 837 /** 838 * Remove class `name` when present, or 839 * pass a regular expression to remove 840 * any which match. 841 * 842 * @param {String|RegExp} name 843 * @return {ClassList} 844 * @api public 845 */ 846 847 ClassList$1.prototype.remove = function (name) { 848 if ('[object RegExp]' == toString$1.call(name)) { 849 return this.removeMatching(name); 850 } 851 852 // classList 853 if (this.list) { 854 this.list.remove(name); 855 return this; 856 } 857 858 // fallback 859 var arr = this.array(); 860 var i = indexof(arr, name); 861 if (~i) arr.splice(i, 1); 862 this.el.className = arr.join(' '); 863 return this; 864 }; 865 866 /** 867 * Remove all classes matching `re`. 868 * 869 * @param {RegExp} re 870 * @return {ClassList} 871 * @api private 872 */ 873 874 ClassList$1.prototype.removeMatching = function (re) { 875 var arr = this.array(); 876 for (var i = 0; i < arr.length; i++) { 877 if (re.test(arr[i])) { 878 this.remove(arr[i]); 879 } 880 } 881 return this; 882 }; 883 884 /** 885 * Toggle class `name`, can force state via `force`. 886 * 887 * For browsers that support classList, but do not support `force` yet, 888 * the mistake will be detected and corrected. 889 * 890 * @param {String} name 891 * @param {Boolean} force 892 * @return {ClassList} 893 * @api public 894 */ 895 896 ClassList$1.prototype.toggle = function (name, force) { 897 // classList 898 if (this.list) { 899 if ('undefined' !== typeof force) { 900 if (force !== this.list.toggle(name, force)) { 901 this.list.toggle(name); // toggle again to correct 902 } 903 } else { 904 this.list.toggle(name); 905 } 906 return this; 907 } 908 909 // fallback 910 if ('undefined' !== typeof force) { 911 if (!force) { 912 this.remove(name); 913 } else { 914 this.add(name); 915 } 916 } else { 917 if (this.has(name)) { 918 this.remove(name); 919 } else { 920 this.add(name); 921 } 922 } 923 924 return this; 925 }; 926 927 /** 928 * Return an array of classes. 929 * 930 * @return {Array} 931 * @api public 932 */ 933 934 ClassList$1.prototype.array = function () { 935 var className = this.el.getAttribute('class') || ''; 936 var str = className.replace(/^\s+|\s+$/g, ''); 937 var arr = str.split(re$1); 938 if ('' === arr[0]) arr.shift(); 939 return arr; 940 }; 941 942 /** 943 * Check if class `name` is present. 944 * 945 * @param {String} name 946 * @return {ClassList} 947 * @api public 948 */ 949 950 ClassList$1.prototype.has = ClassList$1.prototype.contains = function (name) { 951 return this.list ? this.list.contains(name) : !!~indexof(this.array(), name); 952 }; 953 954 /** 955 * Remove all children from the given element. 956 */ 957 function clear$1(el) { 958 959 var c; 960 961 while (el.childNodes.length) { 962 c = el.childNodes[0]; 963 el.removeChild(c); 964 } 965 966 return el; 967 } 968 969 var proto = typeof Element !== 'undefined' ? Element.prototype : {}; 970 var vendor = proto.matches 971 || proto.matchesSelector 972 || proto.webkitMatchesSelector 973 || proto.mozMatchesSelector 974 || proto.msMatchesSelector 975 || proto.oMatchesSelector; 976 977 var matchesSelector = match; 978 979 /** 980 * Match `el` to `selector`. 981 * 982 * @param {Element} el 983 * @param {String} selector 984 * @return {Boolean} 985 * @api public 986 */ 987 988 function match(el, selector) { 989 if (!el || el.nodeType !== 1) return false; 990 if (vendor) return vendor.call(el, selector); 991 var nodes = el.parentNode.querySelectorAll(selector); 992 for (var i = 0; i < nodes.length; i++) { 993 if (nodes[i] == el) return true; 994 } 995 return false; 996 } 997 998 /** 999 * Closest 1000 * 1001 * @param {Element} el 1002 * @param {String} selector 1003 * @param {Boolean} checkYourSelf (optional) 1004 */ 1005 function closest (element, selector, checkYourSelf) { 1006 var currentElem = checkYourSelf ? element : element.parentNode; 1007 1008 while (currentElem && currentElem.nodeType !== document.DOCUMENT_NODE && currentElem.nodeType !== document.DOCUMENT_FRAGMENT_NODE) { 1009 1010 if (matchesSelector(currentElem, selector)) { 1011 return currentElem; 1012 } 1013 1014 currentElem = currentElem.parentNode; 1015 } 1016 1017 return matchesSelector(currentElem, selector) ? currentElem : null; 1018 } 1019 1020 var bind = window.addEventListener ? 'addEventListener' : 'attachEvent', 1021 unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent', 1022 prefix$6 = bind !== 'addEventListener' ? 'on' : ''; 1023 1024 /** 1025 * Bind `el` event `type` to `fn`. 1026 * 1027 * @param {Element} el 1028 * @param {String} type 1029 * @param {Function} fn 1030 * @param {Boolean} capture 1031 * @return {Function} 1032 * @api public 1033 */ 1034 1035 var bind_1 = function(el, type, fn, capture){ 1036 el[bind](prefix$6 + type, fn, capture || false); 1037 return fn; 1038 }; 1039 1040 /** 1041 * Unbind `el` event `type`'s callback `fn`. 1042 * 1043 * @param {Element} el 1044 * @param {String} type 1045 * @param {Function} fn 1046 * @param {Boolean} capture 1047 * @return {Function} 1048 * @api public 1049 */ 1050 1051 var unbind_1 = function(el, type, fn, capture){ 1052 el[unbind](prefix$6 + type, fn, capture || false); 1053 return fn; 1054 }; 1055 1056 var componentEvent = { 1057 bind: bind_1, 1058 unbind: unbind_1 1059 }; 1060 1061 /** 1062 * Module dependencies. 1063 */ 1064 1065 /** 1066 * Delegate event `type` to `selector` 1067 * and invoke `fn(e)`. A callback function 1068 * is returned which may be passed to `.unbind()`. 1069 * 1070 * @param {Element} el 1071 * @param {String} selector 1072 * @param {String} type 1073 * @param {Function} fn 1074 * @param {Boolean} capture 1075 * @return {Function} 1076 * @api public 1077 */ 1078 1079 // Some events don't bubble, so we want to bind to the capture phase instead 1080 // when delegating. 1081 var forceCaptureEvents = ['focus', 'blur']; 1082 1083 function bind$1(el, selector, type, fn, capture) { 1084 if (forceCaptureEvents.indexOf(type) !== -1) { 1085 capture = true; 1086 } 1087 1088 return componentEvent.bind(el, type, function (e) { 1089 var target = e.target || e.srcElement; 1090 e.delegateTarget = closest(target, selector, true); 1091 if (e.delegateTarget) { 1092 fn.call(el, e); 1093 } 1094 }, capture); 1095 } 1096 1097 /** 1098 * Unbind event `type`'s callback `fn`. 1099 * 1100 * @param {Element} el 1101 * @param {String} type 1102 * @param {Function} fn 1103 * @param {Boolean} capture 1104 * @api public 1105 */ 1106 function unbind$1(el, type, fn, capture) { 1107 if (forceCaptureEvents.indexOf(type) !== -1) { 1108 capture = true; 1109 } 1110 1111 return componentEvent.unbind(el, type, fn, capture); 1112 } 1113 1114 var delegate = { 1115 bind: bind$1, 1116 unbind: unbind$1 1117 }; 1118 1119 /** 1120 * Expose `parse`. 1121 */ 1122 1123 var domify = parse$1; 1124 1125 /** 1126 * Tests for browser support. 1127 */ 1128 1129 var innerHTMLBug = false; 1130 var bugTestDiv; 1131 if (typeof document !== 'undefined') { 1132 bugTestDiv = document.createElement('div'); 1133 // Setup 1134 bugTestDiv.innerHTML = ' <link/><table></table><a href="/a">a</a><input type="checkbox"/>'; 1135 // Make sure that link elements get serialized correctly by innerHTML 1136 // This requires a wrapper element in IE 1137 innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length; 1138 bugTestDiv = undefined; 1139 } 1140 1141 /** 1142 * Wrap map from jquery. 1143 */ 1144 1145 var map = { 1146 legend: [1, '<fieldset>', '</fieldset>'], 1147 tr: [2, '<table><tbody>', '</tbody></table>'], 1148 col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'], 1149 // for script/link/style tags to work in IE6-8, you have to wrap 1150 // in a div with a non-whitespace character in front, ha! 1151 _default: innerHTMLBug ? [1, 'X<div>', '</div>'] : [0, '', ''] 1152 }; 1153 1154 map.td = 1155 map.th = [3, '<table><tbody><tr>', '</tr></tbody></table>']; 1156 1157 map.option = 1158 map.optgroup = [1, '<select multiple="multiple">', '</select>']; 1159 1160 map.thead = 1161 map.tbody = 1162 map.colgroup = 1163 map.caption = 1164 map.tfoot = [1, '<table>', '</table>']; 1165 1166 map.polyline = 1167 map.ellipse = 1168 map.polygon = 1169 map.circle = 1170 map.text = 1171 map.line = 1172 map.path = 1173 map.rect = 1174 map.g = [1, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>']; 1175 1176 /** 1177 * Parse `html` and return a DOM Node instance, which could be a TextNode, 1178 * HTML DOM Node of some kind (<div> for example), or a DocumentFragment 1179 * instance, depending on the contents of the `html` string. 1180 * 1181 * @param {String} html - HTML string to "domify" 1182 * @param {Document} doc - The `document` instance to create the Node for 1183 * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance 1184 * @api private 1185 */ 1186 1187 function parse$1(html, doc) { 1188 if ('string' != typeof html) throw new TypeError('String expected'); 1189 1190 // default to the global `document` object 1191 if (!doc) doc = document; 1192 1193 // tag name 1194 var m = /<([\w:]+)/.exec(html); 1195 if (!m) return doc.createTextNode(html); 1196 1197 html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace 1198 1199 var tag = m[1]; 1200 1201 // body support 1202 if (tag == 'body') { 1203 var el = doc.createElement('html'); 1204 el.innerHTML = html; 1205 return el.removeChild(el.lastChild); 1206 } 1207 1208 // wrap map 1209 var wrap = map[tag] || map._default; 1210 var depth = wrap[0]; 1211 var prefix = wrap[1]; 1212 var suffix = wrap[2]; 1213 var el = doc.createElement('div'); 1214 el.innerHTML = prefix + html + suffix; 1215 while (depth--) el = el.lastChild; 1216 1217 // one element 1218 if (el.firstChild == el.lastChild) { 1219 return el.removeChild(el.firstChild); 1220 } 1221 1222 // several elements 1223 var fragment = doc.createDocumentFragment(); 1224 while (el.firstChild) { 1225 fragment.appendChild(el.removeChild(el.firstChild)); 1226 } 1227 1228 return fragment; 1229 } 1230 1231 function query(selector, el) { 1232 el = el || document; 1233 1234 return el.querySelector(selector); 1235 } 1236 1237 function all(selector, el) { 1238 el = el || document; 1239 1240 return el.querySelectorAll(selector); 1241 } 1242 1243 function remove$2(el) { 1244 el.parentNode && el.parentNode.removeChild(el); 1245 } 1246 1247 function ensureImported(element, target) { 1248 1249 if (element.ownerDocument !== target.ownerDocument) { 1250 try { 1251 // may fail on webkit 1252 return target.ownerDocument.importNode(element, true); 1253 } catch (e) { 1254 // ignore 1255 } 1256 } 1257 1258 return element; 1259 } 1260 1261 /** 1262 * appendTo utility 1263 */ 1264 1265 /** 1266 * Append a node to a target element and return the appended node. 1267 * 1268 * @param {SVGElement} element 1269 * @param {SVGElement} target 1270 * 1271 * @return {SVGElement} the appended node 1272 */ 1273 function appendTo(element, target) { 1274 return target.appendChild(ensureImported(element, target)); 1275 } 1276 1277 /** 1278 * append utility 1279 */ 1280 1281 /** 1282 * Append a node to an element 1283 * 1284 * @param {SVGElement} element 1285 * @param {SVGElement} node 1286 * 1287 * @return {SVGElement} the element 1288 */ 1289 function append(target, node) { 1290 appendTo(node, target); 1291 return target; 1292 } 1293 1294 /** 1295 * attribute accessor utility 1296 */ 1297 1298 var LENGTH_ATTR = 2; 1299 1300 var CSS_PROPERTIES = { 1301 'alignment-baseline': 1, 1302 'baseline-shift': 1, 1303 'clip': 1, 1304 'clip-path': 1, 1305 'clip-rule': 1, 1306 'color': 1, 1307 'color-interpolation': 1, 1308 'color-interpolation-filters': 1, 1309 'color-profile': 1, 1310 'color-rendering': 1, 1311 'cursor': 1, 1312 'direction': 1, 1313 'display': 1, 1314 'dominant-baseline': 1, 1315 'enable-background': 1, 1316 'fill': 1, 1317 'fill-opacity': 1, 1318 'fill-rule': 1, 1319 'filter': 1, 1320 'flood-color': 1, 1321 'flood-opacity': 1, 1322 'font': 1, 1323 'font-family': 1, 1324 'font-size': LENGTH_ATTR, 1325 'font-size-adjust': 1, 1326 'font-stretch': 1, 1327 'font-style': 1, 1328 'font-variant': 1, 1329 'font-weight': 1, 1330 'glyph-orientation-horizontal': 1, 1331 'glyph-orientation-vertical': 1, 1332 'image-rendering': 1, 1333 'kerning': 1, 1334 'letter-spacing': 1, 1335 'lighting-color': 1, 1336 'marker': 1, 1337 'marker-end': 1, 1338 'marker-mid': 1, 1339 'marker-start': 1, 1340 'mask': 1, 1341 'opacity': 1, 1342 'overflow': 1, 1343 'pointer-events': 1, 1344 'shape-rendering': 1, 1345 'stop-color': 1, 1346 'stop-opacity': 1, 1347 'stroke': 1, 1348 'stroke-dasharray': 1, 1349 'stroke-dashoffset': 1, 1350 'stroke-linecap': 1, 1351 'stroke-linejoin': 1, 1352 'stroke-miterlimit': 1, 1353 'stroke-opacity': 1, 1354 'stroke-width': LENGTH_ATTR, 1355 'text-anchor': 1, 1356 'text-decoration': 1, 1357 'text-rendering': 1, 1358 'unicode-bidi': 1, 1359 'visibility': 1, 1360 'word-spacing': 1, 1361 'writing-mode': 1 1362 }; 1363 1364 1365 function getAttribute(node, name) { 1366 if (CSS_PROPERTIES[name]) { 1367 return node.style[name]; 1368 } else { 1369 return node.getAttributeNS(null, name); 1370 } 1371 } 1372 1373 function setAttribute(node, name, value) { 1374 var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); 1375 1376 var type = CSS_PROPERTIES[hyphenated]; 1377 1378 if (type) { 1379 // append pixel unit, unless present 1380 if (type === LENGTH_ATTR && typeof value === 'number') { 1381 value = String(value) + 'px'; 1382 } 1383 1384 node.style[hyphenated] = value; 1385 } else { 1386 node.setAttributeNS(null, name, value); 1387 } 1388 } 1389 1390 function setAttributes(node, attrs) { 1391 1392 var names = Object.keys(attrs), i, name; 1393 1394 for (i = 0, name; (name = names[i]); i++) { 1395 setAttribute(node, name, attrs[name]); 1396 } 1397 } 1398 1399 /** 1400 * Gets or sets raw attributes on a node. 1401 * 1402 * @param {SVGElement} node 1403 * @param {Object} [attrs] 1404 * @param {String} [name] 1405 * @param {String} [value] 1406 * 1407 * @return {String} 1408 */ 1409 function attr(node, name, value) { 1410 if (typeof name === 'string') { 1411 if (value !== undefined) { 1412 setAttribute(node, name, value); 1413 } else { 1414 return getAttribute(node, name); 1415 } 1416 } else { 1417 setAttributes(node, name); 1418 } 1419 1420 return node; 1421 } 1422 1423 /** 1424 * Clear utility 1425 */ 1426 function index(arr, obj) { 1427 if (arr.indexOf) { 1428 return arr.indexOf(obj); 1429 } 1430 1431 1432 for (var i = 0; i < arr.length; ++i) { 1433 if (arr[i] === obj) { 1434 return i; 1435 } 1436 } 1437 1438 return -1; 1439 } 1440 1441 var re = /\s+/; 1442 1443 var toString = Object.prototype.toString; 1444 1445 function defined(o) { 1446 return typeof o !== 'undefined'; 1447 } 1448 1449 /** 1450 * Wrap `el` in a `ClassList`. 1451 * 1452 * @param {Element} el 1453 * @return {ClassList} 1454 * @api public 1455 */ 1456 1457 function classes(el) { 1458 return new ClassList(el); 1459 } 1460 1461 function ClassList(el) { 1462 if (!el || !el.nodeType) { 1463 throw new Error('A DOM element reference is required'); 1464 } 1465 this.el = el; 1466 this.list = el.classList; 1467 } 1468 1469 /** 1470 * Add class `name` if not already present. 1471 * 1472 * @param {String} name 1473 * @return {ClassList} 1474 * @api public 1475 */ 1476 1477 ClassList.prototype.add = function(name) { 1478 1479 // classList 1480 if (this.list) { 1481 this.list.add(name); 1482 return this; 1483 } 1484 1485 // fallback 1486 var arr = this.array(); 1487 var i = index(arr, name); 1488 if (!~i) { 1489 arr.push(name); 1490 } 1491 1492 if (defined(this.el.className.baseVal)) { 1493 this.el.className.baseVal = arr.join(' '); 1494 } else { 1495 this.el.className = arr.join(' '); 1496 } 1497 1498 return this; 1499 }; 1500 1501 /** 1502 * Remove class `name` when present, or 1503 * pass a regular expression to remove 1504 * any which match. 1505 * 1506 * @param {String|RegExp} name 1507 * @return {ClassList} 1508 * @api public 1509 */ 1510 1511 ClassList.prototype.remove = function(name) { 1512 if ('[object RegExp]' === toString.call(name)) { 1513 return this.removeMatching(name); 1514 } 1515 1516 // classList 1517 if (this.list) { 1518 this.list.remove(name); 1519 return this; 1520 } 1521 1522 // fallback 1523 var arr = this.array(); 1524 var i = index(arr, name); 1525 if (~i) { 1526 arr.splice(i, 1); 1527 } 1528 this.el.className.baseVal = arr.join(' '); 1529 return this; 1530 }; 1531 1532 /** 1533 * Remove all classes matching `re`. 1534 * 1535 * @param {RegExp} re 1536 * @return {ClassList} 1537 * @api private 1538 */ 1539 1540 ClassList.prototype.removeMatching = function(re) { 1541 var arr = this.array(); 1542 for (var i = 0; i < arr.length; i++) { 1543 if (re.test(arr[i])) { 1544 this.remove(arr[i]); 1545 } 1546 } 1547 return this; 1548 }; 1549 1550 /** 1551 * Toggle class `name`, can force state via `force`. 1552 * 1553 * For browsers that support classList, but do not support `force` yet, 1554 * the mistake will be detected and corrected. 1555 * 1556 * @param {String} name 1557 * @param {Boolean} force 1558 * @return {ClassList} 1559 * @api public 1560 */ 1561 1562 ClassList.prototype.toggle = function(name, force) { 1563 // classList 1564 if (this.list) { 1565 if (defined(force)) { 1566 if (force !== this.list.toggle(name, force)) { 1567 this.list.toggle(name); // toggle again to correct 1568 } 1569 } else { 1570 this.list.toggle(name); 1571 } 1572 return this; 1573 } 1574 1575 // fallback 1576 if (defined(force)) { 1577 if (!force) { 1578 this.remove(name); 1579 } else { 1580 this.add(name); 1581 } 1582 } else { 1583 if (this.has(name)) { 1584 this.remove(name); 1585 } else { 1586 this.add(name); 1587 } 1588 } 1589 1590 return this; 1591 }; 1592 1593 /** 1594 * Return an array of classes. 1595 * 1596 * @return {Array} 1597 * @api public 1598 */ 1599 1600 ClassList.prototype.array = function() { 1601 var className = this.el.getAttribute('class') || ''; 1602 var str = className.replace(/^\s+|\s+$/g, ''); 1603 var arr = str.split(re); 1604 if ('' === arr[0]) { 1605 arr.shift(); 1606 } 1607 return arr; 1608 }; 1609 1610 /** 1611 * Check if class `name` is present. 1612 * 1613 * @param {String} name 1614 * @return {ClassList} 1615 * @api public 1616 */ 1617 1618 ClassList.prototype.has = 1619 ClassList.prototype.contains = function(name) { 1620 return ( 1621 this.list ? 1622 this.list.contains(name) : 1623 !! ~index(this.array(), name) 1624 ); 1625 }; 1626 1627 function remove$1(element) { 1628 var parent = element.parentNode; 1629 1630 if (parent) { 1631 parent.removeChild(element); 1632 } 1633 1634 return element; 1635 } 1636 1637 /** 1638 * Clear utility 1639 */ 1640 1641 /** 1642 * Removes all children from the given element 1643 * 1644 * @param {DOMElement} element 1645 * @return {DOMElement} the element (for chaining) 1646 */ 1647 function clear(element) { 1648 var child; 1649 1650 while ((child = element.firstChild)) { 1651 remove$1(child); 1652 } 1653 1654 return element; 1655 } 1656 1657 function clone$1(element) { 1658 return element.cloneNode(true); 1659 } 1660 1661 var ns = { 1662 svg: 'http://www.w3.org/2000/svg' 1663 }; 1664 1665 /** 1666 * DOM parsing utility 1667 */ 1668 1669 var SVG_START = '<svg xmlns="' + ns.svg + '"'; 1670 1671 function parse(svg) { 1672 1673 var unwrap = false; 1674 1675 // ensure we import a valid svg document 1676 if (svg.substring(0, 4) === '<svg') { 1677 if (svg.indexOf(ns.svg) === -1) { 1678 svg = SVG_START + svg.substring(4); 1679 } 1680 } else { 1681 // namespace svg 1682 svg = SVG_START + '>' + svg + '</svg>'; 1683 unwrap = true; 1684 } 1685 1686 var parsed = parseDocument(svg); 1687 1688 if (!unwrap) { 1689 return parsed; 1690 } 1691 1692 var fragment = document.createDocumentFragment(); 1693 1694 var parent = parsed.firstChild; 1695 1696 while (parent.firstChild) { 1697 fragment.appendChild(parent.firstChild); 1698 } 1699 1700 return fragment; 1701 } 1702 1703 function parseDocument(svg) { 1704 1705 var parser; 1706 1707 // parse 1708 parser = new DOMParser(); 1709 parser.async = false; 1710 1711 return parser.parseFromString(svg, 'text/xml'); 1712 } 1713 1714 /** 1715 * Create utility for SVG elements 1716 */ 1717 1718 1719 /** 1720 * Create a specific type from name or SVG markup. 1721 * 1722 * @param {String} name the name or markup of the element 1723 * @param {Object} [attrs] attributes to set on the element 1724 * 1725 * @returns {SVGElement} 1726 */ 1727 function create$1(name, attrs) { 1728 var element; 1729 1730 if (name.charAt(0) === '<') { 1731 element = parse(name).firstChild; 1732 element = document.importNode(element, true); 1733 } else { 1734 element = document.createElementNS(ns.svg, name); 1735 } 1736 1737 if (attrs) { 1738 attr(element, attrs); 1739 } 1740 1741 return element; 1742 } 1743 1744 /** 1745 * Geometry helpers 1746 */ 1747 1748 // fake node used to instantiate svg geometry elements 1749 var node = create$1('svg'); 1750 1751 function extend$1(object, props) { 1752 var i, k, keys = Object.keys(props); 1753 1754 for (i = 0; (k = keys[i]); i++) { 1755 object[k] = props[k]; 1756 } 1757 1758 return object; 1759 } 1760 1761 /** 1762 * Create matrix via args. 1763 * 1764 * @example 1765 * 1766 * createMatrix({ a: 1, b: 1 }); 1767 * createMatrix(); 1768 * createMatrix(1, 2, 0, 0, 30, 20); 1769 * 1770 * @return {SVGMatrix} 1771 */ 1772 function createMatrix(a, b, c, d, e, f) { 1773 var matrix = node.createSVGMatrix(); 1774 1775 switch (arguments.length) { 1776 case 0: 1777 return matrix; 1778 case 1: 1779 return extend$1(matrix, a); 1780 case 6: 1781 return extend$1(matrix, { 1782 a: a, 1783 b: b, 1784 c: c, 1785 d: d, 1786 e: e, 1787 f: f 1788 }); 1789 } 1790 } 1791 1792 function createTransform(matrix) { 1793 if (matrix) { 1794 return node.createSVGTransformFromMatrix(matrix); 1795 } else { 1796 return node.createSVGTransform(); 1797 } 1798 } 1799 1800 /** 1801 * Serialization util 1802 */ 1803 1804 var TEXT_ENTITIES = /([&<>]{1})/g; 1805 var ATTR_ENTITIES = /([\n\r"]{1})/g; 1806 1807 var ENTITY_REPLACEMENT = { 1808 '&': '&', 1809 '<': '<', 1810 '>': '>', 1811 '"': '\'' 1812 }; 1813 1814 function escape$1(str, pattern) { 1815 1816 function replaceFn(match, entity) { 1817 return ENTITY_REPLACEMENT[entity] || entity; 1818 } 1819 1820 return str.replace(pattern, replaceFn); 1821 } 1822 1823 function serialize(node, output) { 1824 1825 var i, len, attrMap, attrNode, childNodes; 1826 1827 switch (node.nodeType) { 1828 // TEXT 1829 case 3: 1830 // replace special XML characters 1831 output.push(escape$1(node.textContent, TEXT_ENTITIES)); 1832 break; 1833 1834 // ELEMENT 1835 case 1: 1836 output.push('<', node.tagName); 1837 1838 if (node.hasAttributes()) { 1839 attrMap = node.attributes; 1840 for (i = 0, len = attrMap.length; i < len; ++i) { 1841 attrNode = attrMap.item(i); 1842 output.push(' ', attrNode.name, '="', escape$1(attrNode.value, ATTR_ENTITIES), '"'); 1843 } 1844 } 1845 1846 if (node.hasChildNodes()) { 1847 output.push('>'); 1848 childNodes = node.childNodes; 1849 for (i = 0, len = childNodes.length; i < len; ++i) { 1850 serialize(childNodes.item(i), output); 1851 } 1852 output.push('</', node.tagName, '>'); 1853 } else { 1854 output.push('/>'); 1855 } 1856 break; 1857 1858 // COMMENT 1859 case 8: 1860 output.push('<!--', escape$1(node.nodeValue, TEXT_ENTITIES), '-->'); 1861 break; 1862 1863 // CDATA 1864 case 4: 1865 output.push('<![CDATA[', node.nodeValue, ']]>'); 1866 break; 1867 1868 default: 1869 throw new Error('unable to handle node ' + node.nodeType); 1870 } 1871 1872 return output; 1873 } 1874 1875 /** 1876 * innerHTML like functionality for SVG elements. 1877 * based on innerSVG (https://code.google.com/p/innersvg) 1878 */ 1879 1880 1881 function set$1(element, svg) { 1882 1883 var parsed = parse(svg); 1884 1885 // clear element contents 1886 clear(element); 1887 1888 if (!svg) { 1889 return; 1890 } 1891 1892 if (!isFragment(parsed)) { 1893 // extract <svg> from parsed document 1894 parsed = parsed.documentElement; 1895 } 1896 1897 var nodes = slice$1(parsed.childNodes); 1898 1899 // import + append each node 1900 for (var i = 0; i < nodes.length; i++) { 1901 appendTo(nodes[i], element); 1902 } 1903 1904 } 1905 1906 function get$1(element) { 1907 var child = element.firstChild, 1908 output = []; 1909 1910 while (child) { 1911 serialize(child, output); 1912 child = child.nextSibling; 1913 } 1914 1915 return output.join(''); 1916 } 1917 1918 function isFragment(node) { 1919 return node.nodeName === '#document-fragment'; 1920 } 1921 1922 function innerSVG(element, svg) { 1923 1924 if (svg !== undefined) { 1925 1926 try { 1927 set$1(element, svg); 1928 } catch (e) { 1929 throw new Error('error parsing SVG: ' + e.message); 1930 } 1931 1932 return element; 1933 } else { 1934 return get$1(element); 1935 } 1936 } 1937 1938 1939 function slice$1(arr) { 1940 return Array.prototype.slice.call(arr); 1941 } 1942 1943 /** 1944 * transform accessor utility 1945 */ 1946 1947 function wrapMatrix(transformList, transform) { 1948 if (transform instanceof SVGMatrix) { 1949 return transformList.createSVGTransformFromMatrix(transform); 1950 } 1951 1952 return transform; 1953 } 1954 1955 1956 function setTransforms(transformList, transforms) { 1957 var i, t; 1958 1959 transformList.clear(); 1960 1961 for (i = 0; (t = transforms[i]); i++) { 1962 transformList.appendItem(wrapMatrix(transformList, t)); 1963 } 1964 } 1965 1966 /** 1967 * Get or set the transforms on the given node. 1968 * 1969 * @param {SVGElement} node 1970 * @param {SVGTransform|SVGMatrix|Array<SVGTransform|SVGMatrix>} [transforms] 1971 * 1972 * @return {SVGTransform} the consolidated transform 1973 */ 1974 function transform$1(node, transforms) { 1975 var transformList = node.transform.baseVal; 1976 1977 if (transforms) { 1978 1979 if (!Array.isArray(transforms)) { 1980 transforms = [ transforms ]; 1981 } 1982 1983 setTransforms(transformList, transforms); 1984 } 1985 1986 return transformList.consolidate(); 1987 } 1988 1989 var CLASS_PATTERN = /^class /; 1990 1991 function isClass(fn) { 1992 return CLASS_PATTERN.test(fn.toString()); 1993 } 1994 1995 function isArray$1(obj) { 1996 return Object.prototype.toString.call(obj) === '[object Array]'; 1997 } 1998 1999 function hasOwnProp(obj, prop) { 2000 return Object.prototype.hasOwnProperty.call(obj, prop); 2001 } 2002 2003 function annotate() { 2004 var args = Array.prototype.slice.call(arguments); 2005 2006 if (args.length === 1 && isArray$1(args[0])) { 2007 args = args[0]; 2008 } 2009 2010 var fn = args.pop(); 2011 2012 fn.$inject = args; 2013 2014 return fn; 2015 } 2016 2017 2018 // Current limitations: 2019 // - can't put into "function arg" comments 2020 // function /* (no parenthesis like this) */ (){} 2021 // function abc( /* xx (no parenthesis like this) */ a, b) {} 2022 // 2023 // Just put the comment before function or inside: 2024 // /* (((this is fine))) */ function(a, b) {} 2025 // function abc(a) { /* (((this is fine))) */} 2026 // 2027 // - can't reliably auto-annotate constructor; we'll match the 2028 // first constructor(...) pattern found which may be the one 2029 // of a nested class, too. 2030 2031 var CONSTRUCTOR_ARGS = /constructor\s*[^(]*\(\s*([^)]*)\)/m; 2032 var FN_ARGS = /^(?:async )?(?:function\s*)?[^(]*\(\s*([^)]*)\)/m; 2033 var FN_ARG = /\/\*([^*]*)\*\//m; 2034 2035 function parseAnnotations(fn) { 2036 2037 if (typeof fn !== 'function') { 2038 throw new Error('Cannot annotate "' + fn + '". Expected a function!'); 2039 } 2040 2041 var match = fn.toString().match(isClass(fn) ? CONSTRUCTOR_ARGS : FN_ARGS); 2042 2043 // may parse class without constructor 2044 if (!match) { 2045 return []; 2046 } 2047 2048 return match[1] && match[1].split(',').map(function(arg) { 2049 match = arg.match(FN_ARG); 2050 return match ? match[1].trim() : arg.trim(); 2051 }) || []; 2052 } 2053 2054 function Module() { 2055 var providers = []; 2056 2057 this.factory = function(name, factory) { 2058 providers.push([name, 'factory', factory]); 2059 return this; 2060 }; 2061 2062 this.value = function(name, value) { 2063 providers.push([name, 'value', value]); 2064 return this; 2065 }; 2066 2067 this.type = function(name, type) { 2068 providers.push([name, 'type', type]); 2069 return this; 2070 }; 2071 2072 this.forEach = function(iterator) { 2073 providers.forEach(iterator); 2074 }; 2075 2076 } 2077 2078 function Injector(modules, parent) { 2079 parent = parent || { 2080 get: function(name, strict) { 2081 currentlyResolving.push(name); 2082 2083 if (strict === false) { 2084 return null; 2085 } else { 2086 throw error('No provider for "' + name + '"!'); 2087 } 2088 } 2089 }; 2090 2091 var currentlyResolving = []; 2092 var providers = this._providers = Object.create(parent._providers || null); 2093 var instances = this._instances = Object.create(null); 2094 2095 var self = instances.injector = this; 2096 2097 var error = function(msg) { 2098 var stack = currentlyResolving.join(' -> '); 2099 currentlyResolving.length = 0; 2100 return new Error(stack ? msg + ' (Resolving: ' + stack + ')' : msg); 2101 }; 2102 2103 /** 2104 * Return a named service. 2105 * 2106 * @param {String} name 2107 * @param {Boolean} [strict=true] if false, resolve missing services to null 2108 * 2109 * @return {Object} 2110 */ 2111 var get = function(name, strict) { 2112 if (!providers[name] && name.indexOf('.') !== -1) { 2113 var parts = name.split('.'); 2114 var pivot = get(parts.shift()); 2115 2116 while (parts.length) { 2117 pivot = pivot[parts.shift()]; 2118 } 2119 2120 return pivot; 2121 } 2122 2123 if (hasOwnProp(instances, name)) { 2124 return instances[name]; 2125 } 2126 2127 if (hasOwnProp(providers, name)) { 2128 if (currentlyResolving.indexOf(name) !== -1) { 2129 currentlyResolving.push(name); 2130 throw error('Cannot resolve circular dependency!'); 2131 } 2132 2133 currentlyResolving.push(name); 2134 instances[name] = providers[name][0](providers[name][1]); 2135 currentlyResolving.pop(); 2136 2137 return instances[name]; 2138 } 2139 2140 return parent.get(name, strict); 2141 }; 2142 2143 var fnDef = function(fn, locals) { 2144 2145 if (typeof locals === 'undefined') { 2146 locals = {}; 2147 } 2148 2149 if (typeof fn !== 'function') { 2150 if (isArray$1(fn)) { 2151 fn = annotate(fn.slice()); 2152 } else { 2153 throw new Error('Cannot invoke "' + fn + '". Expected a function!'); 2154 } 2155 } 2156 2157 var inject = fn.$inject || parseAnnotations(fn); 2158 var dependencies = inject.map(function(dep) { 2159 if (hasOwnProp(locals, dep)) { 2160 return locals[dep]; 2161 } else { 2162 return get(dep); 2163 } 2164 }); 2165 2166 return { 2167 fn: fn, 2168 dependencies: dependencies 2169 }; 2170 }; 2171 2172 var instantiate = function(Type) { 2173 var def = fnDef(Type); 2174 2175 var fn = def.fn, 2176 dependencies = def.dependencies; 2177 2178 // instantiate var args constructor 2179 var Constructor = Function.prototype.bind.apply(fn, [ null ].concat(dependencies)); 2180 2181 return new Constructor(); 2182 }; 2183 2184 var invoke = function(func, context, locals) { 2185 var def = fnDef(func, locals); 2186 2187 var fn = def.fn, 2188 dependencies = def.dependencies; 2189 2190 return fn.apply(context, dependencies); 2191 }; 2192 2193 2194 var createPrivateInjectorFactory = function(privateChildInjector) { 2195 return annotate(function(key) { 2196 return privateChildInjector.get(key); 2197 }); 2198 }; 2199 2200 var createChild = function(modules, forceNewInstances) { 2201 if (forceNewInstances && forceNewInstances.length) { 2202 var fromParentModule = Object.create(null); 2203 var matchedScopes = Object.create(null); 2204 2205 var privateInjectorsCache = []; 2206 var privateChildInjectors = []; 2207 var privateChildFactories = []; 2208 2209 var provider; 2210 var cacheIdx; 2211 var privateChildInjector; 2212 var privateChildInjectorFactory; 2213 for (var name in providers) { 2214 provider = providers[name]; 2215 2216 if (forceNewInstances.indexOf(name) !== -1) { 2217 if (provider[2] === 'private') { 2218 cacheIdx = privateInjectorsCache.indexOf(provider[3]); 2219 if (cacheIdx === -1) { 2220 privateChildInjector = provider[3].createChild([], forceNewInstances); 2221 privateChildInjectorFactory = createPrivateInjectorFactory(privateChildInjector); 2222 privateInjectorsCache.push(provider[3]); 2223 privateChildInjectors.push(privateChildInjector); 2224 privateChildFactories.push(privateChildInjectorFactory); 2225 fromParentModule[name] = [privateChildInjectorFactory, name, 'private', privateChildInjector]; 2226 } else { 2227 fromParentModule[name] = [privateChildFactories[cacheIdx], name, 'private', privateChildInjectors[cacheIdx]]; 2228 } 2229 } else { 2230 fromParentModule[name] = [provider[2], provider[1]]; 2231 } 2232 matchedScopes[name] = true; 2233 } 2234 2235 if ((provider[2] === 'factory' || provider[2] === 'type') && provider[1].$scope) { 2236 /* jshint -W083 */ 2237 forceNewInstances.forEach(function(scope) { 2238 if (provider[1].$scope.indexOf(scope) !== -1) { 2239 fromParentModule[name] = [provider[2], provider[1]]; 2240 matchedScopes[scope] = true; 2241 } 2242 }); 2243 } 2244 } 2245 2246 forceNewInstances.forEach(function(scope) { 2247 if (!matchedScopes[scope]) { 2248 throw new Error('No provider for "' + scope + '". Cannot use provider from the parent!'); 2249 } 2250 }); 2251 2252 modules.unshift(fromParentModule); 2253 } 2254 2255 return new Injector(modules, self); 2256 }; 2257 2258 var factoryMap = { 2259 factory: invoke, 2260 type: instantiate, 2261 value: function(value) { 2262 return value; 2263 } 2264 }; 2265 2266 modules.forEach(function(module) { 2267 2268 function arrayUnwrap(type, value) { 2269 if (type !== 'value' && isArray$1(value)) { 2270 value = annotate(value.slice()); 2271 } 2272 2273 return value; 2274 } 2275 2276 // TODO(vojta): handle wrong inputs (modules) 2277 if (module instanceof Module) { 2278 module.forEach(function(provider) { 2279 var name = provider[0]; 2280 var type = provider[1]; 2281 var value = provider[2]; 2282 2283 providers[name] = [factoryMap[type], arrayUnwrap(type, value), type]; 2284 }); 2285 } else if (typeof module === 'object') { 2286 if (module.__exports__) { 2287 var clonedModule = Object.keys(module).reduce(function(m, key) { 2288 if (key.substring(0, 2) !== '__') { 2289 m[key] = module[key]; 2290 } 2291 return m; 2292 }, Object.create(null)); 2293 2294 var privateInjector = new Injector((module.__modules__ || []).concat([clonedModule]), self); 2295 var getFromPrivateInjector = annotate(function(key) { 2296 return privateInjector.get(key); 2297 }); 2298 module.__exports__.forEach(function(key) { 2299 providers[key] = [getFromPrivateInjector, key, 'private', privateInjector]; 2300 }); 2301 } else { 2302 Object.keys(module).forEach(function(name) { 2303 if (module[name][2] === 'private') { 2304 providers[name] = module[name]; 2305 return; 2306 } 2307 2308 var type = module[name][0]; 2309 var value = module[name][1]; 2310 2311 providers[name] = [factoryMap[type], arrayUnwrap(type, value), type]; 2312 }); 2313 } 2314 } 2315 }); 2316 2317 // public API 2318 this.get = get; 2319 this.invoke = invoke; 2320 this.instantiate = instantiate; 2321 this.createChild = createChild; 2322 } 2323 2324 var DEFAULT_RENDER_PRIORITY$1 = 1000; 2325 2326 /** 2327 * The base implementation of shape and connection renderers. 2328 * 2329 * @param {EventBus} eventBus 2330 * @param {number} [renderPriority=1000] 2331 */ 2332 function BaseRenderer(eventBus, renderPriority) { 2333 var self = this; 2334 2335 renderPriority = renderPriority || DEFAULT_RENDER_PRIORITY$1; 2336 2337 eventBus.on([ 'render.shape', 'render.connection' ], renderPriority, function(evt, context) { 2338 var type = evt.type, 2339 element = context.element, 2340 visuals = context.gfx; 2341 2342 if (self.canRender(element)) { 2343 if (type === 'render.shape') { 2344 return self.drawShape(visuals, element); 2345 } else { 2346 return self.drawConnection(visuals, element); 2347 } 2348 } 2349 }); 2350 2351 eventBus.on([ 'render.getShapePath', 'render.getConnectionPath'], renderPriority, function(evt, element) { 2352 if (self.canRender(element)) { 2353 if (evt.type === 'render.getShapePath') { 2354 return self.getShapePath(element); 2355 } else { 2356 return self.getConnectionPath(element); 2357 } 2358 } 2359 }); 2360 } 2361 2362 /** 2363 * Should check whether *this* renderer can render 2364 * the element/connection. 2365 * 2366 * @param {element} element 2367 * 2368 * @returns {boolean} 2369 */ 2370 BaseRenderer.prototype.canRender = function() {}; 2371 2372 /** 2373 * Provides the shape's snap svg element to be drawn on the `canvas`. 2374 * 2375 * @param {djs.Graphics} visuals 2376 * @param {Shape} shape 2377 * 2378 * @returns {Snap.svg} [returns a Snap.svg paper element ] 2379 */ 2380 BaseRenderer.prototype.drawShape = function() {}; 2381 2382 /** 2383 * Provides the shape's snap svg element to be drawn on the `canvas`. 2384 * 2385 * @param {djs.Graphics} visuals 2386 * @param {Connection} connection 2387 * 2388 * @returns {Snap.svg} [returns a Snap.svg paper element ] 2389 */ 2390 BaseRenderer.prototype.drawConnection = function() {}; 2391 2392 /** 2393 * Gets the SVG path of a shape that represents it's visual bounds. 2394 * 2395 * @param {Shape} shape 2396 * 2397 * @return {string} svg path 2398 */ 2399 BaseRenderer.prototype.getShapePath = function() {}; 2400 2401 /** 2402 * Gets the SVG path of a connection that represents it's visual bounds. 2403 * 2404 * @param {Connection} connection 2405 * 2406 * @return {string} svg path 2407 */ 2408 BaseRenderer.prototype.getConnectionPath = function() {}; 2409 2410 function componentsToPath(elements) { 2411 return elements.join(',').replace(/,?([A-z]),?/g, '$1'); 2412 } 2413 2414 function toSVGPoints(points) { 2415 var result = ''; 2416 2417 for (var i = 0, p; (p = points[i]); i++) { 2418 result += p.x + ',' + p.y + ' '; 2419 } 2420 2421 return result; 2422 } 2423 2424 function createLine(points, attrs) { 2425 2426 var line = create$1('polyline'); 2427 attr(line, { points: toSVGPoints(points) }); 2428 2429 if (attrs) { 2430 attr(line, attrs); 2431 } 2432 2433 return line; 2434 } 2435 2436 function updateLine(gfx, points) { 2437 attr(gfx, { points: toSVGPoints(points) }); 2438 2439 return gfx; 2440 } 2441 2442 /** 2443 * Get parent elements. 2444 * 2445 * @param {Array<djs.model.base>} elements 2446 * 2447 * @returns {Array<djs.model.Base>} 2448 */ 2449 function getParents$1(elements) { 2450 2451 // find elements that are not children of any other elements 2452 return filter(elements, function(element) { 2453 return !find(elements, function(e) { 2454 return e !== element && getParent$1(element, e); 2455 }); 2456 }); 2457 } 2458 2459 2460 function getParent$1(element, parent) { 2461 if (!parent) { 2462 return; 2463 } 2464 2465 if (element === parent) { 2466 return parent; 2467 } 2468 2469 if (!element.parent) { 2470 return; 2471 } 2472 2473 return getParent$1(element.parent, parent); 2474 } 2475 2476 2477 /** 2478 * Adds an element to a collection and returns true if the 2479 * element was added. 2480 * 2481 * @param {Array<Object>} elements 2482 * @param {Object} e 2483 * @param {boolean} unique 2484 */ 2485 function add$1(elements, e, unique) { 2486 var canAdd = !unique || elements.indexOf(e) === -1; 2487 2488 if (canAdd) { 2489 elements.push(e); 2490 } 2491 2492 return canAdd; 2493 } 2494 2495 2496 /** 2497 * Iterate over each element in a collection, calling the iterator function `fn` 2498 * with (element, index, recursionDepth). 2499 * 2500 * Recurse into all elements that are returned by `fn`. 2501 * 2502 * @param {Object|Array<Object>} elements 2503 * @param {Function} fn iterator function called with (element, index, recursionDepth) 2504 * @param {number} [depth] maximum recursion depth 2505 */ 2506 function eachElement(elements, fn, depth) { 2507 2508 depth = depth || 0; 2509 2510 if (!isArray$2(elements)) { 2511 elements = [ elements ]; 2512 } 2513 2514 forEach(elements, function(s, i) { 2515 var filter = fn(s, i, depth); 2516 2517 if (isArray$2(filter) && filter.length) { 2518 eachElement(filter, fn, depth + 1); 2519 } 2520 }); 2521 } 2522 2523 2524 /** 2525 * Collects self + child elements up to a given depth from a list of elements. 2526 * 2527 * @param {djs.model.Base|Array<djs.model.Base>} elements the elements to select the children from 2528 * @param {boolean} unique whether to return a unique result set (no duplicates) 2529 * @param {number} maxDepth the depth to search through or -1 for infinite 2530 * 2531 * @return {Array<djs.model.Base>} found elements 2532 */ 2533 function selfAndChildren(elements, unique, maxDepth) { 2534 var result = [], 2535 processedChildren = []; 2536 2537 eachElement(elements, function(element, i, depth) { 2538 add$1(result, element, unique); 2539 2540 var children = element.children; 2541 2542 // max traversal depth not reached yet 2543 if (maxDepth === -1 || depth < maxDepth) { 2544 2545 // children exist && children not yet processed 2546 if (children && add$1(processedChildren, children, unique)) { 2547 return children; 2548 } 2549 } 2550 }); 2551 2552 return result; 2553 } 2554 2555 2556 /** 2557 * Return self + ALL children for a number of elements 2558 * 2559 * @param {Array<djs.model.Base>} elements to query 2560 * @param {boolean} allowDuplicates to allow duplicates in the result set 2561 * 2562 * @return {Array<djs.model.Base>} the collected elements 2563 */ 2564 function selfAndAllChildren(elements, allowDuplicates) { 2565 return selfAndChildren(elements, !allowDuplicates, -1); 2566 } 2567 2568 2569 /** 2570 * Gets the the closure for all selected elements, 2571 * their enclosed children and connections. 2572 * 2573 * @param {Array<djs.model.Base>} elements 2574 * @param {boolean} [isTopLevel=true] 2575 * @param {Object} [existingClosure] 2576 * 2577 * @return {Object} newClosure 2578 */ 2579 function getClosure(elements, isTopLevel, closure) { 2580 2581 if (isUndefined$1(isTopLevel)) { 2582 isTopLevel = true; 2583 } 2584 2585 if (isObject(isTopLevel)) { 2586 closure = isTopLevel; 2587 isTopLevel = true; 2588 } 2589 2590 2591 closure = closure || {}; 2592 2593 var allShapes = copyObject(closure.allShapes), 2594 allConnections = copyObject(closure.allConnections), 2595 enclosedElements = copyObject(closure.enclosedElements), 2596 enclosedConnections = copyObject(closure.enclosedConnections); 2597 2598 var topLevel = copyObject( 2599 closure.topLevel, 2600 isTopLevel && groupBy(elements, function(e) { return e.id; }) 2601 ); 2602 2603 2604 function handleConnection(c) { 2605 if (topLevel[c.source.id] && topLevel[c.target.id]) { 2606 topLevel[c.id] = [ c ]; 2607 } 2608 2609 // not enclosed as a child, but maybe logically 2610 // (connecting two moved elements?) 2611 if (allShapes[c.source.id] && allShapes[c.target.id]) { 2612 enclosedConnections[c.id] = enclosedElements[c.id] = c; 2613 } 2614 2615 allConnections[c.id] = c; 2616 } 2617 2618 function handleElement(element) { 2619 2620 enclosedElements[element.id] = element; 2621 2622 if (element.waypoints) { 2623 2624 // remember connection 2625 enclosedConnections[element.id] = allConnections[element.id] = element; 2626 } else { 2627 2628 // remember shape 2629 allShapes[element.id] = element; 2630 2631 // remember all connections 2632 forEach(element.incoming, handleConnection); 2633 2634 forEach(element.outgoing, handleConnection); 2635 2636 // recurse into children 2637 return element.children; 2638 } 2639 } 2640 2641 eachElement(elements, handleElement); 2642 2643 return { 2644 allShapes: allShapes, 2645 allConnections: allConnections, 2646 topLevel: topLevel, 2647 enclosedConnections: enclosedConnections, 2648 enclosedElements: enclosedElements 2649 }; 2650 } 2651 2652 /** 2653 * Returns the surrounding bbox for all elements in 2654 * the array or the element primitive. 2655 * 2656 * @param {Array<djs.model.Shape>|djs.model.Shape} elements 2657 * @param {boolean} stopRecursion 2658 */ 2659 function getBBox(elements, stopRecursion) { 2660 2661 stopRecursion = !!stopRecursion; 2662 if (!isArray$2(elements)) { 2663 elements = [elements]; 2664 } 2665 2666 var minX, 2667 minY, 2668 maxX, 2669 maxY; 2670 2671 forEach(elements, function(element) { 2672 2673 // If element is a connection the bbox must be computed first 2674 var bbox = element; 2675 if (element.waypoints && !stopRecursion) { 2676 bbox = getBBox(element.waypoints, true); 2677 } 2678 2679 var x = bbox.x, 2680 y = bbox.y, 2681 height = bbox.height || 0, 2682 width = bbox.width || 0; 2683 2684 if (x < minX || minX === undefined) { 2685 minX = x; 2686 } 2687 if (y < minY || minY === undefined) { 2688 minY = y; 2689 } 2690 2691 if ((x + width) > maxX || maxX === undefined) { 2692 maxX = x + width; 2693 } 2694 if ((y + height) > maxY || maxY === undefined) { 2695 maxY = y + height; 2696 } 2697 }); 2698 2699 return { 2700 x: minX, 2701 y: minY, 2702 height: maxY - minY, 2703 width: maxX - minX 2704 }; 2705 } 2706 2707 2708 /** 2709 * Returns all elements that are enclosed from the bounding box. 2710 * 2711 * * If bbox.(width|height) is not specified the method returns 2712 * all elements with element.x/y > bbox.x/y 2713 * * If only bbox.x or bbox.y is specified, method return all elements with 2714 * e.x > bbox.x or e.y > bbox.y 2715 * 2716 * @param {Array<djs.model.Shape>} elements List of Elements to search through 2717 * @param {djs.model.Shape} bbox the enclosing bbox. 2718 * 2719 * @return {Array<djs.model.Shape>} enclosed elements 2720 */ 2721 function getEnclosedElements(elements, bbox) { 2722 2723 var filteredElements = {}; 2724 2725 forEach(elements, function(element) { 2726 2727 var e = element; 2728 2729 if (e.waypoints) { 2730 e = getBBox(e); 2731 } 2732 2733 if (!isNumber(bbox.y) && (e.x > bbox.x)) { 2734 filteredElements[element.id] = element; 2735 } 2736 if (!isNumber(bbox.x) && (e.y > bbox.y)) { 2737 filteredElements[element.id] = element; 2738 } 2739 if (e.x > bbox.x && e.y > bbox.y) { 2740 if (isNumber(bbox.width) && isNumber(bbox.height) && 2741 e.width + e.x < bbox.width + bbox.x && 2742 e.height + e.y < bbox.height + bbox.y) { 2743 2744 filteredElements[element.id] = element; 2745 } else if (!isNumber(bbox.width) || !isNumber(bbox.height)) { 2746 filteredElements[element.id] = element; 2747 } 2748 } 2749 }); 2750 2751 return filteredElements; 2752 } 2753 2754 2755 function getType(element) { 2756 2757 if ('waypoints' in element) { 2758 return 'connection'; 2759 } 2760 2761 if ('x' in element) { 2762 return 'shape'; 2763 } 2764 2765 return 'root'; 2766 } 2767 2768 function isFrameElement$1(element) { 2769 2770 return !!(element && element.isFrame); 2771 } 2772 2773 // helpers /////////////////////////////// 2774 2775 function copyObject(src1, src2) { 2776 return assign({}, src1 || {}, src2 || {}); 2777 } 2778 2779 // apply default renderer with lowest possible priority 2780 // so that it only kicks in if noone else could render 2781 var DEFAULT_RENDER_PRIORITY = 1; 2782 2783 /** 2784 * The default renderer used for shapes and connections. 2785 * 2786 * @param {EventBus} eventBus 2787 * @param {Styles} styles 2788 */ 2789 function DefaultRenderer(eventBus, styles) { 2790 2791 // 2792 BaseRenderer.call(this, eventBus, DEFAULT_RENDER_PRIORITY); 2793 2794 this.CONNECTION_STYLE = styles.style([ 'no-fill' ], { strokeWidth: 5, stroke: 'fuchsia' }); 2795 this.SHAPE_STYLE = styles.style({ fill: 'white', stroke: 'fuchsia', strokeWidth: 2 }); 2796 this.FRAME_STYLE = styles.style([ 'no-fill' ], { stroke: 'fuchsia', strokeDasharray: 4, strokeWidth: 2 }); 2797 } 2798 2799 inherits$1(DefaultRenderer, BaseRenderer); 2800 2801 2802 DefaultRenderer.prototype.canRender = function() { 2803 return true; 2804 }; 2805 2806 DefaultRenderer.prototype.drawShape = function drawShape(visuals, element) { 2807 var rect = create$1('rect'); 2808 2809 attr(rect, { 2810 x: 0, 2811 y: 0, 2812 width: element.width || 0, 2813 height: element.height || 0 2814 }); 2815 2816 if (isFrameElement$1(element)) { 2817 attr(rect, this.FRAME_STYLE); 2818 } else { 2819 attr(rect, this.SHAPE_STYLE); 2820 } 2821 2822 append(visuals, rect); 2823 2824 return rect; 2825 }; 2826 2827 DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection) { 2828 2829 var line = createLine(connection.waypoints, this.CONNECTION_STYLE); 2830 append(visuals, line); 2831 2832 return line; 2833 }; 2834 2835 DefaultRenderer.prototype.getShapePath = function getShapePath(shape) { 2836 2837 var x = shape.x, 2838 y = shape.y, 2839 width = shape.width, 2840 height = shape.height; 2841 2842 var shapePath = [ 2843 ['M', x, y], 2844 ['l', width, 0], 2845 ['l', 0, height], 2846 ['l', -width, 0], 2847 ['z'] 2848 ]; 2849 2850 return componentsToPath(shapePath); 2851 }; 2852 2853 DefaultRenderer.prototype.getConnectionPath = function getConnectionPath(connection) { 2854 var waypoints = connection.waypoints; 2855 2856 var idx, point, connectionPath = []; 2857 2858 for (idx = 0; (point = waypoints[idx]); idx++) { 2859 2860 // take invisible docking into account 2861 // when creating the path 2862 point = point.original || point; 2863 2864 connectionPath.push([ idx === 0 ? 'M' : 'L', point.x, point.y ]); 2865 } 2866 2867 return componentsToPath(connectionPath); 2868 }; 2869 2870 2871 DefaultRenderer.$inject = [ 'eventBus', 'styles' ]; 2872 2873 /** 2874 * A component that manages shape styles 2875 */ 2876 function Styles() { 2877 2878 var defaultTraits = { 2879 2880 'no-fill': { 2881 fill: 'none' 2882 }, 2883 'no-border': { 2884 strokeOpacity: 0.0 2885 }, 2886 'no-events': { 2887 pointerEvents: 'none' 2888 } 2889 }; 2890 2891 var self = this; 2892 2893 /** 2894 * Builds a style definition from a className, a list of traits and an object of additional attributes. 2895 * 2896 * @param {string} className 2897 * @param {Array<string>} traits 2898 * @param {Object} additionalAttrs 2899 * 2900 * @return {Object} the style defintion 2901 */ 2902 this.cls = function(className, traits, additionalAttrs) { 2903 var attrs = this.style(traits, additionalAttrs); 2904 2905 return assign(attrs, { 'class': className }); 2906 }; 2907 2908 /** 2909 * Builds a style definition from a list of traits and an object of additional attributes. 2910 * 2911 * @param {Array<string>} traits 2912 * @param {Object} additionalAttrs 2913 * 2914 * @return {Object} the style defintion 2915 */ 2916 this.style = function(traits, additionalAttrs) { 2917 2918 if (!isArray$2(traits) && !additionalAttrs) { 2919 additionalAttrs = traits; 2920 traits = []; 2921 } 2922 2923 var attrs = reduce(traits, function(attrs, t) { 2924 return assign(attrs, defaultTraits[t] || {}); 2925 }, {}); 2926 2927 return additionalAttrs ? assign(attrs, additionalAttrs) : attrs; 2928 }; 2929 2930 this.computeStyle = function(custom, traits, defaultStyles) { 2931 if (!isArray$2(traits)) { 2932 defaultStyles = traits; 2933 traits = []; 2934 } 2935 2936 return self.style(traits || [], assign({}, defaultStyles, custom || {})); 2937 }; 2938 } 2939 2940 var DrawModule$1 = { 2941 __init__: [ 'defaultRenderer' ], 2942 defaultRenderer: [ 'type', DefaultRenderer ], 2943 styles: [ 'type', Styles ] 2944 }; 2945 2946 /** 2947 * Failsafe remove an element from a collection 2948 * 2949 * @param {Array<Object>} [collection] 2950 * @param {Object} [element] 2951 * 2952 * @return {number} the previous index of the element 2953 */ 2954 function remove(collection, element) { 2955 2956 if (!collection || !element) { 2957 return -1; 2958 } 2959 2960 var idx = collection.indexOf(element); 2961 2962 if (idx !== -1) { 2963 collection.splice(idx, 1); 2964 } 2965 2966 return idx; 2967 } 2968 2969 /** 2970 * Fail save add an element to the given connection, ensuring 2971 * it does not yet exist. 2972 * 2973 * @param {Array<Object>} collection 2974 * @param {Object} element 2975 * @param {number} idx 2976 */ 2977 function add(collection, element, idx) { 2978 2979 if (!collection || !element) { 2980 return; 2981 } 2982 2983 if (typeof idx !== 'number') { 2984 idx = -1; 2985 } 2986 2987 var currentIdx = collection.indexOf(element); 2988 2989 if (currentIdx !== -1) { 2990 2991 if (currentIdx === idx) { 2992 2993 // nothing to do, position has not changed 2994 return; 2995 } else { 2996 2997 if (idx !== -1) { 2998 2999 // remove from current position 3000 collection.splice(currentIdx, 1); 3001 } else { 3002 3003 // already exists in collection 3004 return; 3005 } 3006 } 3007 } 3008 3009 if (idx !== -1) { 3010 3011 // insert at specified position 3012 collection.splice(idx, 0, element); 3013 } else { 3014 3015 // push to end 3016 collection.push(element); 3017 } 3018 } 3019 3020 3021 /** 3022 * Fail save get the index of an element in a collection. 3023 * 3024 * @param {Array<Object>} collection 3025 * @param {Object} element 3026 * 3027 * @return {number} the index or -1 if collection or element do 3028 * not exist or the element is not contained. 3029 */ 3030 function indexOf(collection, element) { 3031 3032 if (!collection || !element) { 3033 return -1; 3034 } 3035 3036 return collection.indexOf(element); 3037 } 3038 3039 /** 3040 * Computes the distance between two points 3041 * 3042 * @param {Point} p 3043 * @param {Point} q 3044 * 3045 * @return {number} distance 3046 */ 3047 function pointDistance(a, b) { 3048 if (!a || !b) { 3049 return -1; 3050 } 3051 3052 return Math.sqrt( 3053 Math.pow(a.x - b.x, 2) + 3054 Math.pow(a.y - b.y, 2) 3055 ); 3056 } 3057 3058 3059 /** 3060 * Returns true if the point r is on the line between p and q 3061 * 3062 * @param {Point} p 3063 * @param {Point} q 3064 * @param {Point} r 3065 * @param {number} [accuracy=5] accuracy for points on line check (lower is better) 3066 * 3067 * @return {boolean} 3068 */ 3069 function pointsOnLine(p, q, r, accuracy) { 3070 3071 if (typeof accuracy === 'undefined') { 3072 accuracy = 5; 3073 } 3074 3075 if (!p || !q || !r) { 3076 return false; 3077 } 3078 3079 var val = (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x), 3080 dist = pointDistance(p, q); 3081 3082 // @see http://stackoverflow.com/a/907491/412190 3083 return Math.abs(val / dist) <= accuracy; 3084 } 3085 3086 3087 var ALIGNED_THRESHOLD = 2; 3088 3089 /** 3090 * Check whether two points are horizontally or vertically aligned. 3091 * 3092 * @param {Array<Point>|Point} 3093 * @param {Point} 3094 * 3095 * @return {string|boolean} 3096 */ 3097 function pointsAligned(a, b) { 3098 var points; 3099 3100 if (isArray$2(a)) { 3101 points = a; 3102 } else { 3103 points = [ a, b ]; 3104 } 3105 3106 if (pointsAlignedHorizontally(points)) { 3107 return 'h'; 3108 } 3109 3110 if (pointsAlignedVertically(points)) { 3111 return 'v'; 3112 } 3113 3114 return false; 3115 } 3116 3117 function pointsAlignedHorizontally(a, b) { 3118 var points; 3119 3120 if (isArray$2(a)) { 3121 points = a; 3122 } else { 3123 points = [ a, b ]; 3124 } 3125 3126 var firstPoint = points.slice().shift(); 3127 3128 return every(points, function(point) { 3129 return Math.abs(firstPoint.y - point.y) <= ALIGNED_THRESHOLD; 3130 }); 3131 } 3132 3133 function pointsAlignedVertically(a, b) { 3134 var points; 3135 3136 if (isArray$2(a)) { 3137 points = a; 3138 } else { 3139 points = [ a, b ]; 3140 } 3141 3142 var firstPoint = points.slice().shift(); 3143 3144 return every(points, function(point) { 3145 return Math.abs(firstPoint.x - point.x) <= ALIGNED_THRESHOLD; 3146 }); 3147 } 3148 3149 3150 3151 /** 3152 * Returns true if the point p is inside the rectangle rect 3153 * 3154 * @param {Point} p 3155 * @param {Rect} rect 3156 * @param {number} tolerance 3157 * 3158 * @return {boolean} 3159 */ 3160 function pointInRect(p, rect, tolerance) { 3161 tolerance = tolerance || 0; 3162 3163 return p.x > rect.x - tolerance && 3164 p.y > rect.y - tolerance && 3165 p.x < rect.x + rect.width + tolerance && 3166 p.y < rect.y + rect.height + tolerance; 3167 } 3168 3169 /** 3170 * Returns a point in the middle of points p and q 3171 * 3172 * @param {Point} p 3173 * @param {Point} q 3174 * 3175 * @return {Point} middle point 3176 */ 3177 function getMidPoint(p, q) { 3178 return { 3179 x: Math.round(p.x + ((q.x - p.x) / 2.0)), 3180 y: Math.round(p.y + ((q.y - p.y) / 2.0)) 3181 }; 3182 } 3183 3184 /** 3185 * This file contains source code adapted from Snap.svg (licensed Apache-2.0). 3186 * 3187 * @see https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js 3188 */ 3189 3190 /* eslint no-fallthrough: "off" */ 3191 3192 var p2s = /,?([a-z]),?/gi, 3193 toFloat = parseFloat, 3194 math = Math, 3195 PI = math.PI, 3196 mmin = math.min, 3197 mmax = math.max, 3198 pow = math.pow, 3199 abs$7 = math.abs, 3200 pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?[\s]*,?[\s]*)+)/ig, 3201 pathValues = /(-?\d*\.?\d*(?:e[-+]?\d+)?)[\s]*,?[\s]*/ig; 3202 3203 var isArray = Array.isArray || function(o) { return o instanceof Array; }; 3204 3205 function hasProperty(obj, property) { 3206 return Object.prototype.hasOwnProperty.call(obj, property); 3207 } 3208 3209 function clone(obj) { 3210 3211 if (typeof obj == 'function' || Object(obj) !== obj) { 3212 return obj; 3213 } 3214 3215 var res = new obj.constructor; 3216 3217 for (var key in obj) { 3218 if (hasProperty(obj, key)) { 3219 res[key] = clone(obj[key]); 3220 } 3221 } 3222 3223 return res; 3224 } 3225 3226 function repush(array, item) { 3227 for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) { 3228 return array.push(array.splice(i, 1)[0]); 3229 } 3230 } 3231 3232 function cacher(f) { 3233 3234 function newf() { 3235 3236 var arg = Array.prototype.slice.call(arguments, 0), 3237 args = arg.join('\u2400'), 3238 cache = newf.cache = newf.cache || {}, 3239 count = newf.count = newf.count || []; 3240 3241 if (hasProperty(cache, args)) { 3242 repush(count, args); 3243 return cache[args]; 3244 } 3245 3246 count.length >= 1e3 && delete cache[count.shift()]; 3247 count.push(args); 3248 cache[args] = f.apply(0, arg); 3249 3250 return cache[args]; 3251 } 3252 return newf; 3253 } 3254 3255 function parsePathString(pathString) { 3256 3257 if (!pathString) { 3258 return null; 3259 } 3260 3261 var pth = paths(pathString); 3262 3263 if (pth.arr) { 3264 return clone(pth.arr); 3265 } 3266 3267 var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 }, 3268 data = []; 3269 3270 if (isArray(pathString) && isArray(pathString[0])) { // rough assumption 3271 data = clone(pathString); 3272 } 3273 3274 if (!data.length) { 3275 3276 String(pathString).replace(pathCommand, function(a, b, c) { 3277 var params = [], 3278 name = b.toLowerCase(); 3279 3280 c.replace(pathValues, function(a, b) { 3281 b && params.push(+b); 3282 }); 3283 3284 if (name == 'm' && params.length > 2) { 3285 data.push([b].concat(params.splice(0, 2))); 3286 name = 'l'; 3287 b = b == 'm' ? 'l' : 'L'; 3288 } 3289 3290 while (params.length >= paramCounts[name]) { 3291 data.push([b].concat(params.splice(0, paramCounts[name]))); 3292 if (!paramCounts[name]) { 3293 break; 3294 } 3295 } 3296 }); 3297 } 3298 3299 data.toString = paths.toString; 3300 pth.arr = clone(data); 3301 3302 return data; 3303 } 3304 3305 function paths(ps) { 3306 var p = paths.ps = paths.ps || {}; 3307 3308 if (p[ps]) { 3309 p[ps].sleep = 100; 3310 } else { 3311 p[ps] = { 3312 sleep: 100 3313 }; 3314 } 3315 3316 setTimeout(function() { 3317 for (var key in p) { 3318 if (hasProperty(p, key) && key != ps) { 3319 p[key].sleep--; 3320 !p[key].sleep && delete p[key]; 3321 } 3322 } 3323 }); 3324 3325 return p[ps]; 3326 } 3327 3328 function rectBBox(x, y, width, height) { 3329 3330 if (arguments.length === 1) { 3331 y = x.y; 3332 width = x.width; 3333 height = x.height; 3334 x = x.x; 3335 } 3336 3337 return { 3338 x: x, 3339 y: y, 3340 width: width, 3341 height: height, 3342 x2: x + width, 3343 y2: y + height 3344 }; 3345 } 3346 3347 function pathToString() { 3348 return this.join(',').replace(p2s, '$1'); 3349 } 3350 3351 function pathClone(pathArray) { 3352 var res = clone(pathArray); 3353 res.toString = pathToString; 3354 return res; 3355 } 3356 3357 function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { 3358 var t1 = 1 - t, 3359 t13 = pow(t1, 3), 3360 t12 = pow(t1, 2), 3361 t2 = t * t, 3362 t3 = t2 * t, 3363 x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, 3364 y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y; 3365 3366 return { 3367 x: fixError(x), 3368 y: fixError(y) 3369 }; 3370 } 3371 3372 function bezierBBox(points) { 3373 3374 var bbox = curveBBox.apply(null, points); 3375 3376 return rectBBox( 3377 bbox.x0, 3378 bbox.y0, 3379 bbox.x1 - bbox.x0, 3380 bbox.y1 - bbox.y0 3381 ); 3382 } 3383 3384 function isPointInsideBBox$2(bbox, x, y) { 3385 return x >= bbox.x && 3386 x <= bbox.x + bbox.width && 3387 y >= bbox.y && 3388 y <= bbox.y + bbox.height; 3389 } 3390 3391 function isBBoxIntersect(bbox1, bbox2) { 3392 bbox1 = rectBBox(bbox1); 3393 bbox2 = rectBBox(bbox2); 3394 return isPointInsideBBox$2(bbox2, bbox1.x, bbox1.y) 3395 || isPointInsideBBox$2(bbox2, bbox1.x2, bbox1.y) 3396 || isPointInsideBBox$2(bbox2, bbox1.x, bbox1.y2) 3397 || isPointInsideBBox$2(bbox2, bbox1.x2, bbox1.y2) 3398 || isPointInsideBBox$2(bbox1, bbox2.x, bbox2.y) 3399 || isPointInsideBBox$2(bbox1, bbox2.x2, bbox2.y) 3400 || isPointInsideBBox$2(bbox1, bbox2.x, bbox2.y2) 3401 || isPointInsideBBox$2(bbox1, bbox2.x2, bbox2.y2) 3402 || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x 3403 || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x) 3404 && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y 3405 || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y); 3406 } 3407 3408 function base3(t, p1, p2, p3, p4) { 3409 var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, 3410 t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3; 3411 return t * t2 - 3 * p1 + 3 * p2; 3412 } 3413 3414 function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { 3415 3416 if (z == null) { 3417 z = 1; 3418 } 3419 3420 z = z > 1 ? 1 : z < 0 ? 0 : z; 3421 3422 var z2 = z / 2, 3423 n = 12, 3424 Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816], 3425 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], 3426 sum = 0; 3427 3428 for (var i = 0; i < n; i++) { 3429 var ct = z2 * Tvalues[i] + z2, 3430 xbase = base3(ct, x1, x2, x3, x4), 3431 ybase = base3(ct, y1, y2, y3, y4), 3432 comb = xbase * xbase + ybase * ybase; 3433 3434 sum += Cvalues[i] * math.sqrt(comb); 3435 } 3436 3437 return z2 * sum; 3438 } 3439 3440 3441 function intersectLines(x1, y1, x2, y2, x3, y3, x4, y4) { 3442 3443 if ( 3444 mmax(x1, x2) < mmin(x3, x4) || 3445 mmin(x1, x2) > mmax(x3, x4) || 3446 mmax(y1, y2) < mmin(y3, y4) || 3447 mmin(y1, y2) > mmax(y3, y4) 3448 ) { 3449 return; 3450 } 3451 3452 var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), 3453 ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), 3454 denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); 3455 3456 if (!denominator) { 3457 return; 3458 } 3459 3460 var px = fixError(nx / denominator), 3461 py = fixError(ny / denominator), 3462 px2 = +px.toFixed(2), 3463 py2 = +py.toFixed(2); 3464 3465 if ( 3466 px2 < +mmin(x1, x2).toFixed(2) || 3467 px2 > +mmax(x1, x2).toFixed(2) || 3468 px2 < +mmin(x3, x4).toFixed(2) || 3469 px2 > +mmax(x3, x4).toFixed(2) || 3470 py2 < +mmin(y1, y2).toFixed(2) || 3471 py2 > +mmax(y1, y2).toFixed(2) || 3472 py2 < +mmin(y3, y4).toFixed(2) || 3473 py2 > +mmax(y3, y4).toFixed(2) 3474 ) { 3475 return; 3476 } 3477 3478 return { x: px, y: py }; 3479 } 3480 3481 function fixError(number) { 3482 return Math.round(number * 100000000000) / 100000000000; 3483 } 3484 3485 function findBezierIntersections(bez1, bez2, justCount) { 3486 var bbox1 = bezierBBox(bez1), 3487 bbox2 = bezierBBox(bez2); 3488 3489 if (!isBBoxIntersect(bbox1, bbox2)) { 3490 return justCount ? 0 : []; 3491 } 3492 3493 // As an optimization, lines will have only 1 segment 3494 3495 var l1 = bezlen.apply(0, bez1), 3496 l2 = bezlen.apply(0, bez2), 3497 n1 = isLine(bez1) ? 1 : ~~(l1 / 5) || 1, 3498 n2 = isLine(bez2) ? 1 : ~~(l2 / 5) || 1, 3499 dots1 = [], 3500 dots2 = [], 3501 xy = {}, 3502 res = justCount ? 0 : []; 3503 3504 for (var i = 0; i < n1 + 1; i++) { 3505 var p = findDotsAtSegment.apply(0, bez1.concat(i / n1)); 3506 dots1.push({ x: p.x, y: p.y, t: i / n1 }); 3507 } 3508 3509 for (i = 0; i < n2 + 1; i++) { 3510 p = findDotsAtSegment.apply(0, bez2.concat(i / n2)); 3511 dots2.push({ x: p.x, y: p.y, t: i / n2 }); 3512 } 3513 3514 for (i = 0; i < n1; i++) { 3515 3516 for (var j = 0; j < n2; j++) { 3517 var di = dots1[i], 3518 di1 = dots1[i + 1], 3519 dj = dots2[j], 3520 dj1 = dots2[j + 1], 3521 ci = abs$7(di1.x - di.x) < .01 ? 'y' : 'x', 3522 cj = abs$7(dj1.x - dj.x) < .01 ? 'y' : 'x', 3523 is = intersectLines(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y), 3524 key; 3525 3526 if (is) { 3527 key = is.x.toFixed(9) + '#' + is.y.toFixed(9); 3528 3529 if (xy[key]) { 3530 continue; 3531 } 3532 3533 xy[key] = true; 3534 3535 var t1 = di.t + abs$7((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), 3536 t2 = dj.t + abs$7((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t); 3537 3538 if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) { 3539 3540 if (justCount) { 3541 res++; 3542 } else { 3543 res.push({ 3544 x: is.x, 3545 y: is.y, 3546 t1: t1, 3547 t2: t2 3548 }); 3549 } 3550 } 3551 } 3552 } 3553 } 3554 3555 return res; 3556 } 3557 3558 3559 /** 3560 * Find or counts the intersections between two SVG paths. 3561 * 3562 * Returns a number in counting mode and a list of intersections otherwise. 3563 * 3564 * A single intersection entry contains the intersection coordinates (x, y) 3565 * as well as additional information regarding the intersecting segments 3566 * on each path (segment1, segment2) and the relative location of the 3567 * intersection on these segments (t1, t2). 3568 * 3569 * The path may be an SVG path string or a list of path components 3570 * such as `[ [ 'M', 0, 10 ], [ 'L', 20, 0 ] ]`. 3571 * 3572 * @example 3573 * 3574 * var intersections = findPathIntersections( 3575 * 'M0,0L100,100', 3576 * [ [ 'M', 0, 100 ], [ 'L', 100, 0 ] ] 3577 * ); 3578 * 3579 * // intersections = [ 3580 * // { x: 50, y: 50, segment1: 1, segment2: 1, t1: 0.5, t2: 0.5 } 3581 * // ] 3582 * 3583 * @param {String|Array<PathDef>} path1 3584 * @param {String|Array<PathDef>} path2 3585 * @param {Boolean} [justCount=false] 3586 * 3587 * @return {Array<Intersection>|Number} 3588 */ 3589 function findPathIntersections(path1, path2, justCount) { 3590 path1 = pathToCurve(path1); 3591 path2 = pathToCurve(path2); 3592 3593 var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2, 3594 res = justCount ? 0 : []; 3595 3596 for (var i = 0, ii = path1.length; i < ii; i++) { 3597 var pi = path1[i]; 3598 3599 if (pi[0] == 'M') { 3600 x1 = x1m = pi[1]; 3601 y1 = y1m = pi[2]; 3602 } else { 3603 3604 if (pi[0] == 'C') { 3605 bez1 = [x1, y1].concat(pi.slice(1)); 3606 x1 = bez1[6]; 3607 y1 = bez1[7]; 3608 } else { 3609 bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m]; 3610 x1 = x1m; 3611 y1 = y1m; 3612 } 3613 3614 for (var j = 0, jj = path2.length; j < jj; j++) { 3615 var pj = path2[j]; 3616 3617 if (pj[0] == 'M') { 3618 x2 = x2m = pj[1]; 3619 y2 = y2m = pj[2]; 3620 } else { 3621 3622 if (pj[0] == 'C') { 3623 bez2 = [x2, y2].concat(pj.slice(1)); 3624 x2 = bez2[6]; 3625 y2 = bez2[7]; 3626 } else { 3627 bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m]; 3628 x2 = x2m; 3629 y2 = y2m; 3630 } 3631 3632 var intr = findBezierIntersections(bez1, bez2, justCount); 3633 3634 if (justCount) { 3635 res += intr; 3636 } else { 3637 3638 for (var k = 0, kk = intr.length; k < kk; k++) { 3639 intr[k].segment1 = i; 3640 intr[k].segment2 = j; 3641 intr[k].bez1 = bez1; 3642 intr[k].bez2 = bez2; 3643 } 3644 3645 res = res.concat(intr); 3646 } 3647 } 3648 } 3649 } 3650 } 3651 3652 return res; 3653 } 3654 3655 3656 function pathToAbsolute(pathArray) { 3657 var pth = paths(pathArray); 3658 3659 if (pth.abs) { 3660 return pathClone(pth.abs); 3661 } 3662 3663 if (!isArray(pathArray) || !isArray(pathArray && pathArray[0])) { // rough assumption 3664 pathArray = parsePathString(pathArray); 3665 } 3666 3667 if (!pathArray || !pathArray.length) { 3668 return [['M', 0, 0]]; 3669 } 3670 3671 var res = [], 3672 x = 0, 3673 y = 0, 3674 mx = 0, 3675 my = 0, 3676 start = 0, 3677 pa0; 3678 3679 if (pathArray[0][0] == 'M') { 3680 x = +pathArray[0][1]; 3681 y = +pathArray[0][2]; 3682 mx = x; 3683 my = y; 3684 start++; 3685 res[0] = ['M', x, y]; 3686 } 3687 3688 for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { 3689 res.push(r = []); 3690 pa = pathArray[i]; 3691 pa0 = pa[0]; 3692 3693 if (pa0 != pa0.toUpperCase()) { 3694 r[0] = pa0.toUpperCase(); 3695 3696 switch (r[0]) { 3697 case 'A': 3698 r[1] = pa[1]; 3699 r[2] = pa[2]; 3700 r[3] = pa[3]; 3701 r[4] = pa[4]; 3702 r[5] = pa[5]; 3703 r[6] = +pa[6] + x; 3704 r[7] = +pa[7] + y; 3705 break; 3706 case 'V': 3707 r[1] = +pa[1] + y; 3708 break; 3709 case 'H': 3710 r[1] = +pa[1] + x; 3711 break; 3712 case 'M': 3713 mx = +pa[1] + x; 3714 my = +pa[2] + y; 3715 default: 3716 for (var j = 1, jj = pa.length; j < jj; j++) { 3717 r[j] = +pa[j] + ((j % 2) ? x : y); 3718 } 3719 } 3720 } else { 3721 for (var k = 0, kk = pa.length; k < kk; k++) { 3722 r[k] = pa[k]; 3723 } 3724 } 3725 pa0 = pa0.toUpperCase(); 3726 3727 switch (r[0]) { 3728 case 'Z': 3729 x = +mx; 3730 y = +my; 3731 break; 3732 case 'H': 3733 x = r[1]; 3734 break; 3735 case 'V': 3736 y = r[1]; 3737 break; 3738 case 'M': 3739 mx = r[r.length - 2]; 3740 my = r[r.length - 1]; 3741 default: 3742 x = r[r.length - 2]; 3743 y = r[r.length - 1]; 3744 } 3745 } 3746 3747 res.toString = pathToString; 3748 pth.abs = pathClone(res); 3749 3750 return res; 3751 } 3752 3753 function isLine(bez) { 3754 return ( 3755 bez[0] === bez[2] && 3756 bez[1] === bez[3] && 3757 bez[4] === bez[6] && 3758 bez[5] === bez[7] 3759 ); 3760 } 3761 3762 function lineToCurve(x1, y1, x2, y2) { 3763 return [ 3764 x1, y1, x2, 3765 y2, x2, y2 3766 ]; 3767 } 3768 3769 function qubicToCurve(x1, y1, ax, ay, x2, y2) { 3770 var _13 = 1 / 3, 3771 _23 = 2 / 3; 3772 3773 return [ 3774 _13 * x1 + _23 * ax, 3775 _13 * y1 + _23 * ay, 3776 _13 * x2 + _23 * ax, 3777 _13 * y2 + _23 * ay, 3778 x2, 3779 y2 3780 ]; 3781 } 3782 3783 function arcToCurve(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { 3784 3785 // for more information of where this math came from visit: 3786 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes 3787 var _120 = PI * 120 / 180, 3788 rad = PI / 180 * (+angle || 0), 3789 res = [], 3790 xy, 3791 rotate = cacher(function(x, y, rad) { 3792 var X = x * math.cos(rad) - y * math.sin(rad), 3793 Y = x * math.sin(rad) + y * math.cos(rad); 3794 3795 return { x: X, y: Y }; 3796 }); 3797 3798 if (!recursive) { 3799 xy = rotate(x1, y1, -rad); 3800 x1 = xy.x; 3801 y1 = xy.y; 3802 xy = rotate(x2, y2, -rad); 3803 x2 = xy.x; 3804 y2 = xy.y; 3805 3806 var x = (x1 - x2) / 2, 3807 y = (y1 - y2) / 2; 3808 3809 var h = (x * x) / (rx * rx) + (y * y) / (ry * ry); 3810 3811 if (h > 1) { 3812 h = math.sqrt(h); 3813 rx = h * rx; 3814 ry = h * ry; 3815 } 3816 3817 var rx2 = rx * rx, 3818 ry2 = ry * ry, 3819 k = (large_arc_flag == sweep_flag ? -1 : 1) * 3820 math.sqrt(abs$7((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))), 3821 cx = k * rx * y / ry + (x1 + x2) / 2, 3822 cy = k * -ry * x / rx + (y1 + y2) / 2, 3823 f1 = math.asin(((y1 - cy) / ry).toFixed(9)), 3824 f2 = math.asin(((y2 - cy) / ry).toFixed(9)); 3825 3826 f1 = x1 < cx ? PI - f1 : f1; 3827 f2 = x2 < cx ? PI - f2 : f2; 3828 f1 < 0 && (f1 = PI * 2 + f1); 3829 f2 < 0 && (f2 = PI * 2 + f2); 3830 3831 if (sweep_flag && f1 > f2) { 3832 f1 = f1 - PI * 2; 3833 } 3834 if (!sweep_flag && f2 > f1) { 3835 f2 = f2 - PI * 2; 3836 } 3837 } else { 3838 f1 = recursive[0]; 3839 f2 = recursive[1]; 3840 cx = recursive[2]; 3841 cy = recursive[3]; 3842 } 3843 3844 var df = f2 - f1; 3845 3846 if (abs$7(df) > _120) { 3847 var f2old = f2, 3848 x2old = x2, 3849 y2old = y2; 3850 3851 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1); 3852 x2 = cx + rx * math.cos(f2); 3853 y2 = cy + ry * math.sin(f2); 3854 res = arcToCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]); 3855 } 3856 3857 df = f2 - f1; 3858 3859 var c1 = math.cos(f1), 3860 s1 = math.sin(f1), 3861 c2 = math.cos(f2), 3862 s2 = math.sin(f2), 3863 t = math.tan(df / 4), 3864 hx = 4 / 3 * rx * t, 3865 hy = 4 / 3 * ry * t, 3866 m1 = [x1, y1], 3867 m2 = [x1 + hx * s1, y1 - hy * c1], 3868 m3 = [x2 + hx * s2, y2 - hy * c2], 3869 m4 = [x2, y2]; 3870 3871 m2[0] = 2 * m1[0] - m2[0]; 3872 m2[1] = 2 * m1[1] - m2[1]; 3873 3874 if (recursive) { 3875 return [m2, m3, m4].concat(res); 3876 } else { 3877 res = [m2, m3, m4].concat(res).join().split(','); 3878 var newres = []; 3879 3880 for (var i = 0, ii = res.length; i < ii; i++) { 3881 newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x; 3882 } 3883 3884 return newres; 3885 } 3886 } 3887 3888 // Returns bounding box of cubic bezier curve. 3889 // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html 3890 // Original version: NISHIO Hirokazu 3891 // Modifications: https://github.com/timo22345 3892 function curveBBox(x0, y0, x1, y1, x2, y2, x3, y3) { 3893 var tvalues = [], 3894 bounds = [[], []], 3895 a, b, c, t, t1, t2, b2ac, sqrtb2ac; 3896 3897 for (var i = 0; i < 2; ++i) { 3898 3899 if (i == 0) { 3900 b = 6 * x0 - 12 * x1 + 6 * x2; 3901 a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; 3902 c = 3 * x1 - 3 * x0; 3903 } else { 3904 b = 6 * y0 - 12 * y1 + 6 * y2; 3905 a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; 3906 c = 3 * y1 - 3 * y0; 3907 } 3908 3909 if (abs$7(a) < 1e-12) { 3910 3911 if (abs$7(b) < 1e-12) { 3912 continue; 3913 } 3914 3915 t = -c / b; 3916 3917 if (0 < t && t < 1) { 3918 tvalues.push(t); 3919 } 3920 3921 continue; 3922 } 3923 3924 b2ac = b * b - 4 * c * a; 3925 sqrtb2ac = math.sqrt(b2ac); 3926 3927 if (b2ac < 0) { 3928 continue; 3929 } 3930 3931 t1 = (-b + sqrtb2ac) / (2 * a); 3932 3933 if (0 < t1 && t1 < 1) { 3934 tvalues.push(t1); 3935 } 3936 3937 t2 = (-b - sqrtb2ac) / (2 * a); 3938 3939 if (0 < t2 && t2 < 1) { 3940 tvalues.push(t2); 3941 } 3942 } 3943 3944 var j = tvalues.length, 3945 jlen = j, 3946 mt; 3947 3948 while (j--) { 3949 t = tvalues[j]; 3950 mt = 1 - t; 3951 bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); 3952 bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); 3953 } 3954 3955 bounds[0][jlen] = x0; 3956 bounds[1][jlen] = y0; 3957 bounds[0][jlen + 1] = x3; 3958 bounds[1][jlen + 1] = y3; 3959 bounds[0].length = bounds[1].length = jlen + 2; 3960 3961 return { 3962 x0: mmin.apply(0, bounds[0]), 3963 y0: mmin.apply(0, bounds[1]), 3964 x1: mmax.apply(0, bounds[0]), 3965 y1: mmax.apply(0, bounds[1]) 3966 }; 3967 } 3968 3969 function pathToCurve(path) { 3970 3971 var pth = paths(path); 3972 3973 // return cached curve, if existing 3974 if (pth.curve) { 3975 return pathClone(pth.curve); 3976 } 3977 3978 var curvedPath = pathToAbsolute(path), 3979 attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, 3980 processPath = function(path, d, pathCommand) { 3981 var nx, ny; 3982 3983 if (!path) { 3984 return ['C', d.x, d.y, d.x, d.y, d.x, d.y]; 3985 } 3986 3987 !(path[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null); 3988 3989 switch (path[0]) { 3990 case 'M': 3991 d.X = path[1]; 3992 d.Y = path[2]; 3993 break; 3994 case 'A': 3995 path = ['C'].concat(arcToCurve.apply(0, [d.x, d.y].concat(path.slice(1)))); 3996 break; 3997 case 'S': 3998 if (pathCommand == 'C' || pathCommand == 'S') { 3999 4000 // In 'S' case we have to take into account, if the previous command is C/S. 4001 nx = d.x * 2 - d.bx; 4002 4003 // And reflect the previous 4004 ny = d.y * 2 - d.by; 4005 4006 // command's control point relative to the current point. 4007 } 4008 else { 4009 4010 // or some else or nothing 4011 nx = d.x; 4012 ny = d.y; 4013 } 4014 path = ['C', nx, ny].concat(path.slice(1)); 4015 break; 4016 case 'T': 4017 if (pathCommand == 'Q' || pathCommand == 'T') { 4018 4019 // In 'T' case we have to take into account, if the previous command is Q/T. 4020 d.qx = d.x * 2 - d.qx; 4021 4022 // And make a reflection similar 4023 d.qy = d.y * 2 - d.qy; 4024 4025 // to case 'S'. 4026 } 4027 else { 4028 4029 // or something else or nothing 4030 d.qx = d.x; 4031 d.qy = d.y; 4032 } 4033 path = ['C'].concat(qubicToCurve(d.x, d.y, d.qx, d.qy, path[1], path[2])); 4034 break; 4035 case 'Q': 4036 d.qx = path[1]; 4037 d.qy = path[2]; 4038 path = ['C'].concat(qubicToCurve(d.x, d.y, path[1], path[2], path[3], path[4])); 4039 break; 4040 case 'L': 4041 path = ['C'].concat(lineToCurve(d.x, d.y, path[1], path[2])); 4042 break; 4043 case 'H': 4044 path = ['C'].concat(lineToCurve(d.x, d.y, path[1], d.y)); 4045 break; 4046 case 'V': 4047 path = ['C'].concat(lineToCurve(d.x, d.y, d.x, path[1])); 4048 break; 4049 case 'Z': 4050 path = ['C'].concat(lineToCurve(d.x, d.y, d.X, d.Y)); 4051 break; 4052 } 4053 4054 return path; 4055 }, 4056 4057 fixArc = function(pp, i) { 4058 4059 if (pp[i].length > 7) { 4060 pp[i].shift(); 4061 var pi = pp[i]; 4062 4063 while (pi.length) { 4064 pathCommands[i] = 'A'; // if created multiple C:s, their original seg is saved 4065 pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6))); 4066 } 4067 4068 pp.splice(i, 1); 4069 ii = curvedPath.length; 4070 } 4071 }, 4072 4073 pathCommands = [], // path commands of original path p 4074 pfirst = '', // temporary holder for original path command 4075 pathCommand = ''; // holder for previous path command of original path 4076 4077 for (var i = 0, ii = curvedPath.length; i < ii; i++) { 4078 curvedPath[i] && (pfirst = curvedPath[i][0]); // save current path command 4079 4080 if (pfirst != 'C') // C is not saved yet, because it may be result of conversion 4081 { 4082 pathCommands[i] = pfirst; // Save current path command 4083 i && (pathCommand = pathCommands[i - 1]); // Get previous path command pathCommand 4084 } 4085 curvedPath[i] = processPath(curvedPath[i], attrs, pathCommand); // Previous path command is inputted to processPath 4086 4087 if (pathCommands[i] != 'A' && pfirst == 'C') pathCommands[i] = 'C'; // A is the only command 4088 // which may produce multiple C:s 4089 // so we have to make sure that C is also C in original path 4090 4091 fixArc(curvedPath, i); // fixArc adds also the right amount of A:s to pathCommands 4092 4093 var seg = curvedPath[i], 4094 seglen = seg.length; 4095 4096 attrs.x = seg[seglen - 2]; 4097 attrs.y = seg[seglen - 1]; 4098 attrs.bx = toFloat(seg[seglen - 4]) || attrs.x; 4099 attrs.by = toFloat(seg[seglen - 3]) || attrs.y; 4100 } 4101 4102 // cache curve 4103 pth.curve = pathClone(curvedPath); 4104 4105 return curvedPath; 4106 } 4107 4108 var intersect = findPathIntersections; 4109 4110 function roundBounds(bounds) { 4111 return { 4112 x: Math.round(bounds.x), 4113 y: Math.round(bounds.y), 4114 width: Math.round(bounds.width), 4115 height: Math.round(bounds.height) 4116 }; 4117 } 4118 4119 4120 function roundPoint(point) { 4121 4122 return { 4123 x: Math.round(point.x), 4124 y: Math.round(point.y) 4125 }; 4126 } 4127 4128 4129 /** 4130 * Convert the given bounds to a { top, left, bottom, right } descriptor. 4131 * 4132 * @param {Bounds|Point} bounds 4133 * 4134 * @return {Object} 4135 */ 4136 function asTRBL(bounds) { 4137 return { 4138 top: bounds.y, 4139 right: bounds.x + (bounds.width || 0), 4140 bottom: bounds.y + (bounds.height || 0), 4141 left: bounds.x 4142 }; 4143 } 4144 4145 4146 /** 4147 * Convert a { top, left, bottom, right } to an objects bounds. 4148 * 4149 * @param {Object} trbl 4150 * 4151 * @return {Bounds} 4152 */ 4153 function asBounds(trbl) { 4154 return { 4155 x: trbl.left, 4156 y: trbl.top, 4157 width: trbl.right - trbl.left, 4158 height: trbl.bottom - trbl.top 4159 }; 4160 } 4161 4162 4163 /** 4164 * Get the mid of the given bounds or point. 4165 * 4166 * @param {Bounds|Point} bounds 4167 * 4168 * @return {Point} 4169 */ 4170 function getMid(bounds) { 4171 return roundPoint({ 4172 x: bounds.x + (bounds.width || 0) / 2, 4173 y: bounds.y + (bounds.height || 0) / 2 4174 }); 4175 } 4176 4177 4178 // orientation utils ////////////////////// 4179 4180 /** 4181 * Get orientation of the given rectangle with respect to 4182 * the reference rectangle. 4183 * 4184 * A padding (positive or negative) may be passed to influence 4185 * horizontal / vertical orientation and intersection. 4186 * 4187 * @param {Bounds} rect 4188 * @param {Bounds} reference 4189 * @param {Point|number} padding 4190 * 4191 * @return {string} the orientation; one of top, top-left, left, ..., bottom, right or intersect. 4192 */ 4193 function getOrientation(rect, reference, padding) { 4194 4195 padding = padding || 0; 4196 4197 // make sure we can use an object, too 4198 // for individual { x, y } padding 4199 if (!isObject(padding)) { 4200 padding = { x: padding, y: padding }; 4201 } 4202 4203 4204 var rectOrientation = asTRBL(rect), 4205 referenceOrientation = asTRBL(reference); 4206 4207 var top = rectOrientation.bottom + padding.y <= referenceOrientation.top, 4208 right = rectOrientation.left - padding.x >= referenceOrientation.right, 4209 bottom = rectOrientation.top - padding.y >= referenceOrientation.bottom, 4210 left = rectOrientation.right + padding.x <= referenceOrientation.left; 4211 4212 var vertical = top ? 'top' : (bottom ? 'bottom' : null), 4213 horizontal = left ? 'left' : (right ? 'right' : null); 4214 4215 if (horizontal && vertical) { 4216 return vertical + '-' + horizontal; 4217 } else { 4218 return horizontal || vertical || 'intersect'; 4219 } 4220 } 4221 4222 4223 // intersection utils ////////////////////// 4224 4225 /** 4226 * Get intersection between an element and a line path. 4227 * 4228 * @param {PathDef} elementPath 4229 * @param {PathDef} linePath 4230 * @param {boolean} cropStart crop from start or end 4231 * 4232 * @return {Point} 4233 */ 4234 function getElementLineIntersection(elementPath, linePath, cropStart) { 4235 4236 var intersections = getIntersections(elementPath, linePath); 4237 4238 // recognize intersections 4239 // only one -> choose 4240 // two close together -> choose first 4241 // two or more distinct -> pull out appropriate one 4242 // none -> ok (fallback to point itself) 4243 if (intersections.length === 1) { 4244 return roundPoint(intersections[0]); 4245 } else if (intersections.length === 2 && pointDistance(intersections[0], intersections[1]) < 1) { 4246 return roundPoint(intersections[0]); 4247 } else if (intersections.length > 1) { 4248 4249 // sort by intersections based on connection segment + 4250 // distance from start 4251 intersections = sortBy(intersections, function(i) { 4252 var distance = Math.floor(i.t2 * 100) || 1; 4253 4254 distance = 100 - distance; 4255 4256 distance = (distance < 10 ? '0' : '') + distance; 4257 4258 // create a sort string that makes sure we sort 4259 // line segment ASC + line segment position DESC (for cropStart) 4260 // line segment ASC + line segment position ASC (for cropEnd) 4261 return i.segment2 + '#' + distance; 4262 }); 4263 4264 return roundPoint(intersections[cropStart ? 0 : intersections.length - 1]); 4265 } 4266 4267 return null; 4268 } 4269 4270 4271 function getIntersections(a, b) { 4272 return intersect(a, b); 4273 } 4274 4275 4276 function filterRedundantWaypoints(waypoints) { 4277 4278 // alter copy of waypoints, not original 4279 waypoints = waypoints.slice(); 4280 4281 var idx = 0, 4282 point, 4283 previousPoint, 4284 nextPoint; 4285 4286 while (waypoints[idx]) { 4287 point = waypoints[idx]; 4288 previousPoint = waypoints[idx - 1]; 4289 nextPoint = waypoints[idx + 1]; 4290 4291 if (pointDistance(point, nextPoint) === 0 || 4292 pointsOnLine(previousPoint, nextPoint, point)) { 4293 4294 // remove point, if overlapping with {nextPoint} 4295 // or on line with {previousPoint} -> {point} -> {nextPoint} 4296 waypoints.splice(idx, 1); 4297 } else { 4298 idx++; 4299 } 4300 } 4301 4302 return waypoints; 4303 } 4304 4305 function round$b(number, resolution) { 4306 return Math.round(number * resolution) / resolution; 4307 } 4308 4309 function ensurePx(number) { 4310 return isNumber(number) ? number + 'px' : number; 4311 } 4312 4313 function findRoot(element) { 4314 while (element.parent) { 4315 element = element.parent; 4316 } 4317 4318 return element; 4319 } 4320 4321 /** 4322 * Creates a HTML container element for a SVG element with 4323 * the given configuration 4324 * 4325 * @param {Object} options 4326 * @return {HTMLElement} the container element 4327 */ 4328 function createContainer(options) { 4329 4330 options = assign({}, { width: '100%', height: '100%' }, options); 4331 4332 var container = options.container || document.body; 4333 4334 // create a <div> around the svg element with the respective size 4335 // this way we can always get the correct container size 4336 // (this is impossible for <svg> elements at the moment) 4337 var parent = document.createElement('div'); 4338 parent.setAttribute('class', 'djs-container'); 4339 4340 assign(parent.style, { 4341 position: 'relative', 4342 overflow: 'hidden', 4343 width: ensurePx(options.width), 4344 height: ensurePx(options.height) 4345 }); 4346 4347 container.appendChild(parent); 4348 4349 return parent; 4350 } 4351 4352 function createGroup(parent, cls, childIndex) { 4353 var group = create$1('g'); 4354 classes(group).add(cls); 4355 4356 var index = childIndex !== undefined ? childIndex : parent.childNodes.length - 1; 4357 4358 // must ensure second argument is node or _null_ 4359 // cf. https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore 4360 parent.insertBefore(group, parent.childNodes[index] || null); 4361 4362 return group; 4363 } 4364 4365 var BASE_LAYER = 'base'; 4366 var HIDDEN_MARKER = 'djs-element-hidden'; 4367 4368 4369 var REQUIRED_MODEL_ATTRS = { 4370 shape: [ 'x', 'y', 'width', 'height' ], 4371 connection: [ 'waypoints' ] 4372 }; 4373 4374 /** 4375 * The main drawing canvas. 4376 * 4377 * @class 4378 * @constructor 4379 * 4380 * @emits Canvas#canvas.init 4381 * 4382 * @param {Object} config 4383 * @param {EventBus} eventBus 4384 * @param {GraphicsFactory} graphicsFactory 4385 * @param {ElementRegistry} elementRegistry 4386 */ 4387 function Canvas(config, eventBus, graphicsFactory, elementRegistry) { 4388 4389 this._eventBus = eventBus; 4390 this._elementRegistry = elementRegistry; 4391 this._graphicsFactory = graphicsFactory; 4392 4393 this._init(config || {}); 4394 } 4395 4396 Canvas.$inject = [ 4397 'config.canvas', 4398 'eventBus', 4399 'graphicsFactory', 4400 'elementRegistry' 4401 ]; 4402 4403 /** 4404 * Creates a <svg> element that is wrapped into a <div>. 4405 * This way we are always able to correctly figure out the size of the svg element 4406 * by querying the parent node. 4407 4408 * (It is not possible to get the size of a svg element cross browser @ 2014-04-01) 4409 4410 * <div class="djs-container" style="width: {desired-width}, height: {desired-height}"> 4411 * <svg width="100%" height="100%"> 4412 * ... 4413 * </svg> 4414 * </div> 4415 */ 4416 Canvas.prototype._init = function(config) { 4417 4418 var eventBus = this._eventBus; 4419 4420 // html container 4421 var container = this._container = createContainer(config); 4422 4423 var svg = this._svg = create$1('svg'); 4424 attr(svg, { width: '100%', height: '100%' }); 4425 4426 append(container, svg); 4427 4428 var viewport = this._viewport = createGroup(svg, 'viewport'); 4429 4430 this._layers = {}; 4431 this._planes = {}; 4432 4433 // debounce canvas.viewbox.changed events 4434 // for smoother diagram interaction 4435 if (config.deferUpdate !== false) { 4436 this._viewboxChanged = debounce(bind$2(this._viewboxChanged, this), 300); 4437 } 4438 4439 eventBus.on('diagram.init', function() { 4440 4441 /** 4442 * An event indicating that the canvas is ready to be drawn on. 4443 * 4444 * @memberOf Canvas 4445 * 4446 * @event canvas.init 4447 * 4448 * @type {Object} 4449 * @property {SVGElement} svg the created svg element 4450 * @property {SVGElement} viewport the direct parent of diagram elements and shapes 4451 */ 4452 eventBus.fire('canvas.init', { 4453 svg: svg, 4454 viewport: viewport 4455 }); 4456 4457 }, this); 4458 4459 // reset viewbox on shape changes to 4460 // recompute the viewbox 4461 eventBus.on([ 4462 'shape.added', 4463 'connection.added', 4464 'shape.removed', 4465 'connection.removed', 4466 'elements.changed' 4467 ], function() { 4468 delete this._cachedViewbox; 4469 }, this); 4470 4471 eventBus.on('diagram.destroy', 500, this._destroy, this); 4472 eventBus.on('diagram.clear', 500, this._clear, this); 4473 }; 4474 4475 Canvas.prototype._destroy = function(emit) { 4476 this._eventBus.fire('canvas.destroy', { 4477 svg: this._svg, 4478 viewport: this._viewport 4479 }); 4480 4481 var parent = this._container.parentNode; 4482 4483 if (parent) { 4484 parent.removeChild(this._container); 4485 } 4486 4487 delete this._svg; 4488 delete this._container; 4489 delete this._layers; 4490 delete this._planes; 4491 delete this._activePlane; 4492 delete this._viewport; 4493 }; 4494 4495 Canvas.prototype._clear = function() { 4496 4497 var self = this; 4498 4499 var allElements = this._elementRegistry.getAll(); 4500 4501 // remove all elements 4502 allElements.forEach(function(element) { 4503 var type = getType(element); 4504 4505 if (type === 'root') { 4506 self.setRootElementForPlane(null, self.findPlane(element), true); 4507 } else { 4508 self._removeElement(element, type); 4509 } 4510 }); 4511 4512 // remove all planes 4513 this._activePlane = null; 4514 this._planes = {}; 4515 4516 // force recomputation of view box 4517 delete this._cachedViewbox; 4518 }; 4519 4520 /** 4521 * Returns the default layer on which 4522 * all elements are drawn. 4523 * 4524 * @returns {SVGElement} 4525 */ 4526 Canvas.prototype.getDefaultLayer = function() { 4527 return this.getLayer(BASE_LAYER); 4528 }; 4529 4530 /** 4531 * Returns a layer that is used to draw elements 4532 * or annotations on it. 4533 * 4534 * Non-existing layers retrieved through this method 4535 * will be created. During creation, the optional index 4536 * may be used to create layers below or above existing layers. 4537 * A layer with a certain index is always created above all 4538 * existing layers with the same index. 4539 * 4540 * @param {string} name 4541 * @param {number} index 4542 * 4543 * @returns {SVGElement} 4544 */ 4545 Canvas.prototype.getLayer = function(name, index) { 4546 4547 if (!name) { 4548 throw new Error('must specify a name'); 4549 } 4550 4551 var layer = this._layers[name]; 4552 4553 if (!layer) { 4554 layer = this._layers[name] = this._createLayer(name, index); 4555 } 4556 4557 // throw an error if layer creation / retrival is 4558 // requested on different index 4559 if (typeof index !== 'undefined' && layer.index !== index) { 4560 throw new Error('layer <' + name + '> already created at index <' + index + '>'); 4561 } 4562 4563 return layer.group; 4564 }; 4565 4566 /** 4567 * Creates a given layer and returns it. 4568 * 4569 * @param {string} name 4570 * @param {number} [index=0] 4571 * 4572 * @return {Object} layer descriptor with { index, group: SVGGroup } 4573 */ 4574 Canvas.prototype._createLayer = function(name, index) { 4575 4576 if (!index) { 4577 index = 0; 4578 } 4579 4580 var childIndex = reduce(this._layers, function(childIndex, layer) { 4581 if (index >= layer.index) { 4582 childIndex++; 4583 } 4584 4585 return childIndex; 4586 }, 0); 4587 4588 return { 4589 group: createGroup(this._viewport, 'layer-' + name, childIndex), 4590 index: index 4591 }; 4592 4593 }; 4594 4595 /** 4596 * Returns a plane that is used to draw elements on it. 4597 * 4598 * @param {string} name 4599 * 4600 * @return {Object} plane descriptor with { layer, rootElement, name } 4601 */ 4602 Canvas.prototype.getPlane = function(name) { 4603 if (!name) { 4604 throw new Error('must specify a name'); 4605 } 4606 4607 var plane = this._planes[name]; 4608 4609 return plane; 4610 }; 4611 4612 /** 4613 * Creates a plane that is used to draw elements on it. If no 4614 * root element is provided, an implicit root will be used. 4615 * 4616 * @param {string} name 4617 * @param {Object|djs.model.Root} [rootElement] optional root element 4618 * 4619 * @return {Object} plane descriptor with { layer, rootElement, name } 4620 */ 4621 Canvas.prototype.createPlane = function(name, rootElement) { 4622 if (!name) { 4623 throw new Error('must specify a name'); 4624 } 4625 4626 if (this._planes[name]) { 4627 throw new Error('plane ' + name + ' already exists'); 4628 } 4629 4630 if (!rootElement) { 4631 rootElement = { 4632 id: '__implicitroot' + name, 4633 children: [], 4634 isImplicit: true 4635 }; 4636 } 4637 4638 var svgLayer = this.getLayer(name); 4639 classes(svgLayer).add(HIDDEN_MARKER); 4640 4641 var plane = this._planes[name] = { 4642 layer: svgLayer, 4643 name: name, 4644 rootElement: null 4645 }; 4646 4647 this.setRootElementForPlane(rootElement, plane); 4648 4649 return plane; 4650 }; 4651 4652 /** 4653 * Sets the active plane and hides the previously active plane. 4654 * 4655 * @param {string|Object} plane 4656 * 4657 * @return {Object} plane descriptor with { layer, rootElement, name } 4658 */ 4659 Canvas.prototype.setActivePlane = function(plane) { 4660 if (!plane) { 4661 throw new Error('must specify a plane'); 4662 } 4663 4664 if (typeof plane === 'string') { 4665 plane = this.getPlane(plane); 4666 } 4667 4668 // hide previous Plane 4669 if (this._activePlane) { 4670 classes(this._activePlane.layer).add(HIDDEN_MARKER); 4671 } 4672 4673 this._activePlane = plane; 4674 4675 // show current Plane 4676 classes(plane.layer).remove(HIDDEN_MARKER); 4677 4678 if (plane.rootElement) { 4679 this._elementRegistry.updateGraphics(plane.rootElement, this._svg, true); 4680 } 4681 4682 this._eventBus.fire('plane.set', { plane: plane }); 4683 4684 return plane; 4685 }; 4686 4687 /** 4688 * Returns the currently active layer 4689 * 4690 * @returns {SVGElement} 4691 */ 4692 4693 Canvas.prototype.getActiveLayer = function() { 4694 return this.getActivePlane().layer; 4695 }; 4696 4697 /** 4698 * Returns the currently active plane. 4699 * 4700 * @return {Object} plane descriptor with { layer, rootElement, name } 4701 */ 4702 Canvas.prototype.getActivePlane = function() { 4703 var plane = this._activePlane; 4704 if (!plane) { 4705 plane = this.createPlane(BASE_LAYER); 4706 this.setActivePlane(BASE_LAYER); 4707 } 4708 4709 return plane; 4710 }; 4711 4712 /** 4713 * Returns the plane which contains the given element. 4714 * 4715 * @param {string|djs.model.Base} element 4716 * 4717 * @return {Object} plane descriptor with { layer, rootElement, name } 4718 */ 4719 Canvas.prototype.findPlane = function(element) { 4720 if (typeof element === 'string') { 4721 element = this._elementRegistry.get(element); 4722 } 4723 4724 var root = findRoot(element); 4725 4726 return find(this._planes, function(plane) { 4727 return plane.rootElement === root; 4728 }); 4729 }; 4730 4731 /** 4732 * Returns the html element that encloses the 4733 * drawing canvas. 4734 * 4735 * @return {DOMNode} 4736 */ 4737 Canvas.prototype.getContainer = function() { 4738 return this._container; 4739 }; 4740 4741 4742 // markers ////////////////////// 4743 4744 Canvas.prototype._updateMarker = function(element, marker, add) { 4745 var container; 4746 4747 if (!element.id) { 4748 element = this._elementRegistry.get(element); 4749 } 4750 4751 // we need to access all 4752 container = this._elementRegistry._elements[element.id]; 4753 4754 if (!container) { 4755 return; 4756 } 4757 4758 forEach([ container.gfx, container.secondaryGfx ], function(gfx) { 4759 if (gfx) { 4760 4761 // invoke either addClass or removeClass based on mode 4762 if (add) { 4763 classes(gfx).add(marker); 4764 } else { 4765 classes(gfx).remove(marker); 4766 } 4767 } 4768 }); 4769 4770 /** 4771 * An event indicating that a marker has been updated for an element 4772 * 4773 * @event element.marker.update 4774 * @type {Object} 4775 * @property {djs.model.Element} element the shape 4776 * @property {Object} gfx the graphical representation of the shape 4777 * @property {string} marker 4778 * @property {boolean} add true if the marker was added, false if it got removed 4779 */ 4780 this._eventBus.fire('element.marker.update', { element: element, gfx: container.gfx, marker: marker, add: !!add }); 4781 }; 4782 4783 4784 /** 4785 * Adds a marker to an element (basically a css class). 4786 * 4787 * Fires the element.marker.update event, making it possible to 4788 * integrate extension into the marker life-cycle, too. 4789 * 4790 * @example 4791 * canvas.addMarker('foo', 'some-marker'); 4792 * 4793 * var fooGfx = canvas.getGraphics('foo'); 4794 * 4795 * fooGfx; // <g class="... some-marker"> ... </g> 4796 * 4797 * @param {string|djs.model.Base} element 4798 * @param {string} marker 4799 */ 4800 Canvas.prototype.addMarker = function(element, marker) { 4801 this._updateMarker(element, marker, true); 4802 }; 4803 4804 4805 /** 4806 * Remove a marker from an element. 4807 * 4808 * Fires the element.marker.update event, making it possible to 4809 * integrate extension into the marker life-cycle, too. 4810 * 4811 * @param {string|djs.model.Base} element 4812 * @param {string} marker 4813 */ 4814 Canvas.prototype.removeMarker = function(element, marker) { 4815 this._updateMarker(element, marker, false); 4816 }; 4817 4818 /** 4819 * Check the existence of a marker on element. 4820 * 4821 * @param {string|djs.model.Base} element 4822 * @param {string} marker 4823 */ 4824 Canvas.prototype.hasMarker = function(element, marker) { 4825 if (!element.id) { 4826 element = this._elementRegistry.get(element); 4827 } 4828 4829 var gfx = this.getGraphics(element); 4830 4831 return classes(gfx).has(marker); 4832 }; 4833 4834 /** 4835 * Toggles a marker on an element. 4836 * 4837 * Fires the element.marker.update event, making it possible to 4838 * integrate extension into the marker life-cycle, too. 4839 * 4840 * @param {string|djs.model.Base} element 4841 * @param {string} marker 4842 */ 4843 Canvas.prototype.toggleMarker = function(element, marker) { 4844 if (this.hasMarker(element, marker)) { 4845 this.removeMarker(element, marker); 4846 } else { 4847 this.addMarker(element, marker); 4848 } 4849 }; 4850 4851 Canvas.prototype.getRootElement = function() { 4852 var plane = this.getActivePlane(); 4853 4854 return plane.rootElement; 4855 }; 4856 4857 4858 4859 // root element handling ////////////////////// 4860 4861 /** 4862 * Sets a given element as the new root element for the canvas 4863 * and returns the new root element. 4864 * 4865 * @param {Object|djs.model.Root} element 4866 * @param {boolean} [override] whether to override the current root element, if any 4867 * 4868 * @return {Object|djs.model.Root} new root element 4869 */ 4870 Canvas.prototype.setRootElement = function(element, override) { 4871 var activePlane = this._activePlane; 4872 4873 if (activePlane) { 4874 return this.setRootElementForPlane(element, activePlane, override); 4875 } else { 4876 var basePlane = this.createPlane(BASE_LAYER, element); 4877 4878 this.setActivePlane(basePlane); 4879 4880 return basePlane.rootElement; 4881 } 4882 }; 4883 4884 4885 /** 4886 * Sets a given element as the new root element for the canvas 4887 * and returns the new root element. 4888 * 4889 * @param {Object|djs.model.Root} element 4890 * @param {Object|djs.model.Root} plane 4891 * @param {boolean} [override] whether to override the current root element, if any 4892 * 4893 * @return {Object|djs.model.Root} new root element 4894 */ 4895 Canvas.prototype.setRootElementForPlane = function(element, plane, override) { 4896 4897 if (typeof plane === 'string') { 4898 plane = this.getPlane(plane); 4899 } 4900 4901 if (element) { 4902 this._ensureValid('root', element); 4903 } 4904 4905 var currentRoot = plane.rootElement, 4906 elementRegistry = this._elementRegistry, 4907 eventBus = this._eventBus; 4908 4909 if (currentRoot) { 4910 if (!override) { 4911 throw new Error('rootElement already set, need to specify override'); 4912 } 4913 4914 // simulate element remove event sequence 4915 eventBus.fire('root.remove', { element: currentRoot }); 4916 eventBus.fire('root.removed', { element: currentRoot }); 4917 4918 elementRegistry.remove(currentRoot); 4919 } 4920 4921 if (element) { 4922 var gfx = plane.layer; 4923 4924 // resemble element add event sequence 4925 eventBus.fire('root.add', { element: element }); 4926 4927 elementRegistry.add(element, gfx); 4928 4929 eventBus.fire('root.added', { element: element, gfx: gfx }); 4930 4931 // associate SVG with root element when active 4932 if (plane === this._activePlane) { 4933 this._elementRegistry.updateGraphics(element, this._svg, true); 4934 } 4935 } 4936 4937 plane.rootElement = element; 4938 4939 return element; 4940 }; 4941 4942 // add functionality ////////////////////// 4943 4944 Canvas.prototype._ensureValid = function(type, element) { 4945 if (!element.id) { 4946 throw new Error('element must have an id'); 4947 } 4948 4949 if (this._elementRegistry.get(element.id)) { 4950 throw new Error('element with id ' + element.id + ' already exists'); 4951 } 4952 4953 var requiredAttrs = REQUIRED_MODEL_ATTRS[type]; 4954 4955 var valid = every(requiredAttrs, function(attr) { 4956 return typeof element[attr] !== 'undefined'; 4957 }); 4958 4959 if (!valid) { 4960 throw new Error( 4961 'must supply { ' + requiredAttrs.join(', ') + ' } with ' + type); 4962 } 4963 }; 4964 4965 Canvas.prototype._setParent = function(element, parent, parentIndex) { 4966 add(parent.children, element, parentIndex); 4967 element.parent = parent; 4968 }; 4969 4970 /** 4971 * Adds an element to the canvas. 4972 * 4973 * This wires the parent <-> child relationship between the element and 4974 * a explicitly specified parent or an implicit root element. 4975 * 4976 * During add it emits the events 4977 * 4978 * * <{type}.add> (element, parent) 4979 * * <{type}.added> (element, gfx) 4980 * 4981 * Extensions may hook into these events to perform their magic. 4982 * 4983 * @param {string} type 4984 * @param {Object|djs.model.Base} element 4985 * @param {Object|djs.model.Base} [parent] 4986 * @param {number} [parentIndex] 4987 * 4988 * @return {Object|djs.model.Base} the added element 4989 */ 4990 Canvas.prototype._addElement = function(type, element, parent, parentIndex) { 4991 4992 parent = parent || this.getRootElement(); 4993 4994 var eventBus = this._eventBus, 4995 graphicsFactory = this._graphicsFactory; 4996 4997 this._ensureValid(type, element); 4998 4999 eventBus.fire(type + '.add', { element: element, parent: parent }); 5000 5001 this._setParent(element, parent, parentIndex); 5002 5003 // create graphics 5004 var gfx = graphicsFactory.create(type, element, parentIndex); 5005 5006 this._elementRegistry.add(element, gfx); 5007 5008 // update its visual 5009 graphicsFactory.update(type, element, gfx); 5010 5011 eventBus.fire(type + '.added', { element: element, gfx: gfx }); 5012 5013 return element; 5014 }; 5015 5016 /** 5017 * Adds a shape to the canvas 5018 * 5019 * @param {Object|djs.model.Shape} shape to add to the diagram 5020 * @param {djs.model.Base} [parent] 5021 * @param {number} [parentIndex] 5022 * 5023 * @return {djs.model.Shape} the added shape 5024 */ 5025 Canvas.prototype.addShape = function(shape, parent, parentIndex) { 5026 return this._addElement('shape', shape, parent, parentIndex); 5027 }; 5028 5029 /** 5030 * Adds a connection to the canvas 5031 * 5032 * @param {Object|djs.model.Connection} connection to add to the diagram 5033 * @param {djs.model.Base} [parent] 5034 * @param {number} [parentIndex] 5035 * 5036 * @return {djs.model.Connection} the added connection 5037 */ 5038 Canvas.prototype.addConnection = function(connection, parent, parentIndex) { 5039 return this._addElement('connection', connection, parent, parentIndex); 5040 }; 5041 5042 5043 /** 5044 * Internal remove element 5045 */ 5046 Canvas.prototype._removeElement = function(element, type) { 5047 5048 var elementRegistry = this._elementRegistry, 5049 graphicsFactory = this._graphicsFactory, 5050 eventBus = this._eventBus; 5051 5052 element = elementRegistry.get(element.id || element); 5053 5054 if (!element) { 5055 5056 // element was removed already 5057 return; 5058 } 5059 5060 eventBus.fire(type + '.remove', { element: element }); 5061 5062 graphicsFactory.remove(element); 5063 5064 // unset parent <-> child relationship 5065 remove(element.parent && element.parent.children, element); 5066 element.parent = null; 5067 5068 eventBus.fire(type + '.removed', { element: element }); 5069 5070 elementRegistry.remove(element); 5071 5072 return element; 5073 }; 5074 5075 5076 /** 5077 * Removes a shape from the canvas 5078 * 5079 * @param {string|djs.model.Shape} shape or shape id to be removed 5080 * 5081 * @return {djs.model.Shape} the removed shape 5082 */ 5083 Canvas.prototype.removeShape = function(shape) { 5084 5085 /** 5086 * An event indicating that a shape is about to be removed from the canvas. 5087 * 5088 * @memberOf Canvas 5089 * 5090 * @event shape.remove 5091 * @type {Object} 5092 * @property {djs.model.Shape} element the shape descriptor 5093 * @property {Object} gfx the graphical representation of the shape 5094 */ 5095 5096 /** 5097 * An event indicating that a shape has been removed from the canvas. 5098 * 5099 * @memberOf Canvas 5100 * 5101 * @event shape.removed 5102 * @type {Object} 5103 * @property {djs.model.Shape} element the shape descriptor 5104 * @property {Object} gfx the graphical representation of the shape 5105 */ 5106 return this._removeElement(shape, 'shape'); 5107 }; 5108 5109 5110 /** 5111 * Removes a connection from the canvas 5112 * 5113 * @param {string|djs.model.Connection} connection or connection id to be removed 5114 * 5115 * @return {djs.model.Connection} the removed connection 5116 */ 5117 Canvas.prototype.removeConnection = function(connection) { 5118 5119 /** 5120 * An event indicating that a connection is about to be removed from the canvas. 5121 * 5122 * @memberOf Canvas 5123 * 5124 * @event connection.remove 5125 * @type {Object} 5126 * @property {djs.model.Connection} element the connection descriptor 5127 * @property {Object} gfx the graphical representation of the connection 5128 */ 5129 5130 /** 5131 * An event indicating that a connection has been removed from the canvas. 5132 * 5133 * @memberOf Canvas 5134 * 5135 * @event connection.removed 5136 * @type {Object} 5137 * @property {djs.model.Connection} element the connection descriptor 5138 * @property {Object} gfx the graphical representation of the connection 5139 */ 5140 return this._removeElement(connection, 'connection'); 5141 }; 5142 5143 5144 /** 5145 * Return the graphical object underlaying a certain diagram element 5146 * 5147 * @param {string|djs.model.Base} element descriptor of the element 5148 * @param {boolean} [secondary=false] whether to return the secondary connected element 5149 * 5150 * @return {SVGElement} 5151 */ 5152 Canvas.prototype.getGraphics = function(element, secondary) { 5153 return this._elementRegistry.getGraphics(element, secondary); 5154 }; 5155 5156 5157 /** 5158 * Perform a viewbox update via a given change function. 5159 * 5160 * @param {Function} changeFn 5161 */ 5162 Canvas.prototype._changeViewbox = function(changeFn) { 5163 5164 // notify others of the upcoming viewbox change 5165 this._eventBus.fire('canvas.viewbox.changing'); 5166 5167 // perform actual change 5168 changeFn.apply(this); 5169 5170 // reset the cached viewbox so that 5171 // a new get operation on viewbox or zoom 5172 // triggers a viewbox re-computation 5173 this._cachedViewbox = null; 5174 5175 // notify others of the change; this step 5176 // may or may not be debounced 5177 this._viewboxChanged(); 5178 }; 5179 5180 Canvas.prototype._viewboxChanged = function() { 5181 this._eventBus.fire('canvas.viewbox.changed', { viewbox: this.viewbox() }); 5182 }; 5183 5184 5185 /** 5186 * Gets or sets the view box of the canvas, i.e. the 5187 * area that is currently displayed. 5188 * 5189 * The getter may return a cached viewbox (if it is currently 5190 * changing). To force a recomputation, pass `false` as the first argument. 5191 * 5192 * @example 5193 * 5194 * canvas.viewbox({ x: 100, y: 100, width: 500, height: 500 }) 5195 * 5196 * // sets the visible area of the diagram to (100|100) -> (600|100) 5197 * // and and scales it according to the diagram width 5198 * 5199 * var viewbox = canvas.viewbox(); // pass `false` to force recomputing the box. 5200 * 5201 * console.log(viewbox); 5202 * // { 5203 * // inner: Dimensions, 5204 * // outer: Dimensions, 5205 * // scale, 5206 * // x, y, 5207 * // width, height 5208 * // } 5209 * 5210 * // if the current diagram is zoomed and scrolled, you may reset it to the 5211 * // default zoom via this method, too: 5212 * 5213 * var zoomedAndScrolledViewbox = canvas.viewbox(); 5214 * 5215 * canvas.viewbox({ 5216 * x: 0, 5217 * y: 0, 5218 * width: zoomedAndScrolledViewbox.outer.width, 5219 * height: zoomedAndScrolledViewbox.outer.height 5220 * }); 5221 * 5222 * @param {Object} [box] the new view box to set 5223 * @param {number} box.x the top left X coordinate of the canvas visible in view box 5224 * @param {number} box.y the top left Y coordinate of the canvas visible in view box 5225 * @param {number} box.width the visible width 5226 * @param {number} box.height 5227 * 5228 * @return {Object} the current view box 5229 */ 5230 Canvas.prototype.viewbox = function(box) { 5231 5232 if (box === undefined && this._cachedViewbox) { 5233 return this._cachedViewbox; 5234 } 5235 5236 var viewport = this._viewport, 5237 innerBox, 5238 outerBox = this.getSize(), 5239 matrix, 5240 transform, 5241 scale, 5242 x, y; 5243 5244 if (!box) { 5245 5246 // compute the inner box based on the 5247 // diagrams default layer. This allows us to exclude 5248 // external components, such as overlays 5249 innerBox = this.getDefaultLayer().getBBox(); 5250 5251 transform = transform$1(viewport); 5252 matrix = transform ? transform.matrix : createMatrix(); 5253 scale = round$b(matrix.a, 1000); 5254 5255 x = round$b(-matrix.e || 0, 1000); 5256 y = round$b(-matrix.f || 0, 1000); 5257 5258 box = this._cachedViewbox = { 5259 x: x ? x / scale : 0, 5260 y: y ? y / scale : 0, 5261 width: outerBox.width / scale, 5262 height: outerBox.height / scale, 5263 scale: scale, 5264 inner: { 5265 width: innerBox.width, 5266 height: innerBox.height, 5267 x: innerBox.x, 5268 y: innerBox.y 5269 }, 5270 outer: outerBox 5271 }; 5272 5273 return box; 5274 } else { 5275 5276 this._changeViewbox(function() { 5277 scale = Math.min(outerBox.width / box.width, outerBox.height / box.height); 5278 5279 var matrix = this._svg.createSVGMatrix() 5280 .scale(scale) 5281 .translate(-box.x, -box.y); 5282 5283 transform$1(viewport, matrix); 5284 }); 5285 } 5286 5287 return box; 5288 }; 5289 5290 5291 /** 5292 * Gets or sets the scroll of the canvas. 5293 * 5294 * @param {Object} [delta] the new scroll to apply. 5295 * 5296 * @param {number} [delta.dx] 5297 * @param {number} [delta.dy] 5298 */ 5299 Canvas.prototype.scroll = function(delta) { 5300 5301 var node = this._viewport; 5302 var matrix = node.getCTM(); 5303 5304 if (delta) { 5305 this._changeViewbox(function() { 5306 delta = assign({ dx: 0, dy: 0 }, delta || {}); 5307 5308 matrix = this._svg.createSVGMatrix().translate(delta.dx, delta.dy).multiply(matrix); 5309 5310 setCTM(node, matrix); 5311 }); 5312 } 5313 5314 return { x: matrix.e, y: matrix.f }; 5315 }; 5316 5317 /** 5318 * Scrolls the viewbox to contain the given element. 5319 * Optionally specify a padding to be applied to the edges. 5320 * 5321 * @param {Object} [element] the element to scroll to. 5322 * @param {Object|Number} [padding=100] the padding to be applied. Can also specify top, bottom, left and right. 5323 * 5324 */ 5325 Canvas.prototype.scrollToElement = function(element, padding) { 5326 var defaultPadding = 100; 5327 5328 // switch to correct Plane 5329 var targetPlane = this.findPlane(element); 5330 if (targetPlane !== this._activePlane) { 5331 this.setActivePlane(targetPlane); 5332 } 5333 5334 if (!padding) { 5335 padding = {}; 5336 } 5337 if (typeof padding === 'number') { 5338 defaultPadding = padding; 5339 } 5340 5341 padding = { 5342 top: padding.top || defaultPadding, 5343 right: padding.right || defaultPadding, 5344 bottom: padding.bottom || defaultPadding, 5345 left: padding.left || defaultPadding 5346 }; 5347 5348 var elementBounds = getBBox(element), 5349 elementTrbl = asTRBL(elementBounds), 5350 viewboxBounds = this.viewbox(), 5351 zoom = this.zoom(), 5352 dx, dy; 5353 5354 // shrink viewboxBounds with padding 5355 viewboxBounds.y += padding.top / zoom; 5356 viewboxBounds.x += padding.left / zoom; 5357 viewboxBounds.width -= (padding.right + padding.left) / zoom; 5358 viewboxBounds.height -= (padding.bottom + padding.top) / zoom; 5359 5360 var viewboxTrbl = asTRBL(viewboxBounds); 5361 5362 var canFit = elementBounds.width < viewboxBounds.width && elementBounds.height < viewboxBounds.height; 5363 5364 if (!canFit) { 5365 5366 // top-left when element can't fit 5367 dx = elementBounds.x - viewboxBounds.x; 5368 dy = elementBounds.y - viewboxBounds.y; 5369 5370 } else { 5371 5372 var dRight = Math.max(0, elementTrbl.right - viewboxTrbl.right), 5373 dLeft = Math.min(0, elementTrbl.left - viewboxTrbl.left), 5374 dBottom = Math.max(0, elementTrbl.bottom - viewboxTrbl.bottom), 5375 dTop = Math.min(0, elementTrbl.top - viewboxTrbl.top); 5376 5377 dx = dRight || dLeft; 5378 dy = dBottom || dTop; 5379 5380 } 5381 5382 this.scroll({ dx: -dx * zoom, dy: -dy * zoom }); 5383 }; 5384 5385 /** 5386 * Gets or sets the current zoom of the canvas, optionally zooming 5387 * to the specified position. 5388 * 5389 * The getter may return a cached zoom level. Call it with `false` as 5390 * the first argument to force recomputation of the current level. 5391 * 5392 * @param {string|number} [newScale] the new zoom level, either a number, i.e. 0.9, 5393 * or `fit-viewport` to adjust the size to fit the current viewport 5394 * @param {string|Point} [center] the reference point { x: .., y: ..} to zoom to, 'auto' to zoom into mid or null 5395 * 5396 * @return {number} the current scale 5397 */ 5398 Canvas.prototype.zoom = function(newScale, center) { 5399 5400 if (!newScale) { 5401 return this.viewbox(newScale).scale; 5402 } 5403 5404 if (newScale === 'fit-viewport') { 5405 return this._fitViewport(center); 5406 } 5407 5408 var outer, 5409 matrix; 5410 5411 this._changeViewbox(function() { 5412 5413 if (typeof center !== 'object') { 5414 outer = this.viewbox().outer; 5415 5416 center = { 5417 x: outer.width / 2, 5418 y: outer.height / 2 5419 }; 5420 } 5421 5422 matrix = this._setZoom(newScale, center); 5423 }); 5424 5425 return round$b(matrix.a, 1000); 5426 }; 5427 5428 function setCTM(node, m) { 5429 var mstr = 'matrix(' + m.a + ',' + m.b + ',' + m.c + ',' + m.d + ',' + m.e + ',' + m.f + ')'; 5430 node.setAttribute('transform', mstr); 5431 } 5432 5433 Canvas.prototype._fitViewport = function(center) { 5434 5435 var vbox = this.viewbox(), 5436 outer = vbox.outer, 5437 inner = vbox.inner, 5438 newScale, 5439 newViewbox; 5440 5441 // display the complete diagram without zooming in. 5442 // instead of relying on internal zoom, we perform a 5443 // hard reset on the canvas viewbox to realize this 5444 // 5445 // if diagram does not need to be zoomed in, we focus it around 5446 // the diagram origin instead 5447 5448 if (inner.x >= 0 && 5449 inner.y >= 0 && 5450 inner.x + inner.width <= outer.width && 5451 inner.y + inner.height <= outer.height && 5452 !center) { 5453 5454 newViewbox = { 5455 x: 0, 5456 y: 0, 5457 width: Math.max(inner.width + inner.x, outer.width), 5458 height: Math.max(inner.height + inner.y, outer.height) 5459 }; 5460 } else { 5461 5462 newScale = Math.min(1, outer.width / inner.width, outer.height / inner.height); 5463 newViewbox = { 5464 x: inner.x + (center ? inner.width / 2 - outer.width / newScale / 2 : 0), 5465 y: inner.y + (center ? inner.height / 2 - outer.height / newScale / 2 : 0), 5466 width: outer.width / newScale, 5467 height: outer.height / newScale 5468 }; 5469 } 5470 5471 this.viewbox(newViewbox); 5472 5473 return this.viewbox(false).scale; 5474 }; 5475 5476 5477 Canvas.prototype._setZoom = function(scale, center) { 5478 5479 var svg = this._svg, 5480 viewport = this._viewport; 5481 5482 var matrix = svg.createSVGMatrix(); 5483 var point = svg.createSVGPoint(); 5484 5485 var centerPoint, 5486 originalPoint, 5487 currentMatrix, 5488 scaleMatrix, 5489 newMatrix; 5490 5491 currentMatrix = viewport.getCTM(); 5492 5493 var currentScale = currentMatrix.a; 5494 5495 if (center) { 5496 centerPoint = assign(point, center); 5497 5498 // revert applied viewport transformations 5499 originalPoint = centerPoint.matrixTransform(currentMatrix.inverse()); 5500 5501 // create scale matrix 5502 scaleMatrix = matrix 5503 .translate(originalPoint.x, originalPoint.y) 5504 .scale(1 / currentScale * scale) 5505 .translate(-originalPoint.x, -originalPoint.y); 5506 5507 newMatrix = currentMatrix.multiply(scaleMatrix); 5508 } else { 5509 newMatrix = matrix.scale(scale); 5510 } 5511 5512 setCTM(this._viewport, newMatrix); 5513 5514 return newMatrix; 5515 }; 5516 5517 5518 /** 5519 * Returns the size of the canvas 5520 * 5521 * @return {Dimensions} 5522 */ 5523 Canvas.prototype.getSize = function() { 5524 return { 5525 width: this._container.clientWidth, 5526 height: this._container.clientHeight 5527 }; 5528 }; 5529 5530 5531 /** 5532 * Return the absolute bounding box for the given element 5533 * 5534 * The absolute bounding box may be used to display overlays in the 5535 * callers (browser) coordinate system rather than the zoomed in/out 5536 * canvas coordinates. 5537 * 5538 * @param {ElementDescriptor} element 5539 * @return {Bounds} the absolute bounding box 5540 */ 5541 Canvas.prototype.getAbsoluteBBox = function(element) { 5542 var vbox = this.viewbox(); 5543 var bbox; 5544 5545 // connection 5546 // use svg bbox 5547 if (element.waypoints) { 5548 var gfx = this.getGraphics(element); 5549 5550 bbox = gfx.getBBox(); 5551 } 5552 5553 // shapes 5554 // use data 5555 else { 5556 bbox = element; 5557 } 5558 5559 var x = bbox.x * vbox.scale - vbox.x * vbox.scale; 5560 var y = bbox.y * vbox.scale - vbox.y * vbox.scale; 5561 5562 var width = bbox.width * vbox.scale; 5563 var height = bbox.height * vbox.scale; 5564 5565 return { 5566 x: x, 5567 y: y, 5568 width: width, 5569 height: height 5570 }; 5571 }; 5572 5573 /** 5574 * Fires an event in order other modules can react to the 5575 * canvas resizing 5576 */ 5577 Canvas.prototype.resized = function() { 5578 5579 // force recomputation of view box 5580 delete this._cachedViewbox; 5581 5582 this._eventBus.fire('canvas.resized'); 5583 }; 5584 5585 var ELEMENT_ID = 'data-element-id'; 5586 5587 5588 /** 5589 * @class 5590 * 5591 * A registry that keeps track of all shapes in the diagram. 5592 */ 5593 function ElementRegistry(eventBus) { 5594 this._elements = {}; 5595 5596 this._eventBus = eventBus; 5597 } 5598 5599 ElementRegistry.$inject = [ 'eventBus' ]; 5600 5601 /** 5602 * Register a pair of (element, gfx, (secondaryGfx)). 5603 * 5604 * @param {djs.model.Base} element 5605 * @param {SVGElement} gfx 5606 * @param {SVGElement} [secondaryGfx] optional other element to register, too 5607 */ 5608 ElementRegistry.prototype.add = function(element, gfx, secondaryGfx) { 5609 5610 var id = element.id; 5611 5612 this._validateId(id); 5613 5614 // associate dom node with element 5615 attr(gfx, ELEMENT_ID, id); 5616 5617 if (secondaryGfx) { 5618 attr(secondaryGfx, ELEMENT_ID, id); 5619 } 5620 5621 this._elements[id] = { element: element, gfx: gfx, secondaryGfx: secondaryGfx }; 5622 }; 5623 5624 /** 5625 * Removes an element from the registry. 5626 * 5627 * @param {djs.model.Base} element 5628 */ 5629 ElementRegistry.prototype.remove = function(element) { 5630 var elements = this._elements, 5631 id = element.id || element, 5632 container = id && elements[id]; 5633 5634 if (container) { 5635 5636 // unset element id on gfx 5637 attr(container.gfx, ELEMENT_ID, ''); 5638 5639 if (container.secondaryGfx) { 5640 attr(container.secondaryGfx, ELEMENT_ID, ''); 5641 } 5642 5643 delete elements[id]; 5644 } 5645 }; 5646 5647 /** 5648 * Update the id of an element 5649 * 5650 * @param {djs.model.Base} element 5651 * @param {string} newId 5652 */ 5653 ElementRegistry.prototype.updateId = function(element, newId) { 5654 5655 this._validateId(newId); 5656 5657 if (typeof element === 'string') { 5658 element = this.get(element); 5659 } 5660 5661 this._eventBus.fire('element.updateId', { 5662 element: element, 5663 newId: newId 5664 }); 5665 5666 var gfx = this.getGraphics(element), 5667 secondaryGfx = this.getGraphics(element, true); 5668 5669 this.remove(element); 5670 5671 element.id = newId; 5672 5673 this.add(element, gfx, secondaryGfx); 5674 }; 5675 5676 /** 5677 * Update the graphics of an element 5678 * 5679 * @param {djs.model.Base} element 5680 * @param {SVGElement} gfx 5681 * @param {boolean} [secondary=false] whether to update the secondary connected element 5682 */ 5683 ElementRegistry.prototype.updateGraphics = function(filter, gfx, secondary) { 5684 var id = filter.id || filter; 5685 5686 var container = this._elements[id]; 5687 5688 if (secondary) { 5689 container.secondaryGfx = gfx; 5690 } else { 5691 container.gfx = gfx; 5692 } 5693 5694 attr(gfx, ELEMENT_ID, id); 5695 5696 return gfx; 5697 }; 5698 5699 /** 5700 * Return the model element for a given id or graphics. 5701 * 5702 * @example 5703 * 5704 * elementRegistry.get('SomeElementId_1'); 5705 * elementRegistry.get(gfx); 5706 * 5707 * 5708 * @param {string|SVGElement} filter for selecting the element 5709 * 5710 * @return {djs.model.Base} 5711 */ 5712 ElementRegistry.prototype.get = function(filter) { 5713 var id; 5714 5715 if (typeof filter === 'string') { 5716 id = filter; 5717 } else { 5718 id = filter && attr(filter, ELEMENT_ID); 5719 } 5720 5721 var container = this._elements[id]; 5722 return container && container.element; 5723 }; 5724 5725 /** 5726 * Return all elements that match a given filter function. 5727 * 5728 * @param {Function} fn 5729 * 5730 * @return {Array<djs.model.Base>} 5731 */ 5732 ElementRegistry.prototype.filter = function(fn) { 5733 5734 var filtered = []; 5735 5736 this.forEach(function(element, gfx) { 5737 if (fn(element, gfx)) { 5738 filtered.push(element); 5739 } 5740 }); 5741 5742 return filtered; 5743 }; 5744 5745 /** 5746 * Return the first element that satisfies the provided testing function. 5747 * 5748 * @param {Function} fn 5749 * 5750 * @return {djs.model.Base} 5751 */ 5752 ElementRegistry.prototype.find = function(fn) { 5753 var map = this._elements, 5754 keys = Object.keys(map); 5755 5756 for (var i = 0; i < keys.length; i++) { 5757 var id = keys[i], 5758 container = map[id], 5759 element = container.element, 5760 gfx = container.gfx; 5761 5762 if (fn(element, gfx)) { 5763 return element; 5764 } 5765 } 5766 }; 5767 5768 /** 5769 * Return all rendered model elements. 5770 * 5771 * @return {Array<djs.model.Base>} 5772 */ 5773 ElementRegistry.prototype.getAll = function() { 5774 return this.filter(function(e) { return e; }); 5775 }; 5776 5777 /** 5778 * Iterate over all diagram elements. 5779 * 5780 * @param {Function} fn 5781 */ 5782 ElementRegistry.prototype.forEach = function(fn) { 5783 5784 var map = this._elements; 5785 5786 Object.keys(map).forEach(function(id) { 5787 var container = map[id], 5788 element = container.element, 5789 gfx = container.gfx; 5790 5791 return fn(element, gfx); 5792 }); 5793 }; 5794 5795 /** 5796 * Return the graphical representation of an element or its id. 5797 * 5798 * @example 5799 * elementRegistry.getGraphics('SomeElementId_1'); 5800 * elementRegistry.getGraphics(rootElement); // <g ...> 5801 * 5802 * elementRegistry.getGraphics(rootElement, true); // <svg ...> 5803 * 5804 * 5805 * @param {string|djs.model.Base} filter 5806 * @param {boolean} [secondary=false] whether to return the secondary connected element 5807 * 5808 * @return {SVGElement} 5809 */ 5810 ElementRegistry.prototype.getGraphics = function(filter, secondary) { 5811 var id = filter.id || filter; 5812 5813 var container = this._elements[id]; 5814 return container && (secondary ? container.secondaryGfx : container.gfx); 5815 }; 5816 5817 /** 5818 * Validate the suitability of the given id and signals a problem 5819 * with an exception. 5820 * 5821 * @param {string} id 5822 * 5823 * @throws {Error} if id is empty or already assigned 5824 */ 5825 ElementRegistry.prototype._validateId = function(id) { 5826 if (!id) { 5827 throw new Error('element must have an id'); 5828 } 5829 5830 if (this._elements[id]) { 5831 throw new Error('element with id ' + id + ' already added'); 5832 } 5833 }; 5834 5835 var objectRefs = {exports: {}}; 5836 5837 var collection = {}; 5838 5839 /** 5840 * An empty collection stub. Use {@link RefsCollection.extend} to extend a 5841 * collection with ref semantics. 5842 * 5843 * @class RefsCollection 5844 */ 5845 5846 /** 5847 * Extends a collection with {@link Refs} aware methods 5848 * 5849 * @memberof RefsCollection 5850 * @static 5851 * 5852 * @param {Array<Object>} collection 5853 * @param {Refs} refs instance 5854 * @param {Object} property represented by the collection 5855 * @param {Object} target object the collection is attached to 5856 * 5857 * @return {RefsCollection<Object>} the extended array 5858 */ 5859 function extend(collection, refs, property, target) { 5860 5861 var inverseProperty = property.inverse; 5862 5863 /** 5864 * Removes the given element from the array and returns it. 5865 * 5866 * @method RefsCollection#remove 5867 * 5868 * @param {Object} element the element to remove 5869 */ 5870 Object.defineProperty(collection, 'remove', { 5871 value: function(element) { 5872 var idx = this.indexOf(element); 5873 if (idx !== -1) { 5874 this.splice(idx, 1); 5875 5876 // unset inverse 5877 refs.unset(element, inverseProperty, target); 5878 } 5879 5880 return element; 5881 } 5882 }); 5883 5884 /** 5885 * Returns true if the collection contains the given element 5886 * 5887 * @method RefsCollection#contains 5888 * 5889 * @param {Object} element the element to check for 5890 */ 5891 Object.defineProperty(collection, 'contains', { 5892 value: function(element) { 5893 return this.indexOf(element) !== -1; 5894 } 5895 }); 5896 5897 /** 5898 * Adds an element to the array, unless it exists already (set semantics). 5899 * 5900 * @method RefsCollection#add 5901 * 5902 * @param {Object} element the element to add 5903 * @param {Number} optional index to add element to 5904 * (possibly moving other elements around) 5905 */ 5906 Object.defineProperty(collection, 'add', { 5907 value: function(element, idx) { 5908 5909 var currentIdx = this.indexOf(element); 5910 5911 if (typeof idx === 'undefined') { 5912 5913 if (currentIdx !== -1) { 5914 // element already in collection (!) 5915 return; 5916 } 5917 5918 // add to end of array, as no idx is specified 5919 idx = this.length; 5920 } 5921 5922 // handle already in collection 5923 if (currentIdx !== -1) { 5924 5925 // remove element from currentIdx 5926 this.splice(currentIdx, 1); 5927 } 5928 5929 // add element at idx 5930 this.splice(idx, 0, element); 5931 5932 if (currentIdx === -1) { 5933 // set inverse, unless element was 5934 // in collection already 5935 refs.set(element, inverseProperty, target); 5936 } 5937 } 5938 }); 5939 5940 // a simple marker, identifying this element 5941 // as being a refs collection 5942 Object.defineProperty(collection, '__refs_collection', { 5943 value: true 5944 }); 5945 5946 return collection; 5947 } 5948 5949 5950 function isExtended(collection) { 5951 return collection.__refs_collection === true; 5952 } 5953 5954 collection.extend = extend; 5955 5956 collection.isExtended = isExtended; 5957 5958 var Collection = collection; 5959 5960 function hasOwnProperty$1(e, property) { 5961 return Object.prototype.hasOwnProperty.call(e, property.name || property); 5962 } 5963 5964 function defineCollectionProperty(ref, property, target) { 5965 5966 var collection = Collection.extend(target[property.name] || [], ref, property, target); 5967 5968 Object.defineProperty(target, property.name, { 5969 enumerable: property.enumerable, 5970 value: collection 5971 }); 5972 5973 if (collection.length) { 5974 5975 collection.forEach(function(o) { 5976 ref.set(o, property.inverse, target); 5977 }); 5978 } 5979 } 5980 5981 5982 function defineProperty$1(ref, property, target) { 5983 5984 var inverseProperty = property.inverse; 5985 5986 var _value = target[property.name]; 5987 5988 Object.defineProperty(target, property.name, { 5989 configurable: property.configurable, 5990 enumerable: property.enumerable, 5991 5992 get: function() { 5993 return _value; 5994 }, 5995 5996 set: function(value) { 5997 5998 // return if we already performed all changes 5999 if (value === _value) { 6000 return; 6001 } 6002 6003 var old = _value; 6004 6005 // temporary set null 6006 _value = null; 6007 6008 if (old) { 6009 ref.unset(old, inverseProperty, target); 6010 } 6011 6012 // set new value 6013 _value = value; 6014 6015 // set inverse value 6016 ref.set(_value, inverseProperty, target); 6017 } 6018 }); 6019 6020 } 6021 6022 /** 6023 * Creates a new references object defining two inversly related 6024 * attribute descriptors a and b. 6025 * 6026 * <p> 6027 * When bound to an object using {@link Refs#bind} the references 6028 * get activated and ensure that add and remove operations are applied 6029 * reversely, too. 6030 * </p> 6031 * 6032 * <p> 6033 * For attributes represented as collections {@link Refs} provides the 6034 * {@link RefsCollection#add}, {@link RefsCollection#remove} and {@link RefsCollection#contains} extensions 6035 * that must be used to properly hook into the inverse change mechanism. 6036 * </p> 6037 * 6038 * @class Refs 6039 * 6040 * @classdesc A bi-directional reference between two attributes. 6041 * 6042 * @param {Refs.AttributeDescriptor} a property descriptor 6043 * @param {Refs.AttributeDescriptor} b property descriptor 6044 * 6045 * @example 6046 * 6047 * var refs = Refs({ name: 'wheels', collection: true, enumerable: true }, { name: 'car' }); 6048 * 6049 * var car = { name: 'toyota' }; 6050 * var wheels = [{ pos: 'front-left' }, { pos: 'front-right' }]; 6051 * 6052 * refs.bind(car, 'wheels'); 6053 * 6054 * car.wheels // [] 6055 * car.wheels.add(wheels[0]); 6056 * car.wheels.add(wheels[1]); 6057 * 6058 * car.wheels // [{ pos: 'front-left' }, { pos: 'front-right' }] 6059 * 6060 * wheels[0].car // { name: 'toyota' }; 6061 * car.wheels.remove(wheels[0]); 6062 * 6063 * wheels[0].car // undefined 6064 */ 6065 function Refs$1(a, b) { 6066 6067 if (!(this instanceof Refs$1)) { 6068 return new Refs$1(a, b); 6069 } 6070 6071 // link 6072 a.inverse = b; 6073 b.inverse = a; 6074 6075 this.props = {}; 6076 this.props[a.name] = a; 6077 this.props[b.name] = b; 6078 } 6079 6080 /** 6081 * Binds one side of a bi-directional reference to a 6082 * target object. 6083 * 6084 * @memberOf Refs 6085 * 6086 * @param {Object} target 6087 * @param {String} property 6088 */ 6089 Refs$1.prototype.bind = function(target, property) { 6090 if (typeof property === 'string') { 6091 if (!this.props[property]) { 6092 throw new Error('no property <' + property + '> in ref'); 6093 } 6094 property = this.props[property]; 6095 } 6096 6097 if (property.collection) { 6098 defineCollectionProperty(this, property, target); 6099 } else { 6100 defineProperty$1(this, property, target); 6101 } 6102 }; 6103 6104 Refs$1.prototype.ensureRefsCollection = function(target, property) { 6105 6106 var collection = target[property.name]; 6107 6108 if (!Collection.isExtended(collection)) { 6109 defineCollectionProperty(this, property, target); 6110 } 6111 6112 return collection; 6113 }; 6114 6115 Refs$1.prototype.ensureBound = function(target, property) { 6116 if (!hasOwnProperty$1(target, property)) { 6117 this.bind(target, property); 6118 } 6119 }; 6120 6121 Refs$1.prototype.unset = function(target, property, value) { 6122 6123 if (target) { 6124 this.ensureBound(target, property); 6125 6126 if (property.collection) { 6127 this.ensureRefsCollection(target, property).remove(value); 6128 } else { 6129 target[property.name] = undefined; 6130 } 6131 } 6132 }; 6133 6134 Refs$1.prototype.set = function(target, property, value) { 6135 6136 if (target) { 6137 this.ensureBound(target, property); 6138 6139 if (property.collection) { 6140 this.ensureRefsCollection(target, property).add(value); 6141 } else { 6142 target[property.name] = value; 6143 } 6144 } 6145 }; 6146 6147 var refs = Refs$1; 6148 6149 objectRefs.exports = refs; 6150 6151 objectRefs.exports.Collection = collection; 6152 6153 var Refs = objectRefs.exports; 6154 6155 var parentRefs = new Refs({ name: 'children', enumerable: true, collection: true }, { name: 'parent' }), 6156 labelRefs = new Refs({ name: 'labels', enumerable: true, collection: true }, { name: 'labelTarget' }), 6157 attacherRefs = new Refs({ name: 'attachers', collection: true }, { name: 'host' }), 6158 outgoingRefs = new Refs({ name: 'outgoing', collection: true }, { name: 'source' }), 6159 incomingRefs = new Refs({ name: 'incoming', collection: true }, { name: 'target' }); 6160 6161 /** 6162 * @namespace djs.model 6163 */ 6164 6165 /** 6166 * @memberOf djs.model 6167 */ 6168 6169 /** 6170 * The basic graphical representation 6171 * 6172 * @class 6173 * 6174 * @abstract 6175 */ 6176 function Base$1() { 6177 6178 /** 6179 * The object that backs up the shape 6180 * 6181 * @name Base#businessObject 6182 * @type Object 6183 */ 6184 Object.defineProperty(this, 'businessObject', { 6185 writable: true 6186 }); 6187 6188 6189 /** 6190 * Single label support, will mapped to multi label array 6191 * 6192 * @name Base#label 6193 * @type Object 6194 */ 6195 Object.defineProperty(this, 'label', { 6196 get: function() { 6197 return this.labels[0]; 6198 }, 6199 set: function(newLabel) { 6200 6201 var label = this.label, 6202 labels = this.labels; 6203 6204 if (!newLabel && label) { 6205 labels.remove(label); 6206 } else { 6207 labels.add(newLabel, 0); 6208 } 6209 } 6210 }); 6211 6212 /** 6213 * The parent shape 6214 * 6215 * @name Base#parent 6216 * @type Shape 6217 */ 6218 parentRefs.bind(this, 'parent'); 6219 6220 /** 6221 * The list of labels 6222 * 6223 * @name Base#labels 6224 * @type Label 6225 */ 6226 labelRefs.bind(this, 'labels'); 6227 6228 /** 6229 * The list of outgoing connections 6230 * 6231 * @name Base#outgoing 6232 * @type Array<Connection> 6233 */ 6234 outgoingRefs.bind(this, 'outgoing'); 6235 6236 /** 6237 * The list of incoming connections 6238 * 6239 * @name Base#incoming 6240 * @type Array<Connection> 6241 */ 6242 incomingRefs.bind(this, 'incoming'); 6243 } 6244 6245 6246 /** 6247 * A graphical object 6248 * 6249 * @class 6250 * @constructor 6251 * 6252 * @extends Base 6253 */ 6254 function Shape() { 6255 Base$1.call(this); 6256 6257 /** 6258 * Indicates frame shapes 6259 * 6260 * @name Shape#isFrame 6261 * @type boolean 6262 */ 6263 6264 /** 6265 * The list of children 6266 * 6267 * @name Shape#children 6268 * @type Array<Base> 6269 */ 6270 parentRefs.bind(this, 'children'); 6271 6272 /** 6273 * @name Shape#host 6274 * @type Shape 6275 */ 6276 attacherRefs.bind(this, 'host'); 6277 6278 /** 6279 * @name Shape#attachers 6280 * @type Shape 6281 */ 6282 attacherRefs.bind(this, 'attachers'); 6283 } 6284 6285 inherits$1(Shape, Base$1); 6286 6287 6288 /** 6289 * A root graphical object 6290 * 6291 * @class 6292 * @constructor 6293 * 6294 * @extends Shape 6295 */ 6296 function Root() { 6297 Shape.call(this); 6298 } 6299 6300 inherits$1(Root, Shape); 6301 6302 6303 /** 6304 * A label for an element 6305 * 6306 * @class 6307 * @constructor 6308 * 6309 * @extends Shape 6310 */ 6311 function Label() { 6312 Shape.call(this); 6313 6314 /** 6315 * The labeled element 6316 * 6317 * @name Label#labelTarget 6318 * @type Base 6319 */ 6320 labelRefs.bind(this, 'labelTarget'); 6321 } 6322 6323 inherits$1(Label, Shape); 6324 6325 6326 /** 6327 * A connection between two elements 6328 * 6329 * @class 6330 * @constructor 6331 * 6332 * @extends Base 6333 */ 6334 function Connection() { 6335 Base$1.call(this); 6336 6337 /** 6338 * The element this connection originates from 6339 * 6340 * @name Connection#source 6341 * @type Base 6342 */ 6343 outgoingRefs.bind(this, 'source'); 6344 6345 /** 6346 * The element this connection points to 6347 * 6348 * @name Connection#target 6349 * @type Base 6350 */ 6351 incomingRefs.bind(this, 'target'); 6352 } 6353 6354 inherits$1(Connection, Base$1); 6355 6356 6357 var types$6 = { 6358 connection: Connection, 6359 shape: Shape, 6360 label: Label, 6361 root: Root 6362 }; 6363 6364 /** 6365 * Creates a new model element of the specified type 6366 * 6367 * @method create 6368 * 6369 * @example 6370 * 6371 * var shape1 = Model.create('shape', { x: 10, y: 10, width: 100, height: 100 }); 6372 * var shape2 = Model.create('shape', { x: 210, y: 210, width: 100, height: 100 }); 6373 * 6374 * var connection = Model.create('connection', { waypoints: [ { x: 110, y: 55 }, {x: 210, y: 55 } ] }); 6375 * 6376 * @param {string} type lower-cased model name 6377 * @param {Object} attrs attributes to initialize the new model instance with 6378 * 6379 * @return {Base} the new model instance 6380 */ 6381 function create(type, attrs) { 6382 var Type = types$6[type]; 6383 if (!Type) { 6384 throw new Error('unknown type: <' + type + '>'); 6385 } 6386 return assign(new Type(), attrs); 6387 } 6388 6389 /** 6390 * A factory for diagram-js shapes 6391 */ 6392 function ElementFactory$1() { 6393 this._uid = 12; 6394 } 6395 6396 6397 ElementFactory$1.prototype.createRoot = function(attrs) { 6398 return this.create('root', attrs); 6399 }; 6400 6401 ElementFactory$1.prototype.createLabel = function(attrs) { 6402 return this.create('label', attrs); 6403 }; 6404 6405 ElementFactory$1.prototype.createShape = function(attrs) { 6406 return this.create('shape', attrs); 6407 }; 6408 6409 ElementFactory$1.prototype.createConnection = function(attrs) { 6410 return this.create('connection', attrs); 6411 }; 6412 6413 /** 6414 * Create a model element with the given type and 6415 * a number of pre-set attributes. 6416 * 6417 * @param {string} type 6418 * @param {Object} attrs 6419 * @return {djs.model.Base} the newly created model instance 6420 */ 6421 ElementFactory$1.prototype.create = function(type, attrs) { 6422 6423 attrs = assign({}, attrs || {}); 6424 6425 if (!attrs.id) { 6426 attrs.id = type + '_' + (this._uid++); 6427 } 6428 6429 return create(type, attrs); 6430 }; 6431 6432 var FN_REF = '__fn'; 6433 6434 var DEFAULT_PRIORITY$5 = 1000; 6435 6436 var slice = Array.prototype.slice; 6437 6438 /** 6439 * A general purpose event bus. 6440 * 6441 * This component is used to communicate across a diagram instance. 6442 * Other parts of a diagram can use it to listen to and broadcast events. 6443 * 6444 * 6445 * ## Registering for Events 6446 * 6447 * The event bus provides the {@link EventBus#on} and {@link EventBus#once} 6448 * methods to register for events. {@link EventBus#off} can be used to 6449 * remove event registrations. Listeners receive an instance of {@link Event} 6450 * as the first argument. It allows them to hook into the event execution. 6451 * 6452 * ```javascript 6453 * 6454 * // listen for event 6455 * eventBus.on('foo', function(event) { 6456 * 6457 * // access event type 6458 * event.type; // 'foo' 6459 * 6460 * // stop propagation to other listeners 6461 * event.stopPropagation(); 6462 * 6463 * // prevent event default 6464 * event.preventDefault(); 6465 * }); 6466 * 6467 * // listen for event with custom payload 6468 * eventBus.on('bar', function(event, payload) { 6469 * console.log(payload); 6470 * }); 6471 * 6472 * // listen for event returning value 6473 * eventBus.on('foobar', function(event) { 6474 * 6475 * // stop event propagation + prevent default 6476 * return false; 6477 * 6478 * // stop event propagation + return custom result 6479 * return { 6480 * complex: 'listening result' 6481 * }; 6482 * }); 6483 * 6484 * 6485 * // listen with custom priority (default=1000, higher is better) 6486 * eventBus.on('priorityfoo', 1500, function(event) { 6487 * console.log('invoked first!'); 6488 * }); 6489 * 6490 * 6491 * // listen for event and pass the context (`this`) 6492 * eventBus.on('foobar', function(event) { 6493 * this.foo(); 6494 * }, this); 6495 * ``` 6496 * 6497 * 6498 * ## Emitting Events 6499 * 6500 * Events can be emitted via the event bus using {@link EventBus#fire}. 6501 * 6502 * ```javascript 6503 * 6504 * // false indicates that the default action 6505 * // was prevented by listeners 6506 * if (eventBus.fire('foo') === false) { 6507 * console.log('default has been prevented!'); 6508 * }; 6509 * 6510 * 6511 * // custom args + return value listener 6512 * eventBus.on('sum', function(event, a, b) { 6513 * return a + b; 6514 * }); 6515 * 6516 * // you can pass custom arguments + retrieve result values. 6517 * var sum = eventBus.fire('sum', 1, 2); 6518 * console.log(sum); // 3 6519 * ``` 6520 */ 6521 function EventBus() { 6522 this._listeners = {}; 6523 6524 // cleanup on destroy on lowest priority to allow 6525 // message passing until the bitter end 6526 this.on('diagram.destroy', 1, this._destroy, this); 6527 } 6528 6529 6530 /** 6531 * Register an event listener for events with the given name. 6532 * 6533 * The callback will be invoked with `event, ...additionalArguments` 6534 * that have been passed to {@link EventBus#fire}. 6535 * 6536 * Returning false from a listener will prevent the events default action 6537 * (if any is specified). To stop an event from being processed further in 6538 * other listeners execute {@link Event#stopPropagation}. 6539 * 6540 * Returning anything but `undefined` from a listener will stop the listener propagation. 6541 * 6542 * @param {string|Array<string>} events 6543 * @param {number} [priority=1000] the priority in which this listener is called, larger is higher 6544 * @param {Function} callback 6545 * @param {Object} [that] Pass context (`this`) to the callback 6546 */ 6547 EventBus.prototype.on = function(events, priority, callback, that) { 6548 6549 events = isArray$2(events) ? events : [ events ]; 6550 6551 if (isFunction(priority)) { 6552 that = callback; 6553 callback = priority; 6554 priority = DEFAULT_PRIORITY$5; 6555 } 6556 6557 if (!isNumber(priority)) { 6558 throw new Error('priority must be a number'); 6559 } 6560 6561 var actualCallback = callback; 6562 6563 if (that) { 6564 actualCallback = bind$2(callback, that); 6565 6566 // make sure we remember and are able to remove 6567 // bound callbacks via {@link #off} using the original 6568 // callback 6569 actualCallback[FN_REF] = callback[FN_REF] || callback; 6570 } 6571 6572 var self = this; 6573 6574 events.forEach(function(e) { 6575 self._addListener(e, { 6576 priority: priority, 6577 callback: actualCallback, 6578 next: null 6579 }); 6580 }); 6581 }; 6582 6583 6584 /** 6585 * Register an event listener that is executed only once. 6586 * 6587 * @param {string} event the event name to register for 6588 * @param {number} [priority=1000] the priority in which this listener is called, larger is higher 6589 * @param {Function} callback the callback to execute 6590 * @param {Object} [that] Pass context (`this`) to the callback 6591 */ 6592 EventBus.prototype.once = function(event, priority, callback, that) { 6593 var self = this; 6594 6595 if (isFunction(priority)) { 6596 that = callback; 6597 callback = priority; 6598 priority = DEFAULT_PRIORITY$5; 6599 } 6600 6601 if (!isNumber(priority)) { 6602 throw new Error('priority must be a number'); 6603 } 6604 6605 function wrappedCallback() { 6606 wrappedCallback.__isTomb = true; 6607 6608 var result = callback.apply(that, arguments); 6609 6610 self.off(event, wrappedCallback); 6611 6612 return result; 6613 } 6614 6615 // make sure we remember and are able to remove 6616 // bound callbacks via {@link #off} using the original 6617 // callback 6618 wrappedCallback[FN_REF] = callback; 6619 6620 this.on(event, priority, wrappedCallback); 6621 }; 6622 6623 6624 /** 6625 * Removes event listeners by event and callback. 6626 * 6627 * If no callback is given, all listeners for a given event name are being removed. 6628 * 6629 * @param {string|Array<string>} events 6630 * @param {Function} [callback] 6631 */ 6632 EventBus.prototype.off = function(events, callback) { 6633 6634 events = isArray$2(events) ? events : [ events ]; 6635 6636 var self = this; 6637 6638 events.forEach(function(event) { 6639 self._removeListener(event, callback); 6640 }); 6641 6642 }; 6643 6644 6645 /** 6646 * Create an EventBus event. 6647 * 6648 * @param {Object} data 6649 * 6650 * @return {Object} event, recognized by the eventBus 6651 */ 6652 EventBus.prototype.createEvent = function(data) { 6653 var event = new InternalEvent(); 6654 6655 event.init(data); 6656 6657 return event; 6658 }; 6659 6660 6661 /** 6662 * Fires a named event. 6663 * 6664 * @example 6665 * 6666 * // fire event by name 6667 * events.fire('foo'); 6668 * 6669 * // fire event object with nested type 6670 * var event = { type: 'foo' }; 6671 * events.fire(event); 6672 * 6673 * // fire event with explicit type 6674 * var event = { x: 10, y: 20 }; 6675 * events.fire('element.moved', event); 6676 * 6677 * // pass additional arguments to the event 6678 * events.on('foo', function(event, bar) { 6679 * alert(bar); 6680 * }); 6681 * 6682 * events.fire({ type: 'foo' }, 'I am bar!'); 6683 * 6684 * @param {string} [name] the optional event name 6685 * @param {Object} [event] the event object 6686 * @param {...Object} additional arguments to be passed to the callback functions 6687 * 6688 * @return {boolean} the events return value, if specified or false if the 6689 * default action was prevented by listeners 6690 */ 6691 EventBus.prototype.fire = function(type, data) { 6692 var event, 6693 firstListener, 6694 returnValue, 6695 args; 6696 6697 args = slice.call(arguments); 6698 6699 if (typeof type === 'object') { 6700 data = type; 6701 type = data.type; 6702 } 6703 6704 if (!type) { 6705 throw new Error('no event type specified'); 6706 } 6707 6708 firstListener = this._listeners[type]; 6709 6710 if (!firstListener) { 6711 return; 6712 } 6713 6714 // we make sure we fire instances of our home made 6715 // events here. We wrap them only once, though 6716 if (data instanceof InternalEvent) { 6717 6718 // we are fine, we alread have an event 6719 event = data; 6720 } else { 6721 event = this.createEvent(data); 6722 } 6723 6724 // ensure we pass the event as the first parameter 6725 args[0] = event; 6726 6727 // original event type (in case we delegate) 6728 var originalType = event.type; 6729 6730 // update event type before delegation 6731 if (type !== originalType) { 6732 event.type = type; 6733 } 6734 6735 try { 6736 returnValue = this._invokeListeners(event, args, firstListener); 6737 } finally { 6738 6739 // reset event type after delegation 6740 if (type !== originalType) { 6741 event.type = originalType; 6742 } 6743 } 6744 6745 // set the return value to false if the event default 6746 // got prevented and no other return value exists 6747 if (returnValue === undefined && event.defaultPrevented) { 6748 returnValue = false; 6749 } 6750 6751 return returnValue; 6752 }; 6753 6754 6755 EventBus.prototype.handleError = function(error) { 6756 return this.fire('error', { error: error }) === false; 6757 }; 6758 6759 6760 EventBus.prototype._destroy = function() { 6761 this._listeners = {}; 6762 }; 6763 6764 EventBus.prototype._invokeListeners = function(event, args, listener) { 6765 6766 var returnValue; 6767 6768 while (listener) { 6769 6770 // handle stopped propagation 6771 if (event.cancelBubble) { 6772 break; 6773 } 6774 6775 returnValue = this._invokeListener(event, args, listener); 6776 6777 listener = listener.next; 6778 } 6779 6780 return returnValue; 6781 }; 6782 6783 EventBus.prototype._invokeListener = function(event, args, listener) { 6784 6785 var returnValue; 6786 6787 if (listener.callback.__isTomb) { 6788 return returnValue; 6789 } 6790 6791 try { 6792 6793 // returning false prevents the default action 6794 returnValue = invokeFunction(listener.callback, args); 6795 6796 // stop propagation on return value 6797 if (returnValue !== undefined) { 6798 event.returnValue = returnValue; 6799 event.stopPropagation(); 6800 } 6801 6802 // prevent default on return false 6803 if (returnValue === false) { 6804 event.preventDefault(); 6805 } 6806 } catch (error) { 6807 if (!this.handleError(error)) { 6808 console.error('unhandled error in event listener', error); 6809 6810 throw error; 6811 } 6812 } 6813 6814 return returnValue; 6815 }; 6816 6817 /* 6818 * Add new listener with a certain priority to the list 6819 * of listeners (for the given event). 6820 * 6821 * The semantics of listener registration / listener execution are 6822 * first register, first serve: New listeners will always be inserted 6823 * after existing listeners with the same priority. 6824 * 6825 * Example: Inserting two listeners with priority 1000 and 1300 6826 * 6827 * * before: [ 1500, 1500, 1000, 1000 ] 6828 * * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ] 6829 * 6830 * @param {string} event 6831 * @param {Object} listener { priority, callback } 6832 */ 6833 EventBus.prototype._addListener = function(event, newListener) { 6834 6835 var listener = this._getListeners(event), 6836 previousListener; 6837 6838 // no prior listeners 6839 if (!listener) { 6840 this._setListeners(event, newListener); 6841 6842 return; 6843 } 6844 6845 // ensure we order listeners by priority from 6846 // 0 (high) to n > 0 (low) 6847 while (listener) { 6848 6849 if (listener.priority < newListener.priority) { 6850 6851 newListener.next = listener; 6852 6853 if (previousListener) { 6854 previousListener.next = newListener; 6855 } else { 6856 this._setListeners(event, newListener); 6857 } 6858 6859 return; 6860 } 6861 6862 previousListener = listener; 6863 listener = listener.next; 6864 } 6865 6866 // add new listener to back 6867 previousListener.next = newListener; 6868 }; 6869 6870 6871 EventBus.prototype._getListeners = function(name) { 6872 return this._listeners[name]; 6873 }; 6874 6875 EventBus.prototype._setListeners = function(name, listener) { 6876 this._listeners[name] = listener; 6877 }; 6878 6879 EventBus.prototype._removeListener = function(event, callback) { 6880 6881 var listener = this._getListeners(event), 6882 nextListener, 6883 previousListener, 6884 listenerCallback; 6885 6886 if (!callback) { 6887 6888 // clear listeners 6889 this._setListeners(event, null); 6890 6891 return; 6892 } 6893 6894 while (listener) { 6895 6896 nextListener = listener.next; 6897 6898 listenerCallback = listener.callback; 6899 6900 if (listenerCallback === callback || listenerCallback[FN_REF] === callback) { 6901 if (previousListener) { 6902 previousListener.next = nextListener; 6903 } else { 6904 6905 // new first listener 6906 this._setListeners(event, nextListener); 6907 } 6908 } 6909 6910 previousListener = listener; 6911 listener = nextListener; 6912 } 6913 }; 6914 6915 /** 6916 * A event that is emitted via the event bus. 6917 */ 6918 function InternalEvent() { } 6919 6920 InternalEvent.prototype.stopPropagation = function() { 6921 this.cancelBubble = true; 6922 }; 6923 6924 InternalEvent.prototype.preventDefault = function() { 6925 this.defaultPrevented = true; 6926 }; 6927 6928 InternalEvent.prototype.init = function(data) { 6929 assign(this, data || {}); 6930 }; 6931 6932 6933 /** 6934 * Invoke function. Be fast... 6935 * 6936 * @param {Function} fn 6937 * @param {Array<Object>} args 6938 * 6939 * @return {Any} 6940 */ 6941 function invokeFunction(fn, args) { 6942 return fn.apply(null, args); 6943 } 6944 6945 /** 6946 * SVGs for elements are generated by the {@link GraphicsFactory}. 6947 * 6948 * This utility gives quick access to the important semantic 6949 * parts of an element. 6950 */ 6951 6952 /** 6953 * Returns the visual part of a diagram element 6954 * 6955 * @param {Snap<SVGElement>} gfx 6956 * 6957 * @return {Snap<SVGElement>} 6958 */ 6959 function getVisual(gfx) { 6960 return gfx.childNodes[0]; 6961 } 6962 6963 /** 6964 * Returns the children for a given diagram element. 6965 * 6966 * @param {Snap<SVGElement>} gfx 6967 * @return {Snap<SVGElement>} 6968 */ 6969 function getChildren$1(gfx) { 6970 return gfx.parentNode.childNodes[1]; 6971 } 6972 6973 /** 6974 * @param {<SVGElement>} element 6975 * @param {number} x 6976 * @param {number} y 6977 * @param {number} angle 6978 * @param {number} amount 6979 */ 6980 function transform(gfx, x, y, angle, amount) { 6981 var translate = createTransform(); 6982 translate.setTranslate(x, y); 6983 6984 var rotate = createTransform(); 6985 rotate.setRotate(angle || 0, 0, 0); 6986 6987 var scale = createTransform(); 6988 scale.setScale(amount || 1, amount || 1); 6989 6990 transform$1(gfx, [ translate, rotate, scale ]); 6991 } 6992 6993 6994 /** 6995 * @param {SVGElement} element 6996 * @param {number} x 6997 * @param {number} y 6998 */ 6999 function translate$2(gfx, x, y) { 7000 var translate = createTransform(); 7001 translate.setTranslate(x, y); 7002 7003 transform$1(gfx, translate); 7004 } 7005 7006 7007 /** 7008 * @param {SVGElement} element 7009 * @param {number} angle 7010 */ 7011 function rotate(gfx, angle) { 7012 var rotate = createTransform(); 7013 rotate.setRotate(angle, 0, 0); 7014 7015 transform$1(gfx, rotate); 7016 } 7017 7018 /** 7019 * A factory that creates graphical elements 7020 * 7021 * @param {EventBus} eventBus 7022 * @param {ElementRegistry} elementRegistry 7023 */ 7024 function GraphicsFactory(eventBus, elementRegistry) { 7025 this._eventBus = eventBus; 7026 this._elementRegistry = elementRegistry; 7027 } 7028 7029 GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ]; 7030 7031 7032 GraphicsFactory.prototype._getChildrenContainer = function(element) { 7033 7034 var gfx = this._elementRegistry.getGraphics(element); 7035 7036 var childrenGfx; 7037 7038 // root element 7039 if (!element.parent) { 7040 childrenGfx = gfx; 7041 } else { 7042 childrenGfx = getChildren$1(gfx); 7043 if (!childrenGfx) { 7044 childrenGfx = create$1('g'); 7045 classes(childrenGfx).add('djs-children'); 7046 7047 append(gfx.parentNode, childrenGfx); 7048 } 7049 } 7050 7051 return childrenGfx; 7052 }; 7053 7054 /** 7055 * Clears the graphical representation of the element and returns the 7056 * cleared visual (the <g class="djs-visual" /> element). 7057 */ 7058 GraphicsFactory.prototype._clear = function(gfx) { 7059 var visual = getVisual(gfx); 7060 7061 clear$1(visual); 7062 7063 return visual; 7064 }; 7065 7066 /** 7067 * Creates a gfx container for shapes and connections 7068 * 7069 * The layout is as follows: 7070 * 7071 * <g class="djs-group"> 7072 * 7073 * <!-- the gfx --> 7074 * <g class="djs-element djs-(shape|connection|frame)"> 7075 * <g class="djs-visual"> 7076 * <!-- the renderer draws in here --> 7077 * </g> 7078 * 7079 * <!-- extensions (overlays, click box, ...) goes here 7080 * </g> 7081 * 7082 * <!-- the gfx child nodes --> 7083 * <g class="djs-children"></g> 7084 * </g> 7085 * 7086 * @param {string} type the type of the element, i.e. shape | connection 7087 * @param {SVGElement} [childrenGfx] 7088 * @param {number} [parentIndex] position to create container in parent 7089 * @param {boolean} [isFrame] is frame element 7090 * 7091 * @return {SVGElement} 7092 */ 7093 GraphicsFactory.prototype._createContainer = function( 7094 type, childrenGfx, parentIndex, isFrame 7095 ) { 7096 var outerGfx = create$1('g'); 7097 classes(outerGfx).add('djs-group'); 7098 7099 // insert node at position 7100 if (typeof parentIndex !== 'undefined') { 7101 prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]); 7102 } else { 7103 append(childrenGfx, outerGfx); 7104 } 7105 7106 var gfx = create$1('g'); 7107 classes(gfx).add('djs-element'); 7108 classes(gfx).add('djs-' + type); 7109 7110 if (isFrame) { 7111 classes(gfx).add('djs-frame'); 7112 } 7113 7114 append(outerGfx, gfx); 7115 7116 // create visual 7117 var visual = create$1('g'); 7118 classes(visual).add('djs-visual'); 7119 7120 append(gfx, visual); 7121 7122 return gfx; 7123 }; 7124 7125 GraphicsFactory.prototype.create = function(type, element, parentIndex) { 7126 var childrenGfx = this._getChildrenContainer(element.parent); 7127 return this._createContainer(type, childrenGfx, parentIndex, isFrameElement$1(element)); 7128 }; 7129 7130 GraphicsFactory.prototype.updateContainments = function(elements) { 7131 7132 var self = this, 7133 elementRegistry = this._elementRegistry, 7134 parents; 7135 7136 parents = reduce(elements, function(map, e) { 7137 7138 if (e.parent) { 7139 map[e.parent.id] = e.parent; 7140 } 7141 7142 return map; 7143 }, {}); 7144 7145 // update all parents of changed and reorganized their children 7146 // in the correct order (as indicated in our model) 7147 forEach(parents, function(parent) { 7148 7149 var children = parent.children; 7150 7151 if (!children) { 7152 return; 7153 } 7154 7155 var childrenGfx = self._getChildrenContainer(parent); 7156 7157 forEach(children.slice().reverse(), function(child) { 7158 var childGfx = elementRegistry.getGraphics(child); 7159 7160 prependTo(childGfx.parentNode, childrenGfx); 7161 }); 7162 }); 7163 }; 7164 7165 GraphicsFactory.prototype.drawShape = function(visual, element) { 7166 var eventBus = this._eventBus; 7167 7168 return eventBus.fire('render.shape', { gfx: visual, element: element }); 7169 }; 7170 7171 GraphicsFactory.prototype.getShapePath = function(element) { 7172 var eventBus = this._eventBus; 7173 7174 return eventBus.fire('render.getShapePath', element); 7175 }; 7176 7177 GraphicsFactory.prototype.drawConnection = function(visual, element) { 7178 var eventBus = this._eventBus; 7179 7180 return eventBus.fire('render.connection', { gfx: visual, element: element }); 7181 }; 7182 7183 GraphicsFactory.prototype.getConnectionPath = function(waypoints) { 7184 var eventBus = this._eventBus; 7185 7186 return eventBus.fire('render.getConnectionPath', waypoints); 7187 }; 7188 7189 GraphicsFactory.prototype.update = function(type, element, gfx) { 7190 7191 // do NOT update root element 7192 if (!element.parent) { 7193 return; 7194 } 7195 7196 var visual = this._clear(gfx); 7197 7198 // redraw 7199 if (type === 'shape') { 7200 this.drawShape(visual, element); 7201 7202 // update positioning 7203 translate$2(gfx, element.x, element.y); 7204 } else 7205 if (type === 'connection') { 7206 this.drawConnection(visual, element); 7207 } else { 7208 throw new Error('unknown type: ' + type); 7209 } 7210 7211 if (element.hidden) { 7212 attr(gfx, 'display', 'none'); 7213 } else { 7214 attr(gfx, 'display', 'block'); 7215 } 7216 }; 7217 7218 GraphicsFactory.prototype.remove = function(element) { 7219 var gfx = this._elementRegistry.getGraphics(element); 7220 7221 // remove 7222 remove$1(gfx.parentNode); 7223 }; 7224 7225 7226 // helpers ////////// 7227 7228 function prependTo(newNode, parentNode, siblingNode) { 7229 var node = siblingNode || parentNode.firstChild; 7230 7231 // do not prepend node to itself to prevent IE from crashing 7232 // https://github.com/bpmn-io/bpmn-js/issues/746 7233 if (newNode === node) { 7234 return; 7235 } 7236 7237 parentNode.insertBefore(newNode, node); 7238 } 7239 7240 var CoreModule$1 = { 7241 __depends__: [ DrawModule$1 ], 7242 __init__: [ 'canvas' ], 7243 canvas: [ 'type', Canvas ], 7244 elementRegistry: [ 'type', ElementRegistry ], 7245 elementFactory: [ 'type', ElementFactory$1 ], 7246 eventBus: [ 'type', EventBus ], 7247 graphicsFactory: [ 'type', GraphicsFactory ] 7248 }; 7249 7250 /** 7251 * Bootstrap an injector from a list of modules, instantiating a number of default components 7252 * 7253 * @ignore 7254 * @param {Array<didi.Module>} bootstrapModules 7255 * 7256 * @return {didi.Injector} a injector to use to access the components 7257 */ 7258 function bootstrap(bootstrapModules) { 7259 7260 var modules = [], 7261 components = []; 7262 7263 function hasModule(m) { 7264 return modules.indexOf(m) >= 0; 7265 } 7266 7267 function addModule(m) { 7268 modules.push(m); 7269 } 7270 7271 function visit(m) { 7272 if (hasModule(m)) { 7273 return; 7274 } 7275 7276 (m.__depends__ || []).forEach(visit); 7277 7278 if (hasModule(m)) { 7279 return; 7280 } 7281 7282 addModule(m); 7283 7284 (m.__init__ || []).forEach(function(c) { 7285 components.push(c); 7286 }); 7287 } 7288 7289 bootstrapModules.forEach(visit); 7290 7291 var injector = new Injector(modules); 7292 7293 components.forEach(function(c) { 7294 7295 try { 7296 7297 // eagerly resolve component (fn or string) 7298 injector[typeof c === 'string' ? 'get' : 'invoke'](c); 7299 } catch (e) { 7300 console.error('Failed to instantiate component'); 7301 console.error(e.stack); 7302 7303 throw e; 7304 } 7305 }); 7306 7307 return injector; 7308 } 7309 7310 /** 7311 * Creates an injector from passed options. 7312 * 7313 * @ignore 7314 * @param {Object} options 7315 * @return {didi.Injector} 7316 */ 7317 function createInjector(options) { 7318 7319 options = options || {}; 7320 7321 var configModule = { 7322 'config': ['value', options] 7323 }; 7324 7325 var modules = [ configModule, CoreModule$1 ].concat(options.modules || []); 7326 7327 return bootstrap(modules); 7328 } 7329 7330 7331 /** 7332 * The main diagram-js entry point that bootstraps the diagram with the given 7333 * configuration. 7334 * 7335 * To register extensions with the diagram, pass them as Array<didi.Module> to the constructor. 7336 * 7337 * @class djs.Diagram 7338 * @memberOf djs 7339 * @constructor 7340 * 7341 * @example 7342 * 7343 * <caption>Creating a plug-in that logs whenever a shape is added to the canvas.</caption> 7344 * 7345 * // plug-in implemenentation 7346 * function MyLoggingPlugin(eventBus) { 7347 * eventBus.on('shape.added', function(event) { 7348 * console.log('shape ', event.shape, ' was added to the diagram'); 7349 * }); 7350 * } 7351 * 7352 * // export as module 7353 * export default { 7354 * __init__: [ 'myLoggingPlugin' ], 7355 * myLoggingPlugin: [ 'type', MyLoggingPlugin ] 7356 * }; 7357 * 7358 * 7359 * // instantiate the diagram with the new plug-in 7360 * 7361 * import MyLoggingModule from 'path-to-my-logging-plugin'; 7362 * 7363 * var diagram = new Diagram({ 7364 * modules: [ 7365 * MyLoggingModule 7366 * ] 7367 * }); 7368 * 7369 * diagram.invoke([ 'canvas', function(canvas) { 7370 * // add shape to drawing canvas 7371 * canvas.addShape({ x: 10, y: 10 }); 7372 * }); 7373 * 7374 * // 'shape ... was added to the diagram' logged to console 7375 * 7376 * @param {Object} options 7377 * @param {Array<didi.Module>} [options.modules] external modules to instantiate with the diagram 7378 * @param {didi.Injector} [injector] an (optional) injector to bootstrap the diagram with 7379 */ 7380 function Diagram(options, injector) { 7381 7382 // create injector unless explicitly specified 7383 this.injector = injector = injector || createInjector(options); 7384 7385 // API 7386 7387 /** 7388 * Resolves a diagram service 7389 * 7390 * @method Diagram#get 7391 * 7392 * @param {string} name the name of the diagram service to be retrieved 7393 * @param {boolean} [strict=true] if false, resolve missing services to null 7394 */ 7395 this.get = injector.get; 7396 7397 /** 7398 * Executes a function into which diagram services are injected 7399 * 7400 * @method Diagram#invoke 7401 * 7402 * @param {Function|Object[]} fn the function to resolve 7403 * @param {Object} locals a number of locals to use to resolve certain dependencies 7404 */ 7405 this.invoke = injector.invoke; 7406 7407 // init 7408 7409 // indicate via event 7410 7411 7412 /** 7413 * An event indicating that all plug-ins are loaded. 7414 * 7415 * Use this event to fire other events to interested plug-ins 7416 * 7417 * @memberOf Diagram 7418 * 7419 * @event diagram.init 7420 * 7421 * @example 7422 * 7423 * eventBus.on('diagram.init', function() { 7424 * eventBus.fire('my-custom-event', { foo: 'BAR' }); 7425 * }); 7426 * 7427 * @type {Object} 7428 */ 7429 this.get('eventBus').fire('diagram.init'); 7430 } 7431 7432 7433 /** 7434 * Destroys the diagram 7435 * 7436 * @method Diagram#destroy 7437 */ 7438 Diagram.prototype.destroy = function() { 7439 this.get('eventBus').fire('diagram.destroy'); 7440 }; 7441 7442 /** 7443 * Clear the diagram, removing all contents. 7444 */ 7445 Diagram.prototype.clear = function() { 7446 this.get('eventBus').fire('diagram.clear'); 7447 }; 7448 7449 /** 7450 * Moddle base element. 7451 */ 7452 function Base() { } 7453 7454 Base.prototype.get = function(name) { 7455 return this.$model.properties.get(this, name); 7456 }; 7457 7458 Base.prototype.set = function(name, value) { 7459 this.$model.properties.set(this, name, value); 7460 }; 7461 7462 /** 7463 * A model element factory. 7464 * 7465 * @param {Moddle} model 7466 * @param {Properties} properties 7467 */ 7468 function Factory(model, properties) { 7469 this.model = model; 7470 this.properties = properties; 7471 } 7472 7473 7474 Factory.prototype.createType = function(descriptor) { 7475 7476 var model = this.model; 7477 7478 var props = this.properties, 7479 prototype = Object.create(Base.prototype); 7480 7481 // initialize default values 7482 forEach(descriptor.properties, function(p) { 7483 if (!p.isMany && p.default !== undefined) { 7484 prototype[p.name] = p.default; 7485 } 7486 }); 7487 7488 props.defineModel(prototype, model); 7489 props.defineDescriptor(prototype, descriptor); 7490 7491 var name = descriptor.ns.name; 7492 7493 /** 7494 * The new type constructor 7495 */ 7496 function ModdleElement(attrs) { 7497 props.define(this, '$type', { value: name, enumerable: true }); 7498 props.define(this, '$attrs', { value: {} }); 7499 props.define(this, '$parent', { writable: true }); 7500 7501 forEach(attrs, bind$2(function(val, key) { 7502 this.set(key, val); 7503 }, this)); 7504 } 7505 7506 ModdleElement.prototype = prototype; 7507 7508 ModdleElement.hasType = prototype.$instanceOf = this.model.hasType; 7509 7510 // static links 7511 props.defineModel(ModdleElement, model); 7512 props.defineDescriptor(ModdleElement, descriptor); 7513 7514 return ModdleElement; 7515 }; 7516 7517 /** 7518 * Built-in moddle types 7519 */ 7520 var BUILTINS = { 7521 String: true, 7522 Boolean: true, 7523 Integer: true, 7524 Real: true, 7525 Element: true 7526 }; 7527 7528 /** 7529 * Converters for built in types from string representations 7530 */ 7531 var TYPE_CONVERTERS = { 7532 String: function(s) { return s; }, 7533 Boolean: function(s) { return s === 'true'; }, 7534 Integer: function(s) { return parseInt(s, 10); }, 7535 Real: function(s) { return parseFloat(s); } 7536 }; 7537 7538 /** 7539 * Convert a type to its real representation 7540 */ 7541 function coerceType(type, value) { 7542 7543 var converter = TYPE_CONVERTERS[type]; 7544 7545 if (converter) { 7546 return converter(value); 7547 } else { 7548 return value; 7549 } 7550 } 7551 7552 /** 7553 * Return whether the given type is built-in 7554 */ 7555 function isBuiltIn(type) { 7556 return !!BUILTINS[type]; 7557 } 7558 7559 /** 7560 * Return whether the given type is simple 7561 */ 7562 function isSimple(type) { 7563 return !!TYPE_CONVERTERS[type]; 7564 } 7565 7566 /** 7567 * Parses a namespaced attribute name of the form (ns:)localName to an object, 7568 * given a default prefix to assume in case no explicit namespace is given. 7569 * 7570 * @param {String} name 7571 * @param {String} [defaultPrefix] the default prefix to take, if none is present. 7572 * 7573 * @return {Object} the parsed name 7574 */ 7575 function parseName(name, defaultPrefix) { 7576 var parts = name.split(/:/), 7577 localName, prefix; 7578 7579 // no prefix (i.e. only local name) 7580 if (parts.length === 1) { 7581 localName = name; 7582 prefix = defaultPrefix; 7583 } else 7584 // prefix + local name 7585 if (parts.length === 2) { 7586 localName = parts[1]; 7587 prefix = parts[0]; 7588 } else { 7589 throw new Error('expected <prefix:localName> or <localName>, got ' + name); 7590 } 7591 7592 name = (prefix ? prefix + ':' : '') + localName; 7593 7594 return { 7595 name: name, 7596 prefix: prefix, 7597 localName: localName 7598 }; 7599 } 7600 7601 /** 7602 * A utility to build element descriptors. 7603 */ 7604 function DescriptorBuilder(nameNs) { 7605 this.ns = nameNs; 7606 this.name = nameNs.name; 7607 this.allTypes = []; 7608 this.allTypesByName = {}; 7609 this.properties = []; 7610 this.propertiesByName = {}; 7611 } 7612 7613 7614 DescriptorBuilder.prototype.build = function() { 7615 return pick(this, [ 7616 'ns', 7617 'name', 7618 'allTypes', 7619 'allTypesByName', 7620 'properties', 7621 'propertiesByName', 7622 'bodyProperty', 7623 'idProperty' 7624 ]); 7625 }; 7626 7627 /** 7628 * Add property at given index. 7629 * 7630 * @param {Object} p 7631 * @param {Number} [idx] 7632 * @param {Boolean} [validate=true] 7633 */ 7634 DescriptorBuilder.prototype.addProperty = function(p, idx, validate) { 7635 7636 if (typeof idx === 'boolean') { 7637 validate = idx; 7638 idx = undefined; 7639 } 7640 7641 this.addNamedProperty(p, validate !== false); 7642 7643 var properties = this.properties; 7644 7645 if (idx !== undefined) { 7646 properties.splice(idx, 0, p); 7647 } else { 7648 properties.push(p); 7649 } 7650 }; 7651 7652 7653 DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) { 7654 var oldNameNs = oldProperty.ns; 7655 7656 var props = this.properties, 7657 propertiesByName = this.propertiesByName, 7658 rename = oldProperty.name !== newProperty.name; 7659 7660 if (oldProperty.isId) { 7661 if (!newProperty.isId) { 7662 throw new Error( 7663 'property <' + newProperty.ns.name + '> must be id property ' + 7664 'to refine <' + oldProperty.ns.name + '>'); 7665 } 7666 7667 this.setIdProperty(newProperty, false); 7668 } 7669 7670 if (oldProperty.isBody) { 7671 7672 if (!newProperty.isBody) { 7673 throw new Error( 7674 'property <' + newProperty.ns.name + '> must be body property ' + 7675 'to refine <' + oldProperty.ns.name + '>'); 7676 } 7677 7678 // TODO: Check compatibility 7679 this.setBodyProperty(newProperty, false); 7680 } 7681 7682 // validate existence and get location of old property 7683 var idx = props.indexOf(oldProperty); 7684 if (idx === -1) { 7685 throw new Error('property <' + oldNameNs.name + '> not found in property list'); 7686 } 7687 7688 // remove old property 7689 props.splice(idx, 1); 7690 7691 // replacing the named property is intentional 7692 // 7693 // * validate only if this is a "rename" operation 7694 // * add at specific index unless we "replace" 7695 // 7696 this.addProperty(newProperty, replace ? undefined : idx, rename); 7697 7698 // make new property available under old name 7699 propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty; 7700 }; 7701 7702 7703 DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) { 7704 7705 var nsPrefix = p.ns.prefix; 7706 var parts = targetPropertyName.split('#'); 7707 7708 var name = parseName(parts[0], nsPrefix); 7709 var attrName = parseName(parts[1], name.prefix).name; 7710 7711 var redefinedProperty = this.propertiesByName[attrName]; 7712 if (!redefinedProperty) { 7713 throw new Error('refined property <' + attrName + '> not found'); 7714 } else { 7715 this.replaceProperty(redefinedProperty, p, replace); 7716 } 7717 7718 delete p.redefines; 7719 }; 7720 7721 DescriptorBuilder.prototype.addNamedProperty = function(p, validate) { 7722 var ns = p.ns, 7723 propsByName = this.propertiesByName; 7724 7725 if (validate) { 7726 this.assertNotDefined(p, ns.name); 7727 this.assertNotDefined(p, ns.localName); 7728 } 7729 7730 propsByName[ns.name] = propsByName[ns.localName] = p; 7731 }; 7732 7733 DescriptorBuilder.prototype.removeNamedProperty = function(p) { 7734 var ns = p.ns, 7735 propsByName = this.propertiesByName; 7736 7737 delete propsByName[ns.name]; 7738 delete propsByName[ns.localName]; 7739 }; 7740 7741 DescriptorBuilder.prototype.setBodyProperty = function(p, validate) { 7742 7743 if (validate && this.bodyProperty) { 7744 throw new Error( 7745 'body property defined multiple times ' + 7746 '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)'); 7747 } 7748 7749 this.bodyProperty = p; 7750 }; 7751 7752 DescriptorBuilder.prototype.setIdProperty = function(p, validate) { 7753 7754 if (validate && this.idProperty) { 7755 throw new Error( 7756 'id property defined multiple times ' + 7757 '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)'); 7758 } 7759 7760 this.idProperty = p; 7761 }; 7762 7763 DescriptorBuilder.prototype.assertNotDefined = function(p, name) { 7764 var propertyName = p.name, 7765 definedProperty = this.propertiesByName[propertyName]; 7766 7767 if (definedProperty) { 7768 throw new Error( 7769 'property <' + propertyName + '> already defined; ' + 7770 'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' + 7771 '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines'); 7772 } 7773 }; 7774 7775 DescriptorBuilder.prototype.hasProperty = function(name) { 7776 return this.propertiesByName[name]; 7777 }; 7778 7779 DescriptorBuilder.prototype.addTrait = function(t, inherited) { 7780 7781 var typesByName = this.allTypesByName, 7782 types = this.allTypes; 7783 7784 var typeName = t.name; 7785 7786 if (typeName in typesByName) { 7787 return; 7788 } 7789 7790 forEach(t.properties, bind$2(function(p) { 7791 7792 // clone property to allow extensions 7793 p = assign({}, p, { 7794 name: p.ns.localName, 7795 inherited: inherited 7796 }); 7797 7798 Object.defineProperty(p, 'definedBy', { 7799 value: t 7800 }); 7801 7802 var replaces = p.replaces, 7803 redefines = p.redefines; 7804 7805 // add replace/redefine support 7806 if (replaces || redefines) { 7807 this.redefineProperty(p, replaces || redefines, replaces); 7808 } else { 7809 if (p.isBody) { 7810 this.setBodyProperty(p); 7811 } 7812 if (p.isId) { 7813 this.setIdProperty(p); 7814 } 7815 this.addProperty(p); 7816 } 7817 }, this)); 7818 7819 types.push(t); 7820 typesByName[typeName] = t; 7821 }; 7822 7823 /** 7824 * A registry of Moddle packages. 7825 * 7826 * @param {Array<Package>} packages 7827 * @param {Properties} properties 7828 */ 7829 function Registry(packages, properties) { 7830 this.packageMap = {}; 7831 this.typeMap = {}; 7832 7833 this.packages = []; 7834 7835 this.properties = properties; 7836 7837 forEach(packages, bind$2(this.registerPackage, this)); 7838 } 7839 7840 7841 Registry.prototype.getPackage = function(uriOrPrefix) { 7842 return this.packageMap[uriOrPrefix]; 7843 }; 7844 7845 Registry.prototype.getPackages = function() { 7846 return this.packages; 7847 }; 7848 7849 7850 Registry.prototype.registerPackage = function(pkg) { 7851 7852 // copy package 7853 pkg = assign({}, pkg); 7854 7855 var pkgMap = this.packageMap; 7856 7857 ensureAvailable(pkgMap, pkg, 'prefix'); 7858 ensureAvailable(pkgMap, pkg, 'uri'); 7859 7860 // register types 7861 forEach(pkg.types, bind$2(function(descriptor) { 7862 this.registerType(descriptor, pkg); 7863 }, this)); 7864 7865 pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg; 7866 this.packages.push(pkg); 7867 }; 7868 7869 7870 /** 7871 * Register a type from a specific package with us 7872 */ 7873 Registry.prototype.registerType = function(type, pkg) { 7874 7875 type = assign({}, type, { 7876 superClass: (type.superClass || []).slice(), 7877 extends: (type.extends || []).slice(), 7878 properties: (type.properties || []).slice(), 7879 meta: assign((type.meta || {})) 7880 }); 7881 7882 var ns = parseName(type.name, pkg.prefix), 7883 name = ns.name, 7884 propertiesByName = {}; 7885 7886 // parse properties 7887 forEach(type.properties, bind$2(function(p) { 7888 7889 // namespace property names 7890 var propertyNs = parseName(p.name, ns.prefix), 7891 propertyName = propertyNs.name; 7892 7893 // namespace property types 7894 if (!isBuiltIn(p.type)) { 7895 p.type = parseName(p.type, propertyNs.prefix).name; 7896 } 7897 7898 assign(p, { 7899 ns: propertyNs, 7900 name: propertyName 7901 }); 7902 7903 propertiesByName[propertyName] = p; 7904 }, this)); 7905 7906 // update ns + name 7907 assign(type, { 7908 ns: ns, 7909 name: name, 7910 propertiesByName: propertiesByName 7911 }); 7912 7913 forEach(type.extends, bind$2(function(extendsName) { 7914 var extended = this.typeMap[extendsName]; 7915 7916 extended.traits = extended.traits || []; 7917 extended.traits.push(name); 7918 }, this)); 7919 7920 // link to package 7921 this.definePackage(type, pkg); 7922 7923 // register 7924 this.typeMap[name] = type; 7925 }; 7926 7927 7928 /** 7929 * Traverse the type hierarchy from bottom to top, 7930 * calling iterator with (type, inherited) for all elements in 7931 * the inheritance chain. 7932 * 7933 * @param {Object} nsName 7934 * @param {Function} iterator 7935 * @param {Boolean} [trait=false] 7936 */ 7937 Registry.prototype.mapTypes = function(nsName, iterator, trait) { 7938 7939 var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name]; 7940 7941 var self = this; 7942 7943 /** 7944 * Traverse the selected trait. 7945 * 7946 * @param {String} cls 7947 */ 7948 function traverseTrait(cls) { 7949 return traverseSuper(cls, true); 7950 } 7951 7952 /** 7953 * Traverse the selected super type or trait 7954 * 7955 * @param {String} cls 7956 * @param {Boolean} [trait=false] 7957 */ 7958 function traverseSuper(cls, trait) { 7959 var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix); 7960 self.mapTypes(parentNs, iterator, trait); 7961 } 7962 7963 if (!type) { 7964 throw new Error('unknown type <' + nsName.name + '>'); 7965 } 7966 7967 forEach(type.superClass, trait ? traverseTrait : traverseSuper); 7968 7969 // call iterator with (type, inherited=!trait) 7970 iterator(type, !trait); 7971 7972 forEach(type.traits, traverseTrait); 7973 }; 7974 7975 7976 /** 7977 * Returns the effective descriptor for a type. 7978 * 7979 * @param {String} type the namespaced name (ns:localName) of the type 7980 * 7981 * @return {Descriptor} the resulting effective descriptor 7982 */ 7983 Registry.prototype.getEffectiveDescriptor = function(name) { 7984 7985 var nsName = parseName(name); 7986 7987 var builder = new DescriptorBuilder(nsName); 7988 7989 this.mapTypes(nsName, function(type, inherited) { 7990 builder.addTrait(type, inherited); 7991 }); 7992 7993 var descriptor = builder.build(); 7994 7995 // define package link 7996 this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg); 7997 7998 return descriptor; 7999 }; 8000 8001 8002 Registry.prototype.definePackage = function(target, pkg) { 8003 this.properties.define(target, '$pkg', { value: pkg }); 8004 }; 8005 8006 8007 8008 ///////// helpers //////////////////////////// 8009 8010 function ensureAvailable(packageMap, pkg, identifierKey) { 8011 8012 var value = pkg[identifierKey]; 8013 8014 if (value in packageMap) { 8015 throw new Error('package with ' + identifierKey + ' <' + value + '> already defined'); 8016 } 8017 } 8018 8019 /** 8020 * A utility that gets and sets properties of model elements. 8021 * 8022 * @param {Model} model 8023 */ 8024 function Properties(model) { 8025 this.model = model; 8026 } 8027 8028 8029 /** 8030 * Sets a named property on the target element. 8031 * If the value is undefined, the property gets deleted. 8032 * 8033 * @param {Object} target 8034 * @param {String} name 8035 * @param {Object} value 8036 */ 8037 Properties.prototype.set = function(target, name, value) { 8038 8039 var property = this.model.getPropertyDescriptor(target, name); 8040 8041 var propertyName = property && property.name; 8042 8043 if (isUndefined(value)) { 8044 // unset the property, if the specified value is undefined; 8045 // delete from $attrs (for extensions) or the target itself 8046 if (property) { 8047 delete target[propertyName]; 8048 } else { 8049 delete target.$attrs[name]; 8050 } 8051 } else { 8052 // set the property, defining well defined properties on the fly 8053 // or simply updating them in target.$attrs (for extensions) 8054 if (property) { 8055 if (propertyName in target) { 8056 target[propertyName] = value; 8057 } else { 8058 defineProperty(target, property, value); 8059 } 8060 } else { 8061 target.$attrs[name] = value; 8062 } 8063 } 8064 }; 8065 8066 /** 8067 * Returns the named property of the given element 8068 * 8069 * @param {Object} target 8070 * @param {String} name 8071 * 8072 * @return {Object} 8073 */ 8074 Properties.prototype.get = function(target, name) { 8075 8076 var property = this.model.getPropertyDescriptor(target, name); 8077 8078 if (!property) { 8079 return target.$attrs[name]; 8080 } 8081 8082 var propertyName = property.name; 8083 8084 // check if access to collection property and lazily initialize it 8085 if (!target[propertyName] && property.isMany) { 8086 defineProperty(target, property, []); 8087 } 8088 8089 return target[propertyName]; 8090 }; 8091 8092 8093 /** 8094 * Define a property on the target element 8095 * 8096 * @param {Object} target 8097 * @param {String} name 8098 * @param {Object} options 8099 */ 8100 Properties.prototype.define = function(target, name, options) { 8101 Object.defineProperty(target, name, options); 8102 }; 8103 8104 8105 /** 8106 * Define the descriptor for an element 8107 */ 8108 Properties.prototype.defineDescriptor = function(target, descriptor) { 8109 this.define(target, '$descriptor', { value: descriptor }); 8110 }; 8111 8112 /** 8113 * Define the model for an element 8114 */ 8115 Properties.prototype.defineModel = function(target, model) { 8116 this.define(target, '$model', { value: model }); 8117 }; 8118 8119 8120 function isUndefined(val) { 8121 return typeof val === 'undefined'; 8122 } 8123 8124 function defineProperty(target, property, value) { 8125 Object.defineProperty(target, property.name, { 8126 enumerable: !property.isReference, 8127 writable: true, 8128 value: value, 8129 configurable: true 8130 }); 8131 } 8132 8133 //// Moddle implementation ///////////////////////////////////////////////// 8134 8135 /** 8136 * @class Moddle 8137 * 8138 * A model that can be used to create elements of a specific type. 8139 * 8140 * @example 8141 * 8142 * var Moddle = require('moddle'); 8143 * 8144 * var pkg = { 8145 * name: 'mypackage', 8146 * prefix: 'my', 8147 * types: [ 8148 * { name: 'Root' } 8149 * ] 8150 * }; 8151 * 8152 * var moddle = new Moddle([pkg]); 8153 * 8154 * @param {Array<Package>} packages the packages to contain 8155 */ 8156 function Moddle(packages) { 8157 8158 this.properties = new Properties(this); 8159 8160 this.factory = new Factory(this, this.properties); 8161 this.registry = new Registry(packages, this.properties); 8162 8163 this.typeCache = {}; 8164 } 8165 8166 8167 /** 8168 * Create an instance of the specified type. 8169 * 8170 * @method Moddle#create 8171 * 8172 * @example 8173 * 8174 * var foo = moddle.create('my:Foo'); 8175 * var bar = moddle.create('my:Bar', { id: 'BAR_1' }); 8176 * 8177 * @param {String|Object} descriptor the type descriptor or name know to the model 8178 * @param {Object} attrs a number of attributes to initialize the model instance with 8179 * @return {Object} model instance 8180 */ 8181 Moddle.prototype.create = function(descriptor, attrs) { 8182 var Type = this.getType(descriptor); 8183 8184 if (!Type) { 8185 throw new Error('unknown type <' + descriptor + '>'); 8186 } 8187 8188 return new Type(attrs); 8189 }; 8190 8191 8192 /** 8193 * Returns the type representing a given descriptor 8194 * 8195 * @method Moddle#getType 8196 * 8197 * @example 8198 * 8199 * var Foo = moddle.getType('my:Foo'); 8200 * var foo = new Foo({ 'id' : 'FOO_1' }); 8201 * 8202 * @param {String|Object} descriptor the type descriptor or name know to the model 8203 * @return {Object} the type representing the descriptor 8204 */ 8205 Moddle.prototype.getType = function(descriptor) { 8206 8207 var cache = this.typeCache; 8208 8209 var name = isString(descriptor) ? descriptor : descriptor.ns.name; 8210 8211 var type = cache[name]; 8212 8213 if (!type) { 8214 descriptor = this.registry.getEffectiveDescriptor(name); 8215 type = cache[name] = this.factory.createType(descriptor); 8216 } 8217 8218 return type; 8219 }; 8220 8221 8222 /** 8223 * Creates an any-element type to be used within model instances. 8224 * 8225 * This can be used to create custom elements that lie outside the meta-model. 8226 * The created element contains all the meta-data required to serialize it 8227 * as part of meta-model elements. 8228 * 8229 * @method Moddle#createAny 8230 * 8231 * @example 8232 * 8233 * var foo = moddle.createAny('vendor:Foo', 'http://vendor', { 8234 * value: 'bar' 8235 * }); 8236 * 8237 * var container = moddle.create('my:Container', 'http://my', { 8238 * any: [ foo ] 8239 * }); 8240 * 8241 * // go ahead and serialize the stuff 8242 * 8243 * 8244 * @param {String} name the name of the element 8245 * @param {String} nsUri the namespace uri of the element 8246 * @param {Object} [properties] a map of properties to initialize the instance with 8247 * @return {Object} the any type instance 8248 */ 8249 Moddle.prototype.createAny = function(name, nsUri, properties) { 8250 8251 var nameNs = parseName(name); 8252 8253 var element = { 8254 $type: name, 8255 $instanceOf: function(type) { 8256 return type === this.$type; 8257 } 8258 }; 8259 8260 var descriptor = { 8261 name: name, 8262 isGeneric: true, 8263 ns: { 8264 prefix: nameNs.prefix, 8265 localName: nameNs.localName, 8266 uri: nsUri 8267 } 8268 }; 8269 8270 this.properties.defineDescriptor(element, descriptor); 8271 this.properties.defineModel(element, this); 8272 this.properties.define(element, '$parent', { enumerable: false, writable: true }); 8273 this.properties.define(element, '$instanceOf', { enumerable: false, writable: true }); 8274 8275 forEach(properties, function(a, key) { 8276 if (isObject(a) && a.value !== undefined) { 8277 element[a.name] = a.value; 8278 } else { 8279 element[key] = a; 8280 } 8281 }); 8282 8283 return element; 8284 }; 8285 8286 /** 8287 * Returns a registered package by uri or prefix 8288 * 8289 * @return {Object} the package 8290 */ 8291 Moddle.prototype.getPackage = function(uriOrPrefix) { 8292 return this.registry.getPackage(uriOrPrefix); 8293 }; 8294 8295 /** 8296 * Returns a snapshot of all known packages 8297 * 8298 * @return {Object} the package 8299 */ 8300 Moddle.prototype.getPackages = function() { 8301 return this.registry.getPackages(); 8302 }; 8303 8304 /** 8305 * Returns the descriptor for an element 8306 */ 8307 Moddle.prototype.getElementDescriptor = function(element) { 8308 return element.$descriptor; 8309 }; 8310 8311 /** 8312 * Returns true if the given descriptor or instance 8313 * represents the given type. 8314 * 8315 * May be applied to this, if element is omitted. 8316 */ 8317 Moddle.prototype.hasType = function(element, type) { 8318 if (type === undefined) { 8319 type = element; 8320 element = this; 8321 } 8322 8323 var descriptor = element.$model.getElementDescriptor(element); 8324 8325 return (type in descriptor.allTypesByName); 8326 }; 8327 8328 /** 8329 * Returns the descriptor of an elements named property 8330 */ 8331 Moddle.prototype.getPropertyDescriptor = function(element, property) { 8332 return this.getElementDescriptor(element).propertiesByName[property]; 8333 }; 8334 8335 /** 8336 * Returns a mapped type's descriptor 8337 */ 8338 Moddle.prototype.getTypeDescriptor = function(type) { 8339 return this.registry.typeMap[type]; 8340 }; 8341 8342 var fromCharCode = String.fromCharCode; 8343 8344 var hasOwnProperty = Object.prototype.hasOwnProperty; 8345 8346 var ENTITY_PATTERN = /&#(\d+);|&#x([0-9a-f]+);|&(\w+);/ig; 8347 8348 var ENTITY_MAPPING = { 8349 'amp': '&', 8350 'apos': '\'', 8351 'gt': '>', 8352 'lt': '<', 8353 'quot': '"' 8354 }; 8355 8356 // map UPPERCASE variants of supported special chars 8357 Object.keys(ENTITY_MAPPING).forEach(function(k) { 8358 ENTITY_MAPPING[k.toUpperCase()] = ENTITY_MAPPING[k]; 8359 }); 8360 8361 8362 function replaceEntities(_, d, x, z) { 8363 8364 // reserved names, i.e. 8365 if (z) { 8366 if (hasOwnProperty.call(ENTITY_MAPPING, z)) { 8367 return ENTITY_MAPPING[z]; 8368 } else { 8369 8370 // fall back to original value 8371 return '&' + z + ';'; 8372 } 8373 } 8374 8375 // decimal encoded char 8376 if (d) { 8377 return fromCharCode(d); 8378 } 8379 8380 // hex encoded char 8381 return fromCharCode(parseInt(x, 16)); 8382 } 8383 8384 8385 /** 8386 * A basic entity decoder that can decode a minimal 8387 * sub-set of reserved names (&) as well as 8388 * hex (ય) and decimal (ӏ) encoded characters. 8389 * 8390 * @param {string} str 8391 * 8392 * @return {string} decoded string 8393 */ 8394 function decodeEntities(s) { 8395 if (s.length > 3 && s.indexOf('&') !== -1) { 8396 return s.replace(ENTITY_PATTERN, replaceEntities); 8397 } 8398 8399 return s; 8400 } 8401 8402 var XSI_URI = 'http://www.w3.org/2001/XMLSchema-instance'; 8403 var XSI_PREFIX = 'xsi'; 8404 var XSI_TYPE$1 = 'xsi:type'; 8405 8406 var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node'; 8407 8408 function error$2(msg) { 8409 return new Error(msg); 8410 } 8411 8412 function missingNamespaceForPrefix(prefix) { 8413 return 'missing namespace for prefix <' + prefix + '>'; 8414 } 8415 8416 function getter(getFn) { 8417 return { 8418 'get': getFn, 8419 'enumerable': true 8420 }; 8421 } 8422 8423 function cloneNsMatrix(nsMatrix) { 8424 var clone = {}, key; 8425 for (key in nsMatrix) { 8426 clone[key] = nsMatrix[key]; 8427 } 8428 return clone; 8429 } 8430 8431 function uriPrefix(prefix) { 8432 return prefix + '$uri'; 8433 } 8434 8435 function buildNsMatrix(nsUriToPrefix) { 8436 var nsMatrix = {}, 8437 uri, 8438 prefix; 8439 8440 for (uri in nsUriToPrefix) { 8441 prefix = nsUriToPrefix[uri]; 8442 nsMatrix[prefix] = prefix; 8443 nsMatrix[uriPrefix(prefix)] = uri; 8444 } 8445 8446 return nsMatrix; 8447 } 8448 8449 function noopGetContext() { 8450 return { 'line': 0, 'column': 0 }; 8451 } 8452 8453 function throwFunc(err) { 8454 throw err; 8455 } 8456 8457 /** 8458 * Creates a new parser with the given options. 8459 * 8460 * @constructor 8461 * 8462 * @param {!Object<string, ?>=} options 8463 */ 8464 function Parser(options) { 8465 8466 if (!this) { 8467 return new Parser(options); 8468 } 8469 8470 var proxy = options && options['proxy']; 8471 8472 var onText, 8473 onOpenTag, 8474 onCloseTag, 8475 onCDATA, 8476 onError = throwFunc, 8477 onWarning, 8478 onComment, 8479 onQuestion, 8480 onAttention; 8481 8482 var getContext = noopGetContext; 8483 8484 /** 8485 * Do we need to parse the current elements attributes for namespaces? 8486 * 8487 * @type {boolean} 8488 */ 8489 var maybeNS = false; 8490 8491 /** 8492 * Do we process namespaces at all? 8493 * 8494 * @type {boolean} 8495 */ 8496 var isNamespace = false; 8497 8498 /** 8499 * The caught error returned on parse end 8500 * 8501 * @type {Error} 8502 */ 8503 var returnError = null; 8504 8505 /** 8506 * Should we stop parsing? 8507 * 8508 * @type {boolean} 8509 */ 8510 var parseStop = false; 8511 8512 /** 8513 * A map of { uri: prefix } used by the parser. 8514 * 8515 * This map will ensure we can normalize prefixes during processing; 8516 * for each uri, only one prefix will be exposed to the handlers. 8517 * 8518 * @type {!Object<string, string>}} 8519 */ 8520 var nsUriToPrefix; 8521 8522 /** 8523 * Handle parse error. 8524 * 8525 * @param {string|Error} err 8526 */ 8527 function handleError(err) { 8528 if (!(err instanceof Error)) { 8529 err = error$2(err); 8530 } 8531 8532 returnError = err; 8533 8534 onError(err, getContext); 8535 } 8536 8537 /** 8538 * Handle parse error. 8539 * 8540 * @param {string|Error} err 8541 */ 8542 function handleWarning(err) { 8543 8544 if (!onWarning) { 8545 return; 8546 } 8547 8548 if (!(err instanceof Error)) { 8549 err = error$2(err); 8550 } 8551 8552 onWarning(err, getContext); 8553 } 8554 8555 /** 8556 * Register parse listener. 8557 * 8558 * @param {string} name 8559 * @param {Function} cb 8560 * 8561 * @return {Parser} 8562 */ 8563 this['on'] = function(name, cb) { 8564 8565 if (typeof cb !== 'function') { 8566 throw error$2('required args <name, cb>'); 8567 } 8568 8569 switch (name) { 8570 case 'openTag': onOpenTag = cb; break; 8571 case 'text': onText = cb; break; 8572 case 'closeTag': onCloseTag = cb; break; 8573 case 'error': onError = cb; break; 8574 case 'warn': onWarning = cb; break; 8575 case 'cdata': onCDATA = cb; break; 8576 case 'attention': onAttention = cb; break; // <!XXXXX zzzz="eeee"> 8577 case 'question': onQuestion = cb; break; // <? .... ?> 8578 case 'comment': onComment = cb; break; 8579 default: 8580 throw error$2('unsupported event: ' + name); 8581 } 8582 8583 return this; 8584 }; 8585 8586 /** 8587 * Set the namespace to prefix mapping. 8588 * 8589 * @example 8590 * 8591 * parser.ns({ 8592 * 'http://foo': 'foo', 8593 * 'http://bar': 'bar' 8594 * }); 8595 * 8596 * @param {!Object<string, string>} nsMap 8597 * 8598 * @return {Parser} 8599 */ 8600 this['ns'] = function(nsMap) { 8601 8602 if (typeof nsMap === 'undefined') { 8603 nsMap = {}; 8604 } 8605 8606 if (typeof nsMap !== 'object') { 8607 throw error$2('required args <nsMap={}>'); 8608 } 8609 8610 var _nsUriToPrefix = {}, k; 8611 8612 for (k in nsMap) { 8613 _nsUriToPrefix[k] = nsMap[k]; 8614 } 8615 8616 // FORCE default mapping for schema instance 8617 _nsUriToPrefix[XSI_URI] = XSI_PREFIX; 8618 8619 isNamespace = true; 8620 nsUriToPrefix = _nsUriToPrefix; 8621 8622 return this; 8623 }; 8624 8625 /** 8626 * Parse xml string. 8627 * 8628 * @param {string} xml 8629 * 8630 * @return {Error} returnError, if not thrown 8631 */ 8632 this['parse'] = function(xml) { 8633 if (typeof xml !== 'string') { 8634 throw error$2('required args <xml=string>'); 8635 } 8636 8637 returnError = null; 8638 8639 parse(xml); 8640 8641 getContext = noopGetContext; 8642 parseStop = false; 8643 8644 return returnError; 8645 }; 8646 8647 /** 8648 * Stop parsing. 8649 */ 8650 this['stop'] = function() { 8651 parseStop = true; 8652 }; 8653 8654 /** 8655 * Parse string, invoking configured listeners on element. 8656 * 8657 * @param {string} xml 8658 */ 8659 function parse(xml) { 8660 var nsMatrixStack = isNamespace ? [] : null, 8661 nsMatrix = isNamespace ? buildNsMatrix(nsUriToPrefix) : null, 8662 _nsMatrix, 8663 nodeStack = [], 8664 anonymousNsCount = 0, 8665 tagStart = false, 8666 tagEnd = false, 8667 i = 0, j = 0, 8668 x, y, q, w, v, 8669 xmlns, 8670 elementName, 8671 _elementName, 8672 elementProxy 8673 ; 8674 8675 var attrsString = '', 8676 attrsStart = 0, 8677 cachedAttrs // false = parsed with errors, null = needs parsing 8678 ; 8679 8680 /** 8681 * Parse attributes on demand and returns the parsed attributes. 8682 * 8683 * Return semantics: (1) `false` on attribute parse error, 8684 * (2) object hash on extracted attrs. 8685 * 8686 * @return {boolean|Object} 8687 */ 8688 function getAttrs() { 8689 if (cachedAttrs !== null) { 8690 return cachedAttrs; 8691 } 8692 8693 var nsUri, 8694 nsUriPrefix, 8695 nsName, 8696 defaultAlias = isNamespace && nsMatrix['xmlns'], 8697 attrList = isNamespace && maybeNS ? [] : null, 8698 i = attrsStart, 8699 s = attrsString, 8700 l = s.length, 8701 hasNewMatrix, 8702 newalias, 8703 value, 8704 alias, 8705 name, 8706 attrs = {}, 8707 seenAttrs = {}, 8708 skipAttr, 8709 w, 8710 j; 8711 8712 parseAttr: 8713 for (; i < l; i++) { 8714 skipAttr = false; 8715 w = s.charCodeAt(i); 8716 8717 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE={ \f\n\r\t\v} 8718 continue; 8719 } 8720 8721 // wait for non whitespace character 8722 if (w < 65 || w > 122 || (w > 90 && w < 97)) { 8723 if (w !== 95 && w !== 58) { // char 95"_" 58":" 8724 handleWarning('illegal first char attribute name'); 8725 skipAttr = true; 8726 } 8727 } 8728 8729 // parse attribute name 8730 for (j = i + 1; j < l; j++) { 8731 w = s.charCodeAt(j); 8732 8733 if ( 8734 w > 96 && w < 123 || 8735 w > 64 && w < 91 || 8736 w > 47 && w < 59 || 8737 w === 46 || // '.' 8738 w === 45 || // '-' 8739 w === 95 // '_' 8740 ) { 8741 continue; 8742 } 8743 8744 // unexpected whitespace 8745 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE 8746 handleWarning('missing attribute value'); 8747 i = j; 8748 8749 continue parseAttr; 8750 } 8751 8752 // expected "=" 8753 if (w === 61) { // "=" == 61 8754 break; 8755 } 8756 8757 handleWarning('illegal attribute name char'); 8758 skipAttr = true; 8759 } 8760 8761 name = s.substring(i, j); 8762 8763 if (name === 'xmlns:xmlns') { 8764 handleWarning('illegal declaration of xmlns'); 8765 skipAttr = true; 8766 } 8767 8768 w = s.charCodeAt(j + 1); 8769 8770 if (w === 34) { // '"' 8771 j = s.indexOf('"', i = j + 2); 8772 8773 if (j === -1) { 8774 j = s.indexOf('\'', i); 8775 8776 if (j !== -1) { 8777 handleWarning('attribute value quote missmatch'); 8778 skipAttr = true; 8779 } 8780 } 8781 8782 } else if (w === 39) { // "'" 8783 j = s.indexOf('\'', i = j + 2); 8784 8785 if (j === -1) { 8786 j = s.indexOf('"', i); 8787 8788 if (j !== -1) { 8789 handleWarning('attribute value quote missmatch'); 8790 skipAttr = true; 8791 } 8792 } 8793 8794 } else { 8795 handleWarning('missing attribute value quotes'); 8796 skipAttr = true; 8797 8798 // skip to next space 8799 for (j = j + 1; j < l; j++) { 8800 w = s.charCodeAt(j + 1); 8801 8802 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE 8803 break; 8804 } 8805 } 8806 8807 } 8808 8809 if (j === -1) { 8810 handleWarning('missing closing quotes'); 8811 8812 j = l; 8813 skipAttr = true; 8814 } 8815 8816 if (!skipAttr) { 8817 value = s.substring(i, j); 8818 } 8819 8820 i = j; 8821 8822 // ensure SPACE follows attribute 8823 // skip illegal content otherwise 8824 // example a="b"c 8825 for (; j + 1 < l; j++) { 8826 w = s.charCodeAt(j + 1); 8827 8828 if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE 8829 break; 8830 } 8831 8832 // FIRST ILLEGAL CHAR 8833 if (i === j) { 8834 handleWarning('illegal character after attribute end'); 8835 skipAttr = true; 8836 } 8837 } 8838 8839 // advance cursor to next attribute 8840 i = j + 1; 8841 8842 if (skipAttr) { 8843 continue parseAttr; 8844 } 8845 8846 // check attribute re-declaration 8847 if (name in seenAttrs) { 8848 handleWarning('attribute <' + name + '> already defined'); 8849 continue; 8850 } 8851 8852 seenAttrs[name] = true; 8853 8854 if (!isNamespace) { 8855 attrs[name] = value; 8856 continue; 8857 } 8858 8859 // try to extract namespace information 8860 if (maybeNS) { 8861 newalias = ( 8862 name === 'xmlns' 8863 ? 'xmlns' 8864 : (name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:') 8865 ? name.substr(6) 8866 : null 8867 ); 8868 8869 // handle xmlns(:alias) assignment 8870 if (newalias !== null) { 8871 nsUri = decodeEntities(value); 8872 nsUriPrefix = uriPrefix(newalias); 8873 8874 alias = nsUriToPrefix[nsUri]; 8875 8876 if (!alias) { 8877 8878 // no prefix defined or prefix collision 8879 if ( 8880 (newalias === 'xmlns') || 8881 (nsUriPrefix in nsMatrix && nsMatrix[nsUriPrefix] !== nsUri) 8882 ) { 8883 8884 // alocate free ns prefix 8885 do { 8886 alias = 'ns' + (anonymousNsCount++); 8887 } while (typeof nsMatrix[alias] !== 'undefined'); 8888 } else { 8889 alias = newalias; 8890 } 8891 8892 nsUriToPrefix[nsUri] = alias; 8893 } 8894 8895 if (nsMatrix[newalias] !== alias) { 8896 if (!hasNewMatrix) { 8897 nsMatrix = cloneNsMatrix(nsMatrix); 8898 hasNewMatrix = true; 8899 } 8900 8901 nsMatrix[newalias] = alias; 8902 if (newalias === 'xmlns') { 8903 nsMatrix[uriPrefix(alias)] = nsUri; 8904 defaultAlias = alias; 8905 } 8906 8907 nsMatrix[nsUriPrefix] = nsUri; 8908 } 8909 8910 // expose xmlns(:asd)="..." in attributes 8911 attrs[name] = value; 8912 continue; 8913 } 8914 8915 // collect attributes until all namespace 8916 // declarations are processed 8917 attrList.push(name, value); 8918 continue; 8919 8920 } /** end if (maybeNs) */ 8921 8922 // handle attributes on element without 8923 // namespace declarations 8924 w = name.indexOf(':'); 8925 if (w === -1) { 8926 attrs[name] = value; 8927 continue; 8928 } 8929 8930 // normalize ns attribute name 8931 if (!(nsName = nsMatrix[name.substring(0, w)])) { 8932 handleWarning(missingNamespaceForPrefix(name.substring(0, w))); 8933 continue; 8934 } 8935 8936 name = defaultAlias === nsName 8937 ? name.substr(w + 1) 8938 : nsName + name.substr(w); 8939 8940 // end: normalize ns attribute name 8941 8942 // normalize xsi:type ns attribute value 8943 if (name === XSI_TYPE$1) { 8944 w = value.indexOf(':'); 8945 8946 if (w !== -1) { 8947 nsName = value.substring(0, w); 8948 8949 // handle default prefixes, i.e. xs:String gracefully 8950 nsName = nsMatrix[nsName] || nsName; 8951 value = nsName + value.substring(w); 8952 } else { 8953 value = defaultAlias + ':' + value; 8954 } 8955 } 8956 8957 // end: normalize xsi:type ns attribute value 8958 8959 attrs[name] = value; 8960 } 8961 8962 8963 // handle deferred, possibly namespaced attributes 8964 if (maybeNS) { 8965 8966 // normalize captured attributes 8967 for (i = 0, l = attrList.length; i < l; i++) { 8968 8969 name = attrList[i++]; 8970 value = attrList[i]; 8971 8972 w = name.indexOf(':'); 8973 8974 if (w !== -1) { 8975 8976 // normalize ns attribute name 8977 if (!(nsName = nsMatrix[name.substring(0, w)])) { 8978 handleWarning(missingNamespaceForPrefix(name.substring(0, w))); 8979 continue; 8980 } 8981 8982 name = defaultAlias === nsName 8983 ? name.substr(w + 1) 8984 : nsName + name.substr(w); 8985 8986 // end: normalize ns attribute name 8987 8988 // normalize xsi:type ns attribute value 8989 if (name === XSI_TYPE$1) { 8990 w = value.indexOf(':'); 8991 8992 if (w !== -1) { 8993 nsName = value.substring(0, w); 8994 8995 // handle default prefixes, i.e. xs:String gracefully 8996 nsName = nsMatrix[nsName] || nsName; 8997 value = nsName + value.substring(w); 8998 } else { 8999 value = defaultAlias + ':' + value; 9000 } 9001 } 9002 9003 // end: normalize xsi:type ns attribute value 9004 } 9005 9006 attrs[name] = value; 9007 } 9008 9009 // end: normalize captured attributes 9010 } 9011 9012 return cachedAttrs = attrs; 9013 } 9014 9015 /** 9016 * Extract the parse context { line, column, part } 9017 * from the current parser position. 9018 * 9019 * @return {Object} parse context 9020 */ 9021 function getParseContext() { 9022 var splitsRe = /(\r\n|\r|\n)/g; 9023 9024 var line = 0; 9025 var column = 0; 9026 var startOfLine = 0; 9027 var endOfLine = j; 9028 var match; 9029 var data; 9030 9031 while (i >= startOfLine) { 9032 9033 match = splitsRe.exec(xml); 9034 9035 if (!match) { 9036 break; 9037 } 9038 9039 // end of line = (break idx + break chars) 9040 endOfLine = match[0].length + match.index; 9041 9042 if (endOfLine > i) { 9043 break; 9044 } 9045 9046 // advance to next line 9047 line += 1; 9048 9049 startOfLine = endOfLine; 9050 } 9051 9052 // EOF errors 9053 if (i == -1) { 9054 column = endOfLine; 9055 data = xml.substring(j); 9056 } else 9057 9058 // start errors 9059 if (j === 0) { 9060 data = xml.substring(j, i); 9061 } 9062 9063 // other errors 9064 else { 9065 column = i - startOfLine; 9066 data = (j == -1 ? xml.substring(i) : xml.substring(i, j + 1)); 9067 } 9068 9069 return { 9070 'data': data, 9071 'line': line, 9072 'column': column 9073 }; 9074 } 9075 9076 getContext = getParseContext; 9077 9078 9079 if (proxy) { 9080 elementProxy = Object.create({}, { 9081 'name': getter(function() { 9082 return elementName; 9083 }), 9084 'originalName': getter(function() { 9085 return _elementName; 9086 }), 9087 'attrs': getter(getAttrs), 9088 'ns': getter(function() { 9089 return nsMatrix; 9090 }) 9091 }); 9092 } 9093 9094 // actual parse logic 9095 while (j !== -1) { 9096 9097 if (xml.charCodeAt(j) === 60) { // "<" 9098 i = j; 9099 } else { 9100 i = xml.indexOf('<', j); 9101 } 9102 9103 // parse end 9104 if (i === -1) { 9105 if (nodeStack.length) { 9106 return handleError('unexpected end of file'); 9107 } 9108 9109 if (j === 0) { 9110 return handleError('missing start tag'); 9111 } 9112 9113 if (j < xml.length) { 9114 if (xml.substring(j).trim()) { 9115 handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE); 9116 } 9117 } 9118 9119 return; 9120 } 9121 9122 // parse text 9123 if (j !== i) { 9124 9125 if (nodeStack.length) { 9126 if (onText) { 9127 onText(xml.substring(j, i), decodeEntities, getContext); 9128 9129 if (parseStop) { 9130 return; 9131 } 9132 } 9133 } else { 9134 if (xml.substring(j, i).trim()) { 9135 handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE); 9136 9137 if (parseStop) { 9138 return; 9139 } 9140 } 9141 } 9142 } 9143 9144 w = xml.charCodeAt(i+1); 9145 9146 // parse comments + CDATA 9147 if (w === 33) { // "!" 9148 q = xml.charCodeAt(i+2); 9149 9150 // CDATA section 9151 if (q === 91 && xml.substr(i + 3, 6) === 'CDATA[') { // 91 == "[" 9152 j = xml.indexOf(']]>', i); 9153 if (j === -1) { 9154 return handleError('unclosed cdata'); 9155 } 9156 9157 if (onCDATA) { 9158 onCDATA(xml.substring(i + 9, j), getContext); 9159 if (parseStop) { 9160 return; 9161 } 9162 } 9163 9164 j += 3; 9165 continue; 9166 } 9167 9168 // comment 9169 if (q === 45 && xml.charCodeAt(i + 3) === 45) { // 45 == "-" 9170 j = xml.indexOf('-->', i); 9171 if (j === -1) { 9172 return handleError('unclosed comment'); 9173 } 9174 9175 9176 if (onComment) { 9177 onComment(xml.substring(i + 4, j), decodeEntities, getContext); 9178 if (parseStop) { 9179 return; 9180 } 9181 } 9182 9183 j += 3; 9184 continue; 9185 } 9186 } 9187 9188 // parse question <? ... ?> 9189 if (w === 63) { // "?" 9190 j = xml.indexOf('?>', i); 9191 if (j === -1) { 9192 return handleError('unclosed question'); 9193 } 9194 9195 if (onQuestion) { 9196 onQuestion(xml.substring(i, j + 2), getContext); 9197 if (parseStop) { 9198 return; 9199 } 9200 } 9201 9202 j += 2; 9203 continue; 9204 } 9205 9206 // find matching closing tag for attention or standard tags 9207 // for that we must skip through attribute values 9208 // (enclosed in single or double quotes) 9209 for (x = i + 1; ; x++) { 9210 v = xml.charCodeAt(x); 9211 if (isNaN(v)) { 9212 j = -1; 9213 return handleError('unclosed tag'); 9214 } 9215 9216 // [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'" 9217 // skips the quoted string 9218 // (double quotes) does not appear in a literal enclosed by (double quotes) 9219 // (single quote) does not appear in a literal enclosed by (single quote) 9220 if (v === 34) { // '"' 9221 q = xml.indexOf('"', x + 1); 9222 x = q !== -1 ? q : x; 9223 } else if (v === 39) { // "'" 9224 q = xml.indexOf("'", x + 1); 9225 x = q !== -1 ? q : x; 9226 } else if (v === 62) { // '>' 9227 j = x; 9228 break; 9229 } 9230 } 9231 9232 9233 // parse attention <! ...> 9234 // previously comment and CDATA have already been parsed 9235 if (w === 33) { // "!" 9236 9237 if (onAttention) { 9238 onAttention(xml.substring(i, j + 1), decodeEntities, getContext); 9239 if (parseStop) { 9240 return; 9241 } 9242 } 9243 9244 j += 1; 9245 continue; 9246 } 9247 9248 // don't process attributes; 9249 // there are none 9250 cachedAttrs = {}; 9251 9252 // if (xml.charCodeAt(i+1) === 47) { // </... 9253 if (w === 47) { // </... 9254 tagStart = false; 9255 tagEnd = true; 9256 9257 if (!nodeStack.length) { 9258 return handleError('missing open tag'); 9259 } 9260 9261 // verify open <-> close tag match 9262 x = elementName = nodeStack.pop(); 9263 q = i + 2 + x.length; 9264 9265 if (xml.substring(i + 2, q) !== x) { 9266 return handleError('closing tag mismatch'); 9267 } 9268 9269 // verify chars in close tag 9270 for (; q < j; q++) { 9271 w = xml.charCodeAt(q); 9272 9273 if (w === 32 || (w > 8 && w < 14)) { // \f\n\r\t\v space 9274 continue; 9275 } 9276 9277 return handleError('close tag'); 9278 } 9279 9280 } else { 9281 if (xml.charCodeAt(j - 1) === 47) { // .../> 9282 x = elementName = xml.substring(i + 1, j - 1); 9283 9284 tagStart = true; 9285 tagEnd = true; 9286 9287 } else { 9288 x = elementName = xml.substring(i + 1, j); 9289 9290 tagStart = true; 9291 tagEnd = false; 9292 } 9293 9294 if (!(w > 96 && w < 123 || w > 64 && w < 91 || w === 95 || w === 58)) { // char 95"_" 58":" 9295 return handleError('illegal first char nodeName'); 9296 } 9297 9298 for (q = 1, y = x.length; q < y; q++) { 9299 w = x.charCodeAt(q); 9300 9301 if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w == 46) { 9302 continue; 9303 } 9304 9305 if (w === 32 || (w < 14 && w > 8)) { // \f\n\r\t\v space 9306 elementName = x.substring(0, q); 9307 9308 // maybe there are attributes 9309 cachedAttrs = null; 9310 break; 9311 } 9312 9313 return handleError('invalid nodeName'); 9314 } 9315 9316 if (!tagEnd) { 9317 nodeStack.push(elementName); 9318 } 9319 } 9320 9321 if (isNamespace) { 9322 9323 _nsMatrix = nsMatrix; 9324 9325 if (tagStart) { 9326 9327 // remember old namespace 9328 // unless we're self-closing 9329 if (!tagEnd) { 9330 nsMatrixStack.push(_nsMatrix); 9331 } 9332 9333 if (cachedAttrs === null) { 9334 9335 // quick check, whether there may be namespace 9336 // declarations on the node; if that is the case 9337 // we need to eagerly parse the node attributes 9338 if ((maybeNS = x.indexOf('xmlns', q) !== -1)) { 9339 attrsStart = q; 9340 attrsString = x; 9341 9342 getAttrs(); 9343 9344 maybeNS = false; 9345 } 9346 } 9347 } 9348 9349 _elementName = elementName; 9350 9351 w = elementName.indexOf(':'); 9352 if (w !== -1) { 9353 xmlns = nsMatrix[elementName.substring(0, w)]; 9354 9355 // prefix given; namespace must exist 9356 if (!xmlns) { 9357 return handleError('missing namespace on <' + _elementName + '>'); 9358 } 9359 9360 elementName = elementName.substr(w + 1); 9361 } else { 9362 xmlns = nsMatrix['xmlns']; 9363 9364 // if no default namespace is defined, 9365 // we'll import the element as anonymous. 9366 // 9367 // it is up to users to correct that to the document defined 9368 // targetNamespace, or whatever their undersanding of the 9369 // XML spec mandates. 9370 } 9371 9372 // adjust namespace prefixs as configured 9373 if (xmlns) { 9374 elementName = xmlns + ':' + elementName; 9375 } 9376 9377 } 9378 9379 if (tagStart) { 9380 attrsStart = q; 9381 attrsString = x; 9382 9383 if (onOpenTag) { 9384 if (proxy) { 9385 onOpenTag(elementProxy, decodeEntities, tagEnd, getContext); 9386 } else { 9387 onOpenTag(elementName, getAttrs, decodeEntities, tagEnd, getContext); 9388 } 9389 9390 if (parseStop) { 9391 return; 9392 } 9393 } 9394 9395 } 9396 9397 if (tagEnd) { 9398 9399 if (onCloseTag) { 9400 onCloseTag(proxy ? elementProxy : elementName, decodeEntities, tagStart, getContext); 9401 9402 if (parseStop) { 9403 return; 9404 } 9405 } 9406 9407 // restore old namespace 9408 if (isNamespace) { 9409 if (!tagStart) { 9410 nsMatrix = nsMatrixStack.pop(); 9411 } else { 9412 nsMatrix = _nsMatrix; 9413 } 9414 } 9415 } 9416 9417 j += 1; 9418 } 9419 } /** end parse */ 9420 9421 } 9422 9423 function hasLowerCaseAlias(pkg) { 9424 return pkg.xml && pkg.xml.tagAlias === 'lowerCase'; 9425 } 9426 9427 var DEFAULT_NS_MAP = { 9428 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', 9429 'xml': 'http://www.w3.org/XML/1998/namespace' 9430 }; 9431 9432 var XSI_TYPE = 'xsi:type'; 9433 9434 function serializeFormat(element) { 9435 return element.xml && element.xml.serialize; 9436 } 9437 9438 function serializeAsType(element) { 9439 return serializeFormat(element) === XSI_TYPE; 9440 } 9441 9442 function serializeAsProperty(element) { 9443 return serializeFormat(element) === 'property'; 9444 } 9445 9446 function capitalize(str) { 9447 return str.charAt(0).toUpperCase() + str.slice(1); 9448 } 9449 9450 function aliasToName(aliasNs, pkg) { 9451 9452 if (!hasLowerCaseAlias(pkg)) { 9453 return aliasNs.name; 9454 } 9455 9456 return aliasNs.prefix + ':' + capitalize(aliasNs.localName); 9457 } 9458 9459 function prefixedToName(nameNs, pkg) { 9460 9461 var name = nameNs.name, 9462 localName = nameNs.localName; 9463 9464 var typePrefix = pkg.xml && pkg.xml.typePrefix; 9465 9466 if (typePrefix && localName.indexOf(typePrefix) === 0) { 9467 return nameNs.prefix + ':' + localName.slice(typePrefix.length); 9468 } else { 9469 return name; 9470 } 9471 } 9472 9473 function normalizeXsiTypeName(name, model) { 9474 9475 var nameNs = parseName(name); 9476 var pkg = model.getPackage(nameNs.prefix); 9477 9478 return prefixedToName(nameNs, pkg); 9479 } 9480 9481 function error$1(message) { 9482 return new Error(message); 9483 } 9484 9485 /** 9486 * Get the moddle descriptor for a given instance or type. 9487 * 9488 * @param {ModdleElement|Function} element 9489 * 9490 * @return {Object} the moddle descriptor 9491 */ 9492 function getModdleDescriptor(element) { 9493 return element.$descriptor; 9494 } 9495 9496 9497 /** 9498 * A parse context. 9499 * 9500 * @class 9501 * 9502 * @param {Object} options 9503 * @param {ElementHandler} options.rootHandler the root handler for parsing a document 9504 * @param {boolean} [options.lax=false] whether or not to ignore invalid elements 9505 */ 9506 function Context(options) { 9507 9508 /** 9509 * @property {ElementHandler} rootHandler 9510 */ 9511 9512 /** 9513 * @property {Boolean} lax 9514 */ 9515 9516 assign(this, options); 9517 9518 this.elementsById = {}; 9519 this.references = []; 9520 this.warnings = []; 9521 9522 /** 9523 * Add an unresolved reference. 9524 * 9525 * @param {Object} reference 9526 */ 9527 this.addReference = function(reference) { 9528 this.references.push(reference); 9529 }; 9530 9531 /** 9532 * Add a processed element. 9533 * 9534 * @param {ModdleElement} element 9535 */ 9536 this.addElement = function(element) { 9537 9538 if (!element) { 9539 throw error$1('expected element'); 9540 } 9541 9542 var elementsById = this.elementsById; 9543 9544 var descriptor = getModdleDescriptor(element); 9545 9546 var idProperty = descriptor.idProperty, 9547 id; 9548 9549 if (idProperty) { 9550 id = element.get(idProperty.name); 9551 9552 if (id) { 9553 9554 // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar 9555 if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) { 9556 throw new Error('illegal ID <' + id + '>'); 9557 } 9558 9559 if (elementsById[id]) { 9560 throw error$1('duplicate ID <' + id + '>'); 9561 } 9562 9563 elementsById[id] = element; 9564 } 9565 } 9566 }; 9567 9568 /** 9569 * Add an import warning. 9570 * 9571 * @param {Object} warning 9572 * @param {String} warning.message 9573 * @param {Error} [warning.error] 9574 */ 9575 this.addWarning = function(warning) { 9576 this.warnings.push(warning); 9577 }; 9578 } 9579 9580 function BaseHandler() {} 9581 9582 BaseHandler.prototype.handleEnd = function() {}; 9583 BaseHandler.prototype.handleText = function() {}; 9584 BaseHandler.prototype.handleNode = function() {}; 9585 9586 9587 /** 9588 * A simple pass through handler that does nothing except for 9589 * ignoring all input it receives. 9590 * 9591 * This is used to ignore unknown elements and 9592 * attributes. 9593 */ 9594 function NoopHandler() { } 9595 9596 NoopHandler.prototype = Object.create(BaseHandler.prototype); 9597 9598 NoopHandler.prototype.handleNode = function() { 9599 return this; 9600 }; 9601 9602 function BodyHandler() {} 9603 9604 BodyHandler.prototype = Object.create(BaseHandler.prototype); 9605 9606 BodyHandler.prototype.handleText = function(text) { 9607 this.body = (this.body || '') + text; 9608 }; 9609 9610 function ReferenceHandler(property, context) { 9611 this.property = property; 9612 this.context = context; 9613 } 9614 9615 ReferenceHandler.prototype = Object.create(BodyHandler.prototype); 9616 9617 ReferenceHandler.prototype.handleNode = function(node) { 9618 9619 if (this.element) { 9620 throw error$1('expected no sub nodes'); 9621 } else { 9622 this.element = this.createReference(node); 9623 } 9624 9625 return this; 9626 }; 9627 9628 ReferenceHandler.prototype.handleEnd = function() { 9629 this.element.id = this.body; 9630 }; 9631 9632 ReferenceHandler.prototype.createReference = function(node) { 9633 return { 9634 property: this.property.ns.name, 9635 id: '' 9636 }; 9637 }; 9638 9639 function ValueHandler(propertyDesc, element) { 9640 this.element = element; 9641 this.propertyDesc = propertyDesc; 9642 } 9643 9644 ValueHandler.prototype = Object.create(BodyHandler.prototype); 9645 9646 ValueHandler.prototype.handleEnd = function() { 9647 9648 var value = this.body || '', 9649 element = this.element, 9650 propertyDesc = this.propertyDesc; 9651 9652 value = coerceType(propertyDesc.type, value); 9653 9654 if (propertyDesc.isMany) { 9655 element.get(propertyDesc.name).push(value); 9656 } else { 9657 element.set(propertyDesc.name, value); 9658 } 9659 }; 9660 9661 9662 function BaseElementHandler() {} 9663 9664 BaseElementHandler.prototype = Object.create(BodyHandler.prototype); 9665 9666 BaseElementHandler.prototype.handleNode = function(node) { 9667 var parser = this, 9668 element = this.element; 9669 9670 if (!element) { 9671 element = this.element = this.createElement(node); 9672 9673 this.context.addElement(element); 9674 } else { 9675 parser = this.handleChild(node); 9676 } 9677 9678 return parser; 9679 }; 9680 9681 /** 9682 * @class Reader.ElementHandler 9683 * 9684 */ 9685 function ElementHandler(model, typeName, context) { 9686 this.model = model; 9687 this.type = model.getType(typeName); 9688 this.context = context; 9689 } 9690 9691 ElementHandler.prototype = Object.create(BaseElementHandler.prototype); 9692 9693 ElementHandler.prototype.addReference = function(reference) { 9694 this.context.addReference(reference); 9695 }; 9696 9697 ElementHandler.prototype.handleText = function(text) { 9698 9699 var element = this.element, 9700 descriptor = getModdleDescriptor(element), 9701 bodyProperty = descriptor.bodyProperty; 9702 9703 if (!bodyProperty) { 9704 throw error$1('unexpected body text <' + text + '>'); 9705 } 9706 9707 BodyHandler.prototype.handleText.call(this, text); 9708 }; 9709 9710 ElementHandler.prototype.handleEnd = function() { 9711 9712 var value = this.body, 9713 element = this.element, 9714 descriptor = getModdleDescriptor(element), 9715 bodyProperty = descriptor.bodyProperty; 9716 9717 if (bodyProperty && value !== undefined) { 9718 value = coerceType(bodyProperty.type, value); 9719 element.set(bodyProperty.name, value); 9720 } 9721 }; 9722 9723 /** 9724 * Create an instance of the model from the given node. 9725 * 9726 * @param {Element} node the xml node 9727 */ 9728 ElementHandler.prototype.createElement = function(node) { 9729 var attributes = node.attributes, 9730 Type = this.type, 9731 descriptor = getModdleDescriptor(Type), 9732 context = this.context, 9733 instance = new Type({}), 9734 model = this.model, 9735 propNameNs; 9736 9737 forEach(attributes, function(value, name) { 9738 9739 var prop = descriptor.propertiesByName[name], 9740 values; 9741 9742 if (prop && prop.isReference) { 9743 9744 if (!prop.isMany) { 9745 context.addReference({ 9746 element: instance, 9747 property: prop.ns.name, 9748 id: value 9749 }); 9750 } else { 9751 9752 // IDREFS: parse references as whitespace-separated list 9753 values = value.split(' '); 9754 9755 forEach(values, function(v) { 9756 context.addReference({ 9757 element: instance, 9758 property: prop.ns.name, 9759 id: v 9760 }); 9761 }); 9762 } 9763 9764 } else { 9765 if (prop) { 9766 value = coerceType(prop.type, value); 9767 } else 9768 if (name !== 'xmlns') { 9769 propNameNs = parseName(name, descriptor.ns.prefix); 9770 9771 // check whether attribute is defined in a well-known namespace 9772 // if that is the case we emit a warning to indicate potential misuse 9773 if (model.getPackage(propNameNs.prefix)) { 9774 9775 context.addWarning({ 9776 message: 'unknown attribute <' + name + '>', 9777 element: instance, 9778 property: name, 9779 value: value 9780 }); 9781 } 9782 } 9783 9784 instance.set(name, value); 9785 } 9786 }); 9787 9788 return instance; 9789 }; 9790 9791 ElementHandler.prototype.getPropertyForNode = function(node) { 9792 9793 var name = node.name; 9794 var nameNs = parseName(name); 9795 9796 var type = this.type, 9797 model = this.model, 9798 descriptor = getModdleDescriptor(type); 9799 9800 var propertyName = nameNs.name, 9801 property = descriptor.propertiesByName[propertyName], 9802 elementTypeName, 9803 elementType; 9804 9805 // search for properties by name first 9806 9807 if (property && !property.isAttr) { 9808 9809 if (serializeAsType(property)) { 9810 elementTypeName = node.attributes[XSI_TYPE]; 9811 9812 // xsi type is optional, if it does not exists the 9813 // default type is assumed 9814 if (elementTypeName) { 9815 9816 // take possible type prefixes from XML 9817 // into account, i.e.: xsi:type="t{ActualType}" 9818 elementTypeName = normalizeXsiTypeName(elementTypeName, model); 9819 9820 elementType = model.getType(elementTypeName); 9821 9822 return assign({}, property, { 9823 effectiveType: getModdleDescriptor(elementType).name 9824 }); 9825 } 9826 } 9827 9828 // search for properties by name first 9829 return property; 9830 } 9831 9832 var pkg = model.getPackage(nameNs.prefix); 9833 9834 if (pkg) { 9835 elementTypeName = aliasToName(nameNs, pkg); 9836 elementType = model.getType(elementTypeName); 9837 9838 // search for collection members later 9839 property = find(descriptor.properties, function(p) { 9840 return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type); 9841 }); 9842 9843 if (property) { 9844 return assign({}, property, { 9845 effectiveType: getModdleDescriptor(elementType).name 9846 }); 9847 } 9848 } else { 9849 9850 // parse unknown element (maybe extension) 9851 property = find(descriptor.properties, function(p) { 9852 return !p.isReference && !p.isAttribute && p.type === 'Element'; 9853 }); 9854 9855 if (property) { 9856 return property; 9857 } 9858 } 9859 9860 throw error$1('unrecognized element <' + nameNs.name + '>'); 9861 }; 9862 9863 ElementHandler.prototype.toString = function() { 9864 return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']'; 9865 }; 9866 9867 ElementHandler.prototype.valueHandler = function(propertyDesc, element) { 9868 return new ValueHandler(propertyDesc, element); 9869 }; 9870 9871 ElementHandler.prototype.referenceHandler = function(propertyDesc) { 9872 return new ReferenceHandler(propertyDesc, this.context); 9873 }; 9874 9875 ElementHandler.prototype.handler = function(type) { 9876 if (type === 'Element') { 9877 return new GenericElementHandler(this.model, type, this.context); 9878 } else { 9879 return new ElementHandler(this.model, type, this.context); 9880 } 9881 }; 9882 9883 /** 9884 * Handle the child element parsing 9885 * 9886 * @param {Element} node the xml node 9887 */ 9888 ElementHandler.prototype.handleChild = function(node) { 9889 var propertyDesc, type, element, childHandler; 9890 9891 propertyDesc = this.getPropertyForNode(node); 9892 element = this.element; 9893 9894 type = propertyDesc.effectiveType || propertyDesc.type; 9895 9896 if (isSimple(type)) { 9897 return this.valueHandler(propertyDesc, element); 9898 } 9899 9900 if (propertyDesc.isReference) { 9901 childHandler = this.referenceHandler(propertyDesc).handleNode(node); 9902 } else { 9903 childHandler = this.handler(type).handleNode(node); 9904 } 9905 9906 var newElement = childHandler.element; 9907 9908 // child handles may decide to skip elements 9909 // by not returning anything 9910 if (newElement !== undefined) { 9911 9912 if (propertyDesc.isMany) { 9913 element.get(propertyDesc.name).push(newElement); 9914 } else { 9915 element.set(propertyDesc.name, newElement); 9916 } 9917 9918 if (propertyDesc.isReference) { 9919 assign(newElement, { 9920 element: element 9921 }); 9922 9923 this.context.addReference(newElement); 9924 } else { 9925 9926 // establish child -> parent relationship 9927 newElement.$parent = element; 9928 } 9929 } 9930 9931 return childHandler; 9932 }; 9933 9934 /** 9935 * An element handler that performs special validation 9936 * to ensure the node it gets initialized with matches 9937 * the handlers type (namespace wise). 9938 * 9939 * @param {Moddle} model 9940 * @param {String} typeName 9941 * @param {Context} context 9942 */ 9943 function RootElementHandler(model, typeName, context) { 9944 ElementHandler.call(this, model, typeName, context); 9945 } 9946 9947 RootElementHandler.prototype = Object.create(ElementHandler.prototype); 9948 9949 RootElementHandler.prototype.createElement = function(node) { 9950 9951 var name = node.name, 9952 nameNs = parseName(name), 9953 model = this.model, 9954 type = this.type, 9955 pkg = model.getPackage(nameNs.prefix), 9956 typeName = pkg && aliasToName(nameNs, pkg) || name; 9957 9958 // verify the correct namespace if we parse 9959 // the first element in the handler tree 9960 // 9961 // this ensures we don't mistakenly import wrong namespace elements 9962 if (!type.hasType(typeName)) { 9963 throw error$1('unexpected element <' + node.originalName + '>'); 9964 } 9965 9966 return ElementHandler.prototype.createElement.call(this, node); 9967 }; 9968 9969 9970 function GenericElementHandler(model, typeName, context) { 9971 this.model = model; 9972 this.context = context; 9973 } 9974 9975 GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype); 9976 9977 GenericElementHandler.prototype.createElement = function(node) { 9978 9979 var name = node.name, 9980 ns = parseName(name), 9981 prefix = ns.prefix, 9982 uri = node.ns[prefix + '$uri'], 9983 attributes = node.attributes; 9984 9985 return this.model.createAny(name, uri, attributes); 9986 }; 9987 9988 GenericElementHandler.prototype.handleChild = function(node) { 9989 9990 var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node), 9991 element = this.element; 9992 9993 var newElement = handler.element, 9994 children; 9995 9996 if (newElement !== undefined) { 9997 children = element.$children = element.$children || []; 9998 children.push(newElement); 9999 10000 // establish child -> parent relationship 10001 newElement.$parent = element; 10002 } 10003 10004 return handler; 10005 }; 10006 10007 GenericElementHandler.prototype.handleEnd = function() { 10008 if (this.body) { 10009 this.element.$body = this.body; 10010 } 10011 }; 10012 10013 /** 10014 * A reader for a meta-model 10015 * 10016 * @param {Object} options 10017 * @param {Model} options.model used to read xml files 10018 * @param {Boolean} options.lax whether to make parse errors warnings 10019 */ 10020 function Reader(options) { 10021 10022 if (options instanceof Moddle) { 10023 options = { 10024 model: options 10025 }; 10026 } 10027 10028 assign(this, { lax: false }, options); 10029 } 10030 10031 /** 10032 * The fromXML result. 10033 * 10034 * @typedef {Object} ParseResult 10035 * 10036 * @property {ModdleElement} rootElement 10037 * @property {Array<Object>} references 10038 * @property {Array<Error>} warnings 10039 * @property {Object} elementsById - a mapping containing each ID -> ModdleElement 10040 */ 10041 10042 /** 10043 * The fromXML result. 10044 * 10045 * @typedef {Error} ParseError 10046 * 10047 * @property {Array<Error>} warnings 10048 */ 10049 10050 /** 10051 * Parse the given XML into a moddle document tree. 10052 * 10053 * @param {String} xml 10054 * @param {ElementHandler|Object} options or rootHandler 10055 * 10056 * @returns {Promise<ParseResult, ParseError>} 10057 */ 10058 Reader.prototype.fromXML = function(xml, options, done) { 10059 10060 var rootHandler = options.rootHandler; 10061 10062 if (options instanceof ElementHandler) { 10063 10064 // root handler passed via (xml, { rootHandler: ElementHandler }, ...) 10065 rootHandler = options; 10066 options = {}; 10067 } else { 10068 if (typeof options === 'string') { 10069 10070 // rootHandler passed via (xml, 'someString', ...) 10071 rootHandler = this.handler(options); 10072 options = {}; 10073 } else if (typeof rootHandler === 'string') { 10074 10075 // rootHandler passed via (xml, { rootHandler: 'someString' }, ...) 10076 rootHandler = this.handler(rootHandler); 10077 } 10078 } 10079 10080 var model = this.model, 10081 lax = this.lax; 10082 10083 var context = new Context(assign({}, options, { rootHandler: rootHandler })), 10084 parser = new Parser({ proxy: true }), 10085 stack = createStack(); 10086 10087 rootHandler.context = context; 10088 10089 // push root handler 10090 stack.push(rootHandler); 10091 10092 10093 /** 10094 * Handle error. 10095 * 10096 * @param {Error} err 10097 * @param {Function} getContext 10098 * @param {boolean} lax 10099 * 10100 * @return {boolean} true if handled 10101 */ 10102 function handleError(err, getContext, lax) { 10103 10104 var ctx = getContext(); 10105 10106 var line = ctx.line, 10107 column = ctx.column, 10108 data = ctx.data; 10109 10110 // we receive the full context data here, 10111 // for elements trim down the information 10112 // to the tag name, only 10113 if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) { 10114 data = data.slice(0, data.indexOf(' ')) + '>'; 10115 } 10116 10117 var message = 10118 'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' + 10119 'line: ' + line + '\n\t' + 10120 'column: ' + column + '\n\t' + 10121 'nested error: ' + err.message; 10122 10123 if (lax) { 10124 context.addWarning({ 10125 message: message, 10126 error: err 10127 }); 10128 10129 return true; 10130 } else { 10131 throw error$1(message); 10132 } 10133 } 10134 10135 function handleWarning(err, getContext) { 10136 10137 // just like handling errors in <lax=true> mode 10138 return handleError(err, getContext, true); 10139 } 10140 10141 /** 10142 * Resolve collected references on parse end. 10143 */ 10144 function resolveReferences() { 10145 10146 var elementsById = context.elementsById; 10147 var references = context.references; 10148 10149 var i, r; 10150 10151 for (i = 0; (r = references[i]); i++) { 10152 var element = r.element; 10153 var reference = elementsById[r.id]; 10154 var property = getModdleDescriptor(element).propertiesByName[r.property]; 10155 10156 if (!reference) { 10157 context.addWarning({ 10158 message: 'unresolved reference <' + r.id + '>', 10159 element: r.element, 10160 property: r.property, 10161 value: r.id 10162 }); 10163 } 10164 10165 if (property.isMany) { 10166 var collection = element.get(property.name), 10167 idx = collection.indexOf(r); 10168 10169 // we replace an existing place holder (idx != -1) or 10170 // append to the collection instead 10171 if (idx === -1) { 10172 idx = collection.length; 10173 } 10174 10175 if (!reference) { 10176 10177 // remove unresolvable reference 10178 collection.splice(idx, 1); 10179 } else { 10180 10181 // add or update reference in collection 10182 collection[idx] = reference; 10183 } 10184 } else { 10185 element.set(property.name, reference); 10186 } 10187 } 10188 } 10189 10190 function handleClose() { 10191 stack.pop().handleEnd(); 10192 } 10193 10194 var PREAMBLE_START_PATTERN = /^<\?xml /i; 10195 10196 var ENCODING_PATTERN = / encoding="([^"]+)"/i; 10197 10198 var UTF_8_PATTERN = /^utf-8$/i; 10199 10200 function handleQuestion(question) { 10201 10202 if (!PREAMBLE_START_PATTERN.test(question)) { 10203 return; 10204 } 10205 10206 var match = ENCODING_PATTERN.exec(question); 10207 var encoding = match && match[1]; 10208 10209 if (!encoding || UTF_8_PATTERN.test(encoding)) { 10210 return; 10211 } 10212 10213 context.addWarning({ 10214 message: 10215 'unsupported document encoding <' + encoding + '>, ' + 10216 'falling back to UTF-8' 10217 }); 10218 } 10219 10220 function handleOpen(node, getContext) { 10221 var handler = stack.peek(); 10222 10223 try { 10224 stack.push(handler.handleNode(node)); 10225 } catch (err) { 10226 10227 if (handleError(err, getContext, lax)) { 10228 stack.push(new NoopHandler()); 10229 } 10230 } 10231 } 10232 10233 function handleCData(text, getContext) { 10234 10235 try { 10236 stack.peek().handleText(text); 10237 } catch (err) { 10238 handleWarning(err, getContext); 10239 } 10240 } 10241 10242 function handleText(text, getContext) { 10243 10244 // strip whitespace only nodes, i.e. before 10245 // <!CDATA[ ... ]> sections and in between tags 10246 10247 if (!text.trim()) { 10248 return; 10249 } 10250 10251 handleCData(text, getContext); 10252 } 10253 10254 var uriMap = model.getPackages().reduce(function(uriMap, p) { 10255 uriMap[p.uri] = p.prefix; 10256 10257 return uriMap; 10258 }, { 10259 'http://www.w3.org/XML/1998/namespace': 'xml' // add default xml ns 10260 }); 10261 parser 10262 .ns(uriMap) 10263 .on('openTag', function(obj, decodeStr, selfClosing, getContext) { 10264 10265 // gracefully handle unparsable attributes (attrs=false) 10266 var attrs = obj.attrs || {}; 10267 10268 var decodedAttrs = Object.keys(attrs).reduce(function(d, key) { 10269 var value = decodeStr(attrs[key]); 10270 10271 d[key] = value; 10272 10273 return d; 10274 }, {}); 10275 10276 var node = { 10277 name: obj.name, 10278 originalName: obj.originalName, 10279 attributes: decodedAttrs, 10280 ns: obj.ns 10281 }; 10282 10283 handleOpen(node, getContext); 10284 }) 10285 .on('question', handleQuestion) 10286 .on('closeTag', handleClose) 10287 .on('cdata', handleCData) 10288 .on('text', function(text, decodeEntities, getContext) { 10289 handleText(decodeEntities(text), getContext); 10290 }) 10291 .on('error', handleError) 10292 .on('warn', handleWarning); 10293 10294 // async XML parsing to make sure the execution environment 10295 // (node or brower) is kept responsive and that certain optimization 10296 // strategies can kick in. 10297 return new Promise(function(resolve, reject) { 10298 10299 var err; 10300 10301 try { 10302 parser.parse(xml); 10303 10304 resolveReferences(); 10305 } catch (e) { 10306 err = e; 10307 } 10308 10309 var rootElement = rootHandler.element; 10310 10311 if (!err && !rootElement) { 10312 err = error$1('failed to parse document as <' + rootHandler.type.$descriptor.name + '>'); 10313 } 10314 10315 var warnings = context.warnings; 10316 var references = context.references; 10317 var elementsById = context.elementsById; 10318 10319 if (err) { 10320 err.warnings = warnings; 10321 10322 return reject(err); 10323 } else { 10324 return resolve({ 10325 rootElement: rootElement, 10326 elementsById: elementsById, 10327 references: references, 10328 warnings: warnings 10329 }); 10330 } 10331 }); 10332 }; 10333 10334 Reader.prototype.handler = function(name) { 10335 return new RootElementHandler(this.model, name); 10336 }; 10337 10338 10339 // helpers ////////////////////////// 10340 10341 function createStack() { 10342 var stack = []; 10343 10344 Object.defineProperty(stack, 'peek', { 10345 value: function() { 10346 return this[this.length - 1]; 10347 } 10348 }); 10349 10350 return stack; 10351 } 10352 10353 var XML_PREAMBLE = '<?xml version="1.0" encoding="UTF-8"?>\n'; 10354 10355 var ESCAPE_ATTR_CHARS = /<|>|'|"|&|\n\r|\n/g; 10356 var ESCAPE_CHARS = /<|>|&/g; 10357 10358 10359 function Namespaces(parent) { 10360 10361 var prefixMap = {}; 10362 var uriMap = {}; 10363 var used = {}; 10364 10365 var wellknown = []; 10366 var custom = []; 10367 10368 // API 10369 10370 this.byUri = function(uri) { 10371 return uriMap[uri] || ( 10372 parent && parent.byUri(uri) 10373 ); 10374 }; 10375 10376 this.add = function(ns, isWellknown) { 10377 10378 uriMap[ns.uri] = ns; 10379 10380 if (isWellknown) { 10381 wellknown.push(ns); 10382 } else { 10383 custom.push(ns); 10384 } 10385 10386 this.mapPrefix(ns.prefix, ns.uri); 10387 }; 10388 10389 this.uriByPrefix = function(prefix) { 10390 return prefixMap[prefix || 'xmlns']; 10391 }; 10392 10393 this.mapPrefix = function(prefix, uri) { 10394 prefixMap[prefix || 'xmlns'] = uri; 10395 }; 10396 10397 this.getNSKey = function(ns) { 10398 return (ns.prefix !== undefined) ? (ns.uri + '|' + ns.prefix) : ns.uri; 10399 }; 10400 10401 this.logUsed = function(ns) { 10402 10403 var uri = ns.uri; 10404 var nsKey = this.getNSKey(ns); 10405 10406 used[nsKey] = this.byUri(uri); 10407 10408 // Inform parent recursively about the usage of this NS 10409 if (parent) { 10410 parent.logUsed(ns); 10411 } 10412 }; 10413 10414 this.getUsed = function(ns) { 10415 10416 function isUsed(ns) { 10417 var nsKey = self.getNSKey(ns); 10418 10419 return used[nsKey]; 10420 } 10421 10422 var self = this; 10423 10424 var allNs = [].concat(wellknown, custom); 10425 10426 return allNs.filter(isUsed); 10427 }; 10428 10429 } 10430 10431 function lower(string) { 10432 return string.charAt(0).toLowerCase() + string.slice(1); 10433 } 10434 10435 function nameToAlias(name, pkg) { 10436 if (hasLowerCaseAlias(pkg)) { 10437 return lower(name); 10438 } else { 10439 return name; 10440 } 10441 } 10442 10443 function inherits(ctor, superCtor) { 10444 ctor.super_ = superCtor; 10445 ctor.prototype = Object.create(superCtor.prototype, { 10446 constructor: { 10447 value: ctor, 10448 enumerable: false, 10449 writable: true, 10450 configurable: true 10451 } 10452 }); 10453 } 10454 10455 function nsName(ns) { 10456 if (isString(ns)) { 10457 return ns; 10458 } else { 10459 return (ns.prefix ? ns.prefix + ':' : '') + ns.localName; 10460 } 10461 } 10462 10463 function getNsAttrs(namespaces) { 10464 10465 return namespaces.getUsed().filter(function(ns) { 10466 10467 // do not serialize built in <xml> namespace 10468 return ns.prefix !== 'xml'; 10469 }).map(function(ns) { 10470 var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : ''); 10471 return { name: name, value: ns.uri }; 10472 }); 10473 10474 } 10475 10476 function getElementNs(ns, descriptor) { 10477 if (descriptor.isGeneric) { 10478 return assign({ localName: descriptor.ns.localName }, ns); 10479 } else { 10480 return assign({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns); 10481 } 10482 } 10483 10484 function getPropertyNs(ns, descriptor) { 10485 return assign({ localName: descriptor.ns.localName }, ns); 10486 } 10487 10488 function getSerializableProperties(element) { 10489 var descriptor = element.$descriptor; 10490 10491 return filter(descriptor.properties, function(p) { 10492 var name = p.name; 10493 10494 if (p.isVirtual) { 10495 return false; 10496 } 10497 10498 // do not serialize defaults 10499 if (!has(element, name)) { 10500 return false; 10501 } 10502 10503 var value = element[name]; 10504 10505 // do not serialize default equals 10506 if (value === p.default) { 10507 return false; 10508 } 10509 10510 // do not serialize null properties 10511 if (value === null) { 10512 return false; 10513 } 10514 10515 return p.isMany ? value.length : true; 10516 }); 10517 } 10518 10519 var ESCAPE_ATTR_MAP = { 10520 '\n': '#10', 10521 '\n\r': '#10', 10522 '"': '#34', 10523 '\'': '#39', 10524 '<': '#60', 10525 '>': '#62', 10526 '&': '#38' 10527 }; 10528 10529 var ESCAPE_MAP = { 10530 '<': 'lt', 10531 '>': 'gt', 10532 '&': 'amp' 10533 }; 10534 10535 function escape(str, charPattern, replaceMap) { 10536 10537 // ensure we are handling strings here 10538 str = isString(str) ? str : '' + str; 10539 10540 return str.replace(charPattern, function(s) { 10541 return '&' + replaceMap[s] + ';'; 10542 }); 10543 } 10544 10545 /** 10546 * Escape a string attribute to not contain any bad values (line breaks, '"', ...) 10547 * 10548 * @param {String} str the string to escape 10549 * @return {String} the escaped string 10550 */ 10551 function escapeAttr(str) { 10552 return escape(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP); 10553 } 10554 10555 function escapeBody(str) { 10556 return escape(str, ESCAPE_CHARS, ESCAPE_MAP); 10557 } 10558 10559 function filterAttributes(props) { 10560 return filter(props, function(p) { return p.isAttr; }); 10561 } 10562 10563 function filterContained(props) { 10564 return filter(props, function(p) { return !p.isAttr; }); 10565 } 10566 10567 10568 function ReferenceSerializer(tagName) { 10569 this.tagName = tagName; 10570 } 10571 10572 ReferenceSerializer.prototype.build = function(element) { 10573 this.element = element; 10574 return this; 10575 }; 10576 10577 ReferenceSerializer.prototype.serializeTo = function(writer) { 10578 writer 10579 .appendIndent() 10580 .append('<' + this.tagName + '>' + this.element.id + '</' + this.tagName + '>') 10581 .appendNewLine(); 10582 }; 10583 10584 function BodySerializer() {} 10585 10586 BodySerializer.prototype.serializeValue = 10587 BodySerializer.prototype.serializeTo = function(writer) { 10588 writer.append( 10589 this.escape 10590 ? escapeBody(this.value) 10591 : this.value 10592 ); 10593 }; 10594 10595 BodySerializer.prototype.build = function(prop, value) { 10596 this.value = value; 10597 10598 if (prop.type === 'String' && value.search(ESCAPE_CHARS) !== -1) { 10599 this.escape = true; 10600 } 10601 10602 return this; 10603 }; 10604 10605 function ValueSerializer(tagName) { 10606 this.tagName = tagName; 10607 } 10608 10609 inherits(ValueSerializer, BodySerializer); 10610 10611 ValueSerializer.prototype.serializeTo = function(writer) { 10612 10613 writer 10614 .appendIndent() 10615 .append('<' + this.tagName + '>'); 10616 10617 this.serializeValue(writer); 10618 10619 writer 10620 .append('</' + this.tagName + '>') 10621 .appendNewLine(); 10622 }; 10623 10624 function ElementSerializer(parent, propertyDescriptor) { 10625 this.body = []; 10626 this.attrs = []; 10627 10628 this.parent = parent; 10629 this.propertyDescriptor = propertyDescriptor; 10630 } 10631 10632 ElementSerializer.prototype.build = function(element) { 10633 this.element = element; 10634 10635 var elementDescriptor = element.$descriptor, 10636 propertyDescriptor = this.propertyDescriptor; 10637 10638 var otherAttrs, 10639 properties; 10640 10641 var isGeneric = elementDescriptor.isGeneric; 10642 10643 if (isGeneric) { 10644 otherAttrs = this.parseGeneric(element); 10645 } else { 10646 otherAttrs = this.parseNsAttributes(element); 10647 } 10648 10649 if (propertyDescriptor) { 10650 this.ns = this.nsPropertyTagName(propertyDescriptor); 10651 } else { 10652 this.ns = this.nsTagName(elementDescriptor); 10653 } 10654 10655 // compute tag name 10656 this.tagName = this.addTagName(this.ns); 10657 10658 if (!isGeneric) { 10659 properties = getSerializableProperties(element); 10660 10661 this.parseAttributes(filterAttributes(properties)); 10662 this.parseContainments(filterContained(properties)); 10663 } 10664 10665 this.parseGenericAttributes(element, otherAttrs); 10666 10667 return this; 10668 }; 10669 10670 ElementSerializer.prototype.nsTagName = function(descriptor) { 10671 var effectiveNs = this.logNamespaceUsed(descriptor.ns); 10672 return getElementNs(effectiveNs, descriptor); 10673 }; 10674 10675 ElementSerializer.prototype.nsPropertyTagName = function(descriptor) { 10676 var effectiveNs = this.logNamespaceUsed(descriptor.ns); 10677 return getPropertyNs(effectiveNs, descriptor); 10678 }; 10679 10680 ElementSerializer.prototype.isLocalNs = function(ns) { 10681 return ns.uri === this.ns.uri; 10682 }; 10683 10684 /** 10685 * Get the actual ns attribute name for the given element. 10686 * 10687 * @param {Object} element 10688 * @param {Boolean} [element.inherited=false] 10689 * 10690 * @return {Object} nsName 10691 */ 10692 ElementSerializer.prototype.nsAttributeName = function(element) { 10693 10694 var ns; 10695 10696 if (isString(element)) { 10697 ns = parseName(element); 10698 } else { 10699 ns = element.ns; 10700 } 10701 10702 // return just local name for inherited attributes 10703 if (element.inherited) { 10704 return { localName: ns.localName }; 10705 } 10706 10707 // parse + log effective ns 10708 var effectiveNs = this.logNamespaceUsed(ns); 10709 10710 // LOG ACTUAL namespace use 10711 this.getNamespaces().logUsed(effectiveNs); 10712 10713 // strip prefix if same namespace like parent 10714 if (this.isLocalNs(effectiveNs)) { 10715 return { localName: ns.localName }; 10716 } else { 10717 return assign({ localName: ns.localName }, effectiveNs); 10718 } 10719 }; 10720 10721 ElementSerializer.prototype.parseGeneric = function(element) { 10722 10723 var self = this, 10724 body = this.body; 10725 10726 var attributes = []; 10727 10728 forEach(element, function(val, key) { 10729 10730 var nonNsAttr; 10731 10732 if (key === '$body') { 10733 body.push(new BodySerializer().build({ type: 'String' }, val)); 10734 } else 10735 if (key === '$children') { 10736 forEach(val, function(child) { 10737 body.push(new ElementSerializer(self).build(child)); 10738 }); 10739 } else 10740 if (key.indexOf('$') !== 0) { 10741 nonNsAttr = self.parseNsAttribute(element, key, val); 10742 10743 if (nonNsAttr) { 10744 attributes.push({ name: key, value: val }); 10745 } 10746 } 10747 }); 10748 10749 return attributes; 10750 }; 10751 10752 ElementSerializer.prototype.parseNsAttribute = function(element, name, value) { 10753 var model = element.$model; 10754 10755 var nameNs = parseName(name); 10756 10757 var ns; 10758 10759 // parse xmlns:foo="http://foo.bar" 10760 if (nameNs.prefix === 'xmlns') { 10761 ns = { prefix: nameNs.localName, uri: value }; 10762 } 10763 10764 // parse xmlns="http://foo.bar" 10765 if (!nameNs.prefix && nameNs.localName === 'xmlns') { 10766 ns = { uri: value }; 10767 } 10768 10769 if (!ns) { 10770 return { 10771 name: name, 10772 value: value 10773 }; 10774 } 10775 10776 if (model && model.getPackage(value)) { 10777 10778 // register well known namespace 10779 this.logNamespace(ns, true, true); 10780 } else { 10781 10782 // log custom namespace directly as used 10783 var actualNs = this.logNamespaceUsed(ns, true); 10784 10785 this.getNamespaces().logUsed(actualNs); 10786 } 10787 }; 10788 10789 10790 /** 10791 * Parse namespaces and return a list of left over generic attributes 10792 * 10793 * @param {Object} element 10794 * @return {Array<Object>} 10795 */ 10796 ElementSerializer.prototype.parseNsAttributes = function(element, attrs) { 10797 var self = this; 10798 10799 var genericAttrs = element.$attrs; 10800 10801 var attributes = []; 10802 10803 // parse namespace attributes first 10804 // and log them. push non namespace attributes to a list 10805 // and process them later 10806 forEach(genericAttrs, function(value, name) { 10807 10808 var nonNsAttr = self.parseNsAttribute(element, name, value); 10809 10810 if (nonNsAttr) { 10811 attributes.push(nonNsAttr); 10812 } 10813 }); 10814 10815 return attributes; 10816 }; 10817 10818 ElementSerializer.prototype.parseGenericAttributes = function(element, attributes) { 10819 10820 var self = this; 10821 10822 forEach(attributes, function(attr) { 10823 10824 // do not serialize xsi:type attribute 10825 // it is set manually based on the actual implementation type 10826 if (attr.name === XSI_TYPE) { 10827 return; 10828 } 10829 10830 try { 10831 self.addAttribute(self.nsAttributeName(attr.name), attr.value); 10832 } catch (e) { 10833 console.warn( 10834 'missing namespace information for ', 10835 attr.name, '=', attr.value, 'on', element, 10836 e); 10837 } 10838 }); 10839 }; 10840 10841 ElementSerializer.prototype.parseContainments = function(properties) { 10842 10843 var self = this, 10844 body = this.body, 10845 element = this.element; 10846 10847 forEach(properties, function(p) { 10848 var value = element.get(p.name), 10849 isReference = p.isReference, 10850 isMany = p.isMany; 10851 10852 if (!isMany) { 10853 value = [ value ]; 10854 } 10855 10856 if (p.isBody) { 10857 body.push(new BodySerializer().build(p, value[0])); 10858 } else 10859 if (isSimple(p.type)) { 10860 forEach(value, function(v) { 10861 body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v)); 10862 }); 10863 } else 10864 if (isReference) { 10865 forEach(value, function(v) { 10866 body.push(new ReferenceSerializer(self.addTagName(self.nsPropertyTagName(p))).build(v)); 10867 }); 10868 } else { 10869 10870 // allow serialization via type 10871 // rather than element name 10872 var asType = serializeAsType(p), 10873 asProperty = serializeAsProperty(p); 10874 10875 forEach(value, function(v) { 10876 var serializer; 10877 10878 if (asType) { 10879 serializer = new TypeSerializer(self, p); 10880 } else 10881 if (asProperty) { 10882 serializer = new ElementSerializer(self, p); 10883 } else { 10884 serializer = new ElementSerializer(self); 10885 } 10886 10887 body.push(serializer.build(v)); 10888 }); 10889 } 10890 }); 10891 }; 10892 10893 ElementSerializer.prototype.getNamespaces = function(local) { 10894 10895 var namespaces = this.namespaces, 10896 parent = this.parent, 10897 parentNamespaces; 10898 10899 if (!namespaces) { 10900 parentNamespaces = parent && parent.getNamespaces(); 10901 10902 if (local || !parentNamespaces) { 10903 this.namespaces = namespaces = new Namespaces(parentNamespaces); 10904 } else { 10905 namespaces = parentNamespaces; 10906 } 10907 } 10908 10909 return namespaces; 10910 }; 10911 10912 ElementSerializer.prototype.logNamespace = function(ns, wellknown, local) { 10913 var namespaces = this.getNamespaces(local); 10914 10915 var nsUri = ns.uri, 10916 nsPrefix = ns.prefix; 10917 10918 var existing = namespaces.byUri(nsUri); 10919 10920 if (!existing || local) { 10921 namespaces.add(ns, wellknown); 10922 } 10923 10924 namespaces.mapPrefix(nsPrefix, nsUri); 10925 10926 return ns; 10927 }; 10928 10929 ElementSerializer.prototype.logNamespaceUsed = function(ns, local) { 10930 var element = this.element, 10931 model = element.$model, 10932 namespaces = this.getNamespaces(local); 10933 10934 // ns may be 10935 // 10936 // * prefix only 10937 // * prefix:uri 10938 // * localName only 10939 10940 var prefix = ns.prefix, 10941 uri = ns.uri, 10942 newPrefix, idx, 10943 wellknownUri; 10944 10945 // handle anonymous namespaces (elementForm=unqualified), cf. #23 10946 if (!prefix && !uri) { 10947 return { localName: ns.localName }; 10948 } 10949 10950 wellknownUri = DEFAULT_NS_MAP[prefix] || model && (model.getPackage(prefix) || {}).uri; 10951 10952 uri = uri || wellknownUri || namespaces.uriByPrefix(prefix); 10953 10954 if (!uri) { 10955 throw new Error('no namespace uri given for prefix <' + prefix + '>'); 10956 } 10957 10958 ns = namespaces.byUri(uri); 10959 10960 if (!ns) { 10961 newPrefix = prefix; 10962 idx = 1; 10963 10964 // find a prefix that is not mapped yet 10965 while (namespaces.uriByPrefix(newPrefix)) { 10966 newPrefix = prefix + '_' + idx++; 10967 } 10968 10969 ns = this.logNamespace({ prefix: newPrefix, uri: uri }, wellknownUri === uri); 10970 } 10971 10972 if (prefix) { 10973 namespaces.mapPrefix(prefix, uri); 10974 } 10975 10976 return ns; 10977 }; 10978 10979 ElementSerializer.prototype.parseAttributes = function(properties) { 10980 var self = this, 10981 element = this.element; 10982 10983 forEach(properties, function(p) { 10984 10985 var value = element.get(p.name); 10986 10987 if (p.isReference) { 10988 10989 if (!p.isMany) { 10990 value = value.id; 10991 } 10992 else { 10993 var values = []; 10994 forEach(value, function(v) { 10995 values.push(v.id); 10996 }); 10997 10998 // IDREFS is a whitespace-separated list of references. 10999 value = values.join(' '); 11000 } 11001 11002 } 11003 11004 self.addAttribute(self.nsAttributeName(p), value); 11005 }); 11006 }; 11007 11008 ElementSerializer.prototype.addTagName = function(nsTagName) { 11009 var actualNs = this.logNamespaceUsed(nsTagName); 11010 11011 this.getNamespaces().logUsed(actualNs); 11012 11013 return nsName(nsTagName); 11014 }; 11015 11016 ElementSerializer.prototype.addAttribute = function(name, value) { 11017 var attrs = this.attrs; 11018 11019 if (isString(value)) { 11020 value = escapeAttr(value); 11021 } 11022 11023 attrs.push({ name: name, value: value }); 11024 }; 11025 11026 ElementSerializer.prototype.serializeAttributes = function(writer) { 11027 var attrs = this.attrs, 11028 namespaces = this.namespaces; 11029 11030 if (namespaces) { 11031 attrs = getNsAttrs(namespaces).concat(attrs); 11032 } 11033 11034 forEach(attrs, function(a) { 11035 writer 11036 .append(' ') 11037 .append(nsName(a.name)).append('="').append(a.value).append('"'); 11038 }); 11039 }; 11040 11041 ElementSerializer.prototype.serializeTo = function(writer) { 11042 var firstBody = this.body[0], 11043 indent = firstBody && firstBody.constructor !== BodySerializer; 11044 11045 writer 11046 .appendIndent() 11047 .append('<' + this.tagName); 11048 11049 this.serializeAttributes(writer); 11050 11051 writer.append(firstBody ? '>' : ' />'); 11052 11053 if (firstBody) { 11054 11055 if (indent) { 11056 writer 11057 .appendNewLine() 11058 .indent(); 11059 } 11060 11061 forEach(this.body, function(b) { 11062 b.serializeTo(writer); 11063 }); 11064 11065 if (indent) { 11066 writer 11067 .unindent() 11068 .appendIndent(); 11069 } 11070 11071 writer.append('</' + this.tagName + '>'); 11072 } 11073 11074 writer.appendNewLine(); 11075 }; 11076 11077 /** 11078 * A serializer for types that handles serialization of data types 11079 */ 11080 function TypeSerializer(parent, propertyDescriptor) { 11081 ElementSerializer.call(this, parent, propertyDescriptor); 11082 } 11083 11084 inherits(TypeSerializer, ElementSerializer); 11085 11086 TypeSerializer.prototype.parseNsAttributes = function(element) { 11087 11088 // extracted attributes 11089 var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element); 11090 11091 var descriptor = element.$descriptor; 11092 11093 // only serialize xsi:type if necessary 11094 if (descriptor.name === this.propertyDescriptor.type) { 11095 return attributes; 11096 } 11097 11098 var typeNs = this.typeNs = this.nsTagName(descriptor); 11099 this.getNamespaces().logUsed(this.typeNs); 11100 11101 // add xsi:type attribute to represent the elements 11102 // actual type 11103 11104 var pkg = element.$model.getPackage(typeNs.uri), 11105 typePrefix = (pkg.xml && pkg.xml.typePrefix) || ''; 11106 11107 this.addAttribute( 11108 this.nsAttributeName(XSI_TYPE), 11109 (typeNs.prefix ? typeNs.prefix + ':' : '') + typePrefix + descriptor.ns.localName 11110 ); 11111 11112 return attributes; 11113 }; 11114 11115 TypeSerializer.prototype.isLocalNs = function(ns) { 11116 return ns.uri === (this.typeNs || this.ns).uri; 11117 }; 11118 11119 function SavingWriter() { 11120 this.value = ''; 11121 11122 this.write = function(str) { 11123 this.value += str; 11124 }; 11125 } 11126 11127 function FormatingWriter(out, format) { 11128 11129 var indent = ['']; 11130 11131 this.append = function(str) { 11132 out.write(str); 11133 11134 return this; 11135 }; 11136 11137 this.appendNewLine = function() { 11138 if (format) { 11139 out.write('\n'); 11140 } 11141 11142 return this; 11143 }; 11144 11145 this.appendIndent = function() { 11146 if (format) { 11147 out.write(indent.join(' ')); 11148 } 11149 11150 return this; 11151 }; 11152 11153 this.indent = function() { 11154 indent.push(''); 11155 return this; 11156 }; 11157 11158 this.unindent = function() { 11159 indent.pop(); 11160 return this; 11161 }; 11162 } 11163 11164 /** 11165 * A writer for meta-model backed document trees 11166 * 11167 * @param {Object} options output options to pass into the writer 11168 */ 11169 function Writer(options) { 11170 11171 options = assign({ format: false, preamble: true }, options || {}); 11172 11173 function toXML(tree, writer) { 11174 var internalWriter = writer || new SavingWriter(); 11175 var formatingWriter = new FormatingWriter(internalWriter, options.format); 11176 11177 if (options.preamble) { 11178 formatingWriter.append(XML_PREAMBLE); 11179 } 11180 11181 new ElementSerializer().build(tree).serializeTo(formatingWriter); 11182 11183 if (!writer) { 11184 return internalWriter.value; 11185 } 11186 } 11187 11188 return { 11189 toXML: toXML 11190 }; 11191 } 11192 11193 /** 11194 * A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files. 11195 * 11196 * @class BpmnModdle 11197 * @extends Moddle 11198 * 11199 * @param {Object|Array} packages to use for instantiating the model 11200 * @param {Object} [options] additional options to pass over 11201 */ 11202 function BpmnModdle(packages, options) { 11203 Moddle.call(this, packages, options); 11204 } 11205 11206 BpmnModdle.prototype = Object.create(Moddle.prototype); 11207 11208 /** 11209 * The fromXML result. 11210 * 11211 * @typedef {Object} ParseResult 11212 * 11213 * @property {ModdleElement} rootElement 11214 * @property {Array<Object>} references 11215 * @property {Array<Error>} warnings 11216 * @property {Object} elementsById - a mapping containing each ID -> ModdleElement 11217 */ 11218 11219 /** 11220 * The fromXML error. 11221 * 11222 * @typedef {Error} ParseError 11223 * 11224 * @property {Array<Error>} warnings 11225 */ 11226 11227 /** 11228 * Instantiates a BPMN model tree from a given xml string. 11229 * 11230 * @param {String} xmlStr 11231 * @param {String} [typeName='bpmn:Definitions'] name of the root element 11232 * @param {Object} [options] options to pass to the underlying reader 11233 * 11234 * @returns {Promise<ParseResult, ParseError>} 11235 */ 11236 BpmnModdle.prototype.fromXML = function(xmlStr, typeName, options) { 11237 11238 if (!isString(typeName)) { 11239 options = typeName; 11240 typeName = 'bpmn:Definitions'; 11241 } 11242 11243 var reader = new Reader(assign({ model: this, lax: true }, options)); 11244 var rootHandler = reader.handler(typeName); 11245 11246 return reader.fromXML(xmlStr, rootHandler); 11247 }; 11248 11249 11250 /** 11251 * The toXML result. 11252 * 11253 * @typedef {Object} SerializationResult 11254 * 11255 * @property {String} xml 11256 */ 11257 11258 /** 11259 * Serializes a BPMN 2.0 object tree to XML. 11260 * 11261 * @param {String} element the root element, typically an instance of `bpmn:Definitions` 11262 * @param {Object} [options] to pass to the underlying writer 11263 * 11264 * @returns {Promise<SerializationResult, Error>} 11265 */ 11266 BpmnModdle.prototype.toXML = function(element, options) { 11267 11268 var writer = new Writer(options); 11269 11270 return new Promise(function(resolve, reject) { 11271 try { 11272 var result = writer.toXML(element); 11273 11274 return resolve({ 11275 xml: result 11276 }); 11277 } catch (err) { 11278 return reject(err); 11279 } 11280 }); 11281 }; 11282 11283 var name = "BPMN20"; 11284 var uri = "http://www.omg.org/spec/BPMN/20100524/MODEL"; 11285 var prefix = "bpmn"; 11286 var associations = [ 11287 ]; 11288 var types = [ 11289 { 11290 name: "Interface", 11291 superClass: [ 11292 "RootElement" 11293 ], 11294 properties: [ 11295 { 11296 name: "name", 11297 isAttr: true, 11298 type: "String" 11299 }, 11300 { 11301 name: "operations", 11302 type: "Operation", 11303 isMany: true 11304 }, 11305 { 11306 name: "implementationRef", 11307 isAttr: true, 11308 type: "String" 11309 } 11310 ] 11311 }, 11312 { 11313 name: "Operation", 11314 superClass: [ 11315 "BaseElement" 11316 ], 11317 properties: [ 11318 { 11319 name: "name", 11320 isAttr: true, 11321 type: "String" 11322 }, 11323 { 11324 name: "inMessageRef", 11325 type: "Message", 11326 isReference: true 11327 }, 11328 { 11329 name: "outMessageRef", 11330 type: "Message", 11331 isReference: true 11332 }, 11333 { 11334 name: "errorRef", 11335 type: "Error", 11336 isMany: true, 11337 isReference: true 11338 }, 11339 { 11340 name: "implementationRef", 11341 isAttr: true, 11342 type: "String" 11343 } 11344 ] 11345 }, 11346 { 11347 name: "EndPoint", 11348 superClass: [ 11349 "RootElement" 11350 ] 11351 }, 11352 { 11353 name: "Auditing", 11354 superClass: [ 11355 "BaseElement" 11356 ] 11357 }, 11358 { 11359 name: "GlobalTask", 11360 superClass: [ 11361 "CallableElement" 11362 ], 11363 properties: [ 11364 { 11365 name: "resources", 11366 type: "ResourceRole", 11367 isMany: true 11368 } 11369 ] 11370 }, 11371 { 11372 name: "Monitoring", 11373 superClass: [ 11374 "BaseElement" 11375 ] 11376 }, 11377 { 11378 name: "Performer", 11379 superClass: [ 11380 "ResourceRole" 11381 ] 11382 }, 11383 { 11384 name: "Process", 11385 superClass: [ 11386 "FlowElementsContainer", 11387 "CallableElement" 11388 ], 11389 properties: [ 11390 { 11391 name: "processType", 11392 type: "ProcessType", 11393 isAttr: true 11394 }, 11395 { 11396 name: "isClosed", 11397 isAttr: true, 11398 type: "Boolean" 11399 }, 11400 { 11401 name: "auditing", 11402 type: "Auditing" 11403 }, 11404 { 11405 name: "monitoring", 11406 type: "Monitoring" 11407 }, 11408 { 11409 name: "properties", 11410 type: "Property", 11411 isMany: true 11412 }, 11413 { 11414 name: "laneSets", 11415 isMany: true, 11416 replaces: "FlowElementsContainer#laneSets", 11417 type: "LaneSet" 11418 }, 11419 { 11420 name: "flowElements", 11421 isMany: true, 11422 replaces: "FlowElementsContainer#flowElements", 11423 type: "FlowElement" 11424 }, 11425 { 11426 name: "artifacts", 11427 type: "Artifact", 11428 isMany: true 11429 }, 11430 { 11431 name: "resources", 11432 type: "ResourceRole", 11433 isMany: true 11434 }, 11435 { 11436 name: "correlationSubscriptions", 11437 type: "CorrelationSubscription", 11438 isMany: true 11439 }, 11440 { 11441 name: "supports", 11442 type: "Process", 11443 isMany: true, 11444 isReference: true 11445 }, 11446 { 11447 name: "definitionalCollaborationRef", 11448 type: "Collaboration", 11449 isAttr: true, 11450 isReference: true 11451 }, 11452 { 11453 name: "isExecutable", 11454 isAttr: true, 11455 type: "Boolean" 11456 } 11457 ] 11458 }, 11459 { 11460 name: "LaneSet", 11461 superClass: [ 11462 "BaseElement" 11463 ], 11464 properties: [ 11465 { 11466 name: "lanes", 11467 type: "Lane", 11468 isMany: true 11469 }, 11470 { 11471 name: "name", 11472 isAttr: true, 11473 type: "String" 11474 } 11475 ] 11476 }, 11477 { 11478 name: "Lane", 11479 superClass: [ 11480 "BaseElement" 11481 ], 11482 properties: [ 11483 { 11484 name: "name", 11485 isAttr: true, 11486 type: "String" 11487 }, 11488 { 11489 name: "partitionElementRef", 11490 type: "BaseElement", 11491 isAttr: true, 11492 isReference: true 11493 }, 11494 { 11495 name: "partitionElement", 11496 type: "BaseElement" 11497 }, 11498 { 11499 name: "flowNodeRef", 11500 type: "FlowNode", 11501 isMany: true, 11502 isReference: true 11503 }, 11504 { 11505 name: "childLaneSet", 11506 type: "LaneSet", 11507 xml: { 11508 serialize: "xsi:type" 11509 } 11510 } 11511 ] 11512 }, 11513 { 11514 name: "GlobalManualTask", 11515 superClass: [ 11516 "GlobalTask" 11517 ] 11518 }, 11519 { 11520 name: "ManualTask", 11521 superClass: [ 11522 "Task" 11523 ] 11524 }, 11525 { 11526 name: "UserTask", 11527 superClass: [ 11528 "Task" 11529 ], 11530 properties: [ 11531 { 11532 name: "renderings", 11533 type: "Rendering", 11534 isMany: true 11535 }, 11536 { 11537 name: "implementation", 11538 isAttr: true, 11539 type: "String" 11540 } 11541 ] 11542 }, 11543 { 11544 name: "Rendering", 11545 superClass: [ 11546 "BaseElement" 11547 ] 11548 }, 11549 { 11550 name: "HumanPerformer", 11551 superClass: [ 11552 "Performer" 11553 ] 11554 }, 11555 { 11556 name: "PotentialOwner", 11557 superClass: [ 11558 "HumanPerformer" 11559 ] 11560 }, 11561 { 11562 name: "GlobalUserTask", 11563 superClass: [ 11564 "GlobalTask" 11565 ], 11566 properties: [ 11567 { 11568 name: "implementation", 11569 isAttr: true, 11570 type: "String" 11571 }, 11572 { 11573 name: "renderings", 11574 type: "Rendering", 11575 isMany: true 11576 } 11577 ] 11578 }, 11579 { 11580 name: "Gateway", 11581 isAbstract: true, 11582 superClass: [ 11583 "FlowNode" 11584 ], 11585 properties: [ 11586 { 11587 name: "gatewayDirection", 11588 type: "GatewayDirection", 11589 "default": "Unspecified", 11590 isAttr: true 11591 } 11592 ] 11593 }, 11594 { 11595 name: "EventBasedGateway", 11596 superClass: [ 11597 "Gateway" 11598 ], 11599 properties: [ 11600 { 11601 name: "instantiate", 11602 "default": false, 11603 isAttr: true, 11604 type: "Boolean" 11605 }, 11606 { 11607 name: "eventGatewayType", 11608 type: "EventBasedGatewayType", 11609 isAttr: true, 11610 "default": "Exclusive" 11611 } 11612 ] 11613 }, 11614 { 11615 name: "ComplexGateway", 11616 superClass: [ 11617 "Gateway" 11618 ], 11619 properties: [ 11620 { 11621 name: "activationCondition", 11622 type: "Expression", 11623 xml: { 11624 serialize: "xsi:type" 11625 } 11626 }, 11627 { 11628 name: "default", 11629 type: "SequenceFlow", 11630 isAttr: true, 11631 isReference: true 11632 } 11633 ] 11634 }, 11635 { 11636 name: "ExclusiveGateway", 11637 superClass: [ 11638 "Gateway" 11639 ], 11640 properties: [ 11641 { 11642 name: "default", 11643 type: "SequenceFlow", 11644 isAttr: true, 11645 isReference: true 11646 } 11647 ] 11648 }, 11649 { 11650 name: "InclusiveGateway", 11651 superClass: [ 11652 "Gateway" 11653 ], 11654 properties: [ 11655 { 11656 name: "default", 11657 type: "SequenceFlow", 11658 isAttr: true, 11659 isReference: true 11660 } 11661 ] 11662 }, 11663 { 11664 name: "ParallelGateway", 11665 superClass: [ 11666 "Gateway" 11667 ] 11668 }, 11669 { 11670 name: "RootElement", 11671 isAbstract: true, 11672 superClass: [ 11673 "BaseElement" 11674 ] 11675 }, 11676 { 11677 name: "Relationship", 11678 superClass: [ 11679 "BaseElement" 11680 ], 11681 properties: [ 11682 { 11683 name: "type", 11684 isAttr: true, 11685 type: "String" 11686 }, 11687 { 11688 name: "direction", 11689 type: "RelationshipDirection", 11690 isAttr: true 11691 }, 11692 { 11693 name: "source", 11694 isMany: true, 11695 isReference: true, 11696 type: "Element" 11697 }, 11698 { 11699 name: "target", 11700 isMany: true, 11701 isReference: true, 11702 type: "Element" 11703 } 11704 ] 11705 }, 11706 { 11707 name: "BaseElement", 11708 isAbstract: true, 11709 properties: [ 11710 { 11711 name: "id", 11712 isAttr: true, 11713 type: "String", 11714 isId: true 11715 }, 11716 { 11717 name: "documentation", 11718 type: "Documentation", 11719 isMany: true 11720 }, 11721 { 11722 name: "extensionDefinitions", 11723 type: "ExtensionDefinition", 11724 isMany: true, 11725 isReference: true 11726 }, 11727 { 11728 name: "extensionElements", 11729 type: "ExtensionElements" 11730 } 11731 ] 11732 }, 11733 { 11734 name: "Extension", 11735 properties: [ 11736 { 11737 name: "mustUnderstand", 11738 "default": false, 11739 isAttr: true, 11740 type: "Boolean" 11741 }, 11742 { 11743 name: "definition", 11744 type: "ExtensionDefinition", 11745 isAttr: true, 11746 isReference: true 11747 } 11748 ] 11749 }, 11750 { 11751 name: "ExtensionDefinition", 11752 properties: [ 11753 { 11754 name: "name", 11755 isAttr: true, 11756 type: "String" 11757 }, 11758 { 11759 name: "extensionAttributeDefinitions", 11760 type: "ExtensionAttributeDefinition", 11761 isMany: true 11762 } 11763 ] 11764 }, 11765 { 11766 name: "ExtensionAttributeDefinition", 11767 properties: [ 11768 { 11769 name: "name", 11770 isAttr: true, 11771 type: "String" 11772 }, 11773 { 11774 name: "type", 11775 isAttr: true, 11776 type: "String" 11777 }, 11778 { 11779 name: "isReference", 11780 "default": false, 11781 isAttr: true, 11782 type: "Boolean" 11783 }, 11784 { 11785 name: "extensionDefinition", 11786 type: "ExtensionDefinition", 11787 isAttr: true, 11788 isReference: true 11789 } 11790 ] 11791 }, 11792 { 11793 name: "ExtensionElements", 11794 properties: [ 11795 { 11796 name: "valueRef", 11797 isAttr: true, 11798 isReference: true, 11799 type: "Element" 11800 }, 11801 { 11802 name: "values", 11803 type: "Element", 11804 isMany: true 11805 }, 11806 { 11807 name: "extensionAttributeDefinition", 11808 type: "ExtensionAttributeDefinition", 11809 isAttr: true, 11810 isReference: true 11811 } 11812 ] 11813 }, 11814 { 11815 name: "Documentation", 11816 superClass: [ 11817 "BaseElement" 11818 ], 11819 properties: [ 11820 { 11821 name: "text", 11822 type: "String", 11823 isBody: true 11824 }, 11825 { 11826 name: "textFormat", 11827 "default": "text/plain", 11828 isAttr: true, 11829 type: "String" 11830 } 11831 ] 11832 }, 11833 { 11834 name: "Event", 11835 isAbstract: true, 11836 superClass: [ 11837 "FlowNode", 11838 "InteractionNode" 11839 ], 11840 properties: [ 11841 { 11842 name: "properties", 11843 type: "Property", 11844 isMany: true 11845 } 11846 ] 11847 }, 11848 { 11849 name: "IntermediateCatchEvent", 11850 superClass: [ 11851 "CatchEvent" 11852 ] 11853 }, 11854 { 11855 name: "IntermediateThrowEvent", 11856 superClass: [ 11857 "ThrowEvent" 11858 ] 11859 }, 11860 { 11861 name: "EndEvent", 11862 superClass: [ 11863 "ThrowEvent" 11864 ] 11865 }, 11866 { 11867 name: "StartEvent", 11868 superClass: [ 11869 "CatchEvent" 11870 ], 11871 properties: [ 11872 { 11873 name: "isInterrupting", 11874 "default": true, 11875 isAttr: true, 11876 type: "Boolean" 11877 } 11878 ] 11879 }, 11880 { 11881 name: "ThrowEvent", 11882 isAbstract: true, 11883 superClass: [ 11884 "Event" 11885 ], 11886 properties: [ 11887 { 11888 name: "dataInputs", 11889 type: "DataInput", 11890 isMany: true 11891 }, 11892 { 11893 name: "dataInputAssociations", 11894 type: "DataInputAssociation", 11895 isMany: true 11896 }, 11897 { 11898 name: "inputSet", 11899 type: "InputSet" 11900 }, 11901 { 11902 name: "eventDefinitions", 11903 type: "EventDefinition", 11904 isMany: true 11905 }, 11906 { 11907 name: "eventDefinitionRef", 11908 type: "EventDefinition", 11909 isMany: true, 11910 isReference: true 11911 } 11912 ] 11913 }, 11914 { 11915 name: "CatchEvent", 11916 isAbstract: true, 11917 superClass: [ 11918 "Event" 11919 ], 11920 properties: [ 11921 { 11922 name: "parallelMultiple", 11923 isAttr: true, 11924 type: "Boolean", 11925 "default": false 11926 }, 11927 { 11928 name: "dataOutputs", 11929 type: "DataOutput", 11930 isMany: true 11931 }, 11932 { 11933 name: "dataOutputAssociations", 11934 type: "DataOutputAssociation", 11935 isMany: true 11936 }, 11937 { 11938 name: "outputSet", 11939 type: "OutputSet" 11940 }, 11941 { 11942 name: "eventDefinitions", 11943 type: "EventDefinition", 11944 isMany: true 11945 }, 11946 { 11947 name: "eventDefinitionRef", 11948 type: "EventDefinition", 11949 isMany: true, 11950 isReference: true 11951 } 11952 ] 11953 }, 11954 { 11955 name: "BoundaryEvent", 11956 superClass: [ 11957 "CatchEvent" 11958 ], 11959 properties: [ 11960 { 11961 name: "cancelActivity", 11962 "default": true, 11963 isAttr: true, 11964 type: "Boolean" 11965 }, 11966 { 11967 name: "attachedToRef", 11968 type: "Activity", 11969 isAttr: true, 11970 isReference: true 11971 } 11972 ] 11973 }, 11974 { 11975 name: "EventDefinition", 11976 isAbstract: true, 11977 superClass: [ 11978 "RootElement" 11979 ] 11980 }, 11981 { 11982 name: "CancelEventDefinition", 11983 superClass: [ 11984 "EventDefinition" 11985 ] 11986 }, 11987 { 11988 name: "ErrorEventDefinition", 11989 superClass: [ 11990 "EventDefinition" 11991 ], 11992 properties: [ 11993 { 11994 name: "errorRef", 11995 type: "Error", 11996 isAttr: true, 11997 isReference: true 11998 } 11999 ] 12000 }, 12001 { 12002 name: "TerminateEventDefinition", 12003 superClass: [ 12004 "EventDefinition" 12005 ] 12006 }, 12007 { 12008 name: "EscalationEventDefinition", 12009 superClass: [ 12010 "EventDefinition" 12011 ], 12012 properties: [ 12013 { 12014 name: "escalationRef", 12015 type: "Escalation", 12016 isAttr: true, 12017 isReference: true 12018 } 12019 ] 12020 }, 12021 { 12022 name: "Escalation", 12023 properties: [ 12024 { 12025 name: "structureRef", 12026 type: "ItemDefinition", 12027 isAttr: true, 12028 isReference: true 12029 }, 12030 { 12031 name: "name", 12032 isAttr: true, 12033 type: "String" 12034 }, 12035 { 12036 name: "escalationCode", 12037 isAttr: true, 12038 type: "String" 12039 } 12040 ], 12041 superClass: [ 12042 "RootElement" 12043 ] 12044 }, 12045 { 12046 name: "CompensateEventDefinition", 12047 superClass: [ 12048 "EventDefinition" 12049 ], 12050 properties: [ 12051 { 12052 name: "waitForCompletion", 12053 isAttr: true, 12054 type: "Boolean", 12055 "default": true 12056 }, 12057 { 12058 name: "activityRef", 12059 type: "Activity", 12060 isAttr: true, 12061 isReference: true 12062 } 12063 ] 12064 }, 12065 { 12066 name: "TimerEventDefinition", 12067 superClass: [ 12068 "EventDefinition" 12069 ], 12070 properties: [ 12071 { 12072 name: "timeDate", 12073 type: "Expression", 12074 xml: { 12075 serialize: "xsi:type" 12076 } 12077 }, 12078 { 12079 name: "timeCycle", 12080 type: "Expression", 12081 xml: { 12082 serialize: "xsi:type" 12083 } 12084 }, 12085 { 12086 name: "timeDuration", 12087 type: "Expression", 12088 xml: { 12089 serialize: "xsi:type" 12090 } 12091 } 12092 ] 12093 }, 12094 { 12095 name: "LinkEventDefinition", 12096 superClass: [ 12097 "EventDefinition" 12098 ], 12099 properties: [ 12100 { 12101 name: "name", 12102 isAttr: true, 12103 type: "String" 12104 }, 12105 { 12106 name: "target", 12107 type: "LinkEventDefinition", 12108 isAttr: true, 12109 isReference: true 12110 }, 12111 { 12112 name: "source", 12113 type: "LinkEventDefinition", 12114 isMany: true, 12115 isReference: true 12116 } 12117 ] 12118 }, 12119 { 12120 name: "MessageEventDefinition", 12121 superClass: [ 12122 "EventDefinition" 12123 ], 12124 properties: [ 12125 { 12126 name: "messageRef", 12127 type: "Message", 12128 isAttr: true, 12129 isReference: true 12130 }, 12131 { 12132 name: "operationRef", 12133 type: "Operation", 12134 isAttr: true, 12135 isReference: true 12136 } 12137 ] 12138 }, 12139 { 12140 name: "ConditionalEventDefinition", 12141 superClass: [ 12142 "EventDefinition" 12143 ], 12144 properties: [ 12145 { 12146 name: "condition", 12147 type: "Expression", 12148 xml: { 12149 serialize: "xsi:type" 12150 } 12151 } 12152 ] 12153 }, 12154 { 12155 name: "SignalEventDefinition", 12156 superClass: [ 12157 "EventDefinition" 12158 ], 12159 properties: [ 12160 { 12161 name: "signalRef", 12162 type: "Signal", 12163 isAttr: true, 12164 isReference: true 12165 } 12166 ] 12167 }, 12168 { 12169 name: "Signal", 12170 superClass: [ 12171 "RootElement" 12172 ], 12173 properties: [ 12174 { 12175 name: "structureRef", 12176 type: "ItemDefinition", 12177 isAttr: true, 12178 isReference: true 12179 }, 12180 { 12181 name: "name", 12182 isAttr: true, 12183 type: "String" 12184 } 12185 ] 12186 }, 12187 { 12188 name: "ImplicitThrowEvent", 12189 superClass: [ 12190 "ThrowEvent" 12191 ] 12192 }, 12193 { 12194 name: "DataState", 12195 superClass: [ 12196 "BaseElement" 12197 ], 12198 properties: [ 12199 { 12200 name: "name", 12201 isAttr: true, 12202 type: "String" 12203 } 12204 ] 12205 }, 12206 { 12207 name: "ItemAwareElement", 12208 superClass: [ 12209 "BaseElement" 12210 ], 12211 properties: [ 12212 { 12213 name: "itemSubjectRef", 12214 type: "ItemDefinition", 12215 isAttr: true, 12216 isReference: true 12217 }, 12218 { 12219 name: "dataState", 12220 type: "DataState" 12221 } 12222 ] 12223 }, 12224 { 12225 name: "DataAssociation", 12226 superClass: [ 12227 "BaseElement" 12228 ], 12229 properties: [ 12230 { 12231 name: "sourceRef", 12232 type: "ItemAwareElement", 12233 isMany: true, 12234 isReference: true 12235 }, 12236 { 12237 name: "targetRef", 12238 type: "ItemAwareElement", 12239 isReference: true 12240 }, 12241 { 12242 name: "transformation", 12243 type: "FormalExpression", 12244 xml: { 12245 serialize: "property" 12246 } 12247 }, 12248 { 12249 name: "assignment", 12250 type: "Assignment", 12251 isMany: true 12252 } 12253 ] 12254 }, 12255 { 12256 name: "DataInput", 12257 superClass: [ 12258 "ItemAwareElement" 12259 ], 12260 properties: [ 12261 { 12262 name: "name", 12263 isAttr: true, 12264 type: "String" 12265 }, 12266 { 12267 name: "isCollection", 12268 "default": false, 12269 isAttr: true, 12270 type: "Boolean" 12271 }, 12272 { 12273 name: "inputSetRef", 12274 type: "InputSet", 12275 isMany: true, 12276 isVirtual: true, 12277 isReference: true 12278 }, 12279 { 12280 name: "inputSetWithOptional", 12281 type: "InputSet", 12282 isMany: true, 12283 isVirtual: true, 12284 isReference: true 12285 }, 12286 { 12287 name: "inputSetWithWhileExecuting", 12288 type: "InputSet", 12289 isMany: true, 12290 isVirtual: true, 12291 isReference: true 12292 } 12293 ] 12294 }, 12295 { 12296 name: "DataOutput", 12297 superClass: [ 12298 "ItemAwareElement" 12299 ], 12300 properties: [ 12301 { 12302 name: "name", 12303 isAttr: true, 12304 type: "String" 12305 }, 12306 { 12307 name: "isCollection", 12308 "default": false, 12309 isAttr: true, 12310 type: "Boolean" 12311 }, 12312 { 12313 name: "outputSetRef", 12314 type: "OutputSet", 12315 isMany: true, 12316 isVirtual: true, 12317 isReference: true 12318 }, 12319 { 12320 name: "outputSetWithOptional", 12321 type: "OutputSet", 12322 isMany: true, 12323 isVirtual: true, 12324 isReference: true 12325 }, 12326 { 12327 name: "outputSetWithWhileExecuting", 12328 type: "OutputSet", 12329 isMany: true, 12330 isVirtual: true, 12331 isReference: true 12332 } 12333 ] 12334 }, 12335 { 12336 name: "InputSet", 12337 superClass: [ 12338 "BaseElement" 12339 ], 12340 properties: [ 12341 { 12342 name: "name", 12343 isAttr: true, 12344 type: "String" 12345 }, 12346 { 12347 name: "dataInputRefs", 12348 type: "DataInput", 12349 isMany: true, 12350 isReference: true 12351 }, 12352 { 12353 name: "optionalInputRefs", 12354 type: "DataInput", 12355 isMany: true, 12356 isReference: true 12357 }, 12358 { 12359 name: "whileExecutingInputRefs", 12360 type: "DataInput", 12361 isMany: true, 12362 isReference: true 12363 }, 12364 { 12365 name: "outputSetRefs", 12366 type: "OutputSet", 12367 isMany: true, 12368 isReference: true 12369 } 12370 ] 12371 }, 12372 { 12373 name: "OutputSet", 12374 superClass: [ 12375 "BaseElement" 12376 ], 12377 properties: [ 12378 { 12379 name: "dataOutputRefs", 12380 type: "DataOutput", 12381 isMany: true, 12382 isReference: true 12383 }, 12384 { 12385 name: "name", 12386 isAttr: true, 12387 type: "String" 12388 }, 12389 { 12390 name: "inputSetRefs", 12391 type: "InputSet", 12392 isMany: true, 12393 isReference: true 12394 }, 12395 { 12396 name: "optionalOutputRefs", 12397 type: "DataOutput", 12398 isMany: true, 12399 isReference: true 12400 }, 12401 { 12402 name: "whileExecutingOutputRefs", 12403 type: "DataOutput", 12404 isMany: true, 12405 isReference: true 12406 } 12407 ] 12408 }, 12409 { 12410 name: "Property", 12411 superClass: [ 12412 "ItemAwareElement" 12413 ], 12414 properties: [ 12415 { 12416 name: "name", 12417 isAttr: true, 12418 type: "String" 12419 } 12420 ] 12421 }, 12422 { 12423 name: "DataInputAssociation", 12424 superClass: [ 12425 "DataAssociation" 12426 ] 12427 }, 12428 { 12429 name: "DataOutputAssociation", 12430 superClass: [ 12431 "DataAssociation" 12432 ] 12433 }, 12434 { 12435 name: "InputOutputSpecification", 12436 superClass: [ 12437 "BaseElement" 12438 ], 12439 properties: [ 12440 { 12441 name: "dataInputs", 12442 type: "DataInput", 12443 isMany: true 12444 }, 12445 { 12446 name: "dataOutputs", 12447 type: "DataOutput", 12448 isMany: true 12449 }, 12450 { 12451 name: "inputSets", 12452 type: "InputSet", 12453 isMany: true 12454 }, 12455 { 12456 name: "outputSets", 12457 type: "OutputSet", 12458 isMany: true 12459 } 12460 ] 12461 }, 12462 { 12463 name: "DataObject", 12464 superClass: [ 12465 "FlowElement", 12466 "ItemAwareElement" 12467 ], 12468 properties: [ 12469 { 12470 name: "isCollection", 12471 "default": false, 12472 isAttr: true, 12473 type: "Boolean" 12474 } 12475 ] 12476 }, 12477 { 12478 name: "InputOutputBinding", 12479 properties: [ 12480 { 12481 name: "inputDataRef", 12482 type: "InputSet", 12483 isAttr: true, 12484 isReference: true 12485 }, 12486 { 12487 name: "outputDataRef", 12488 type: "OutputSet", 12489 isAttr: true, 12490 isReference: true 12491 }, 12492 { 12493 name: "operationRef", 12494 type: "Operation", 12495 isAttr: true, 12496 isReference: true 12497 } 12498 ] 12499 }, 12500 { 12501 name: "Assignment", 12502 superClass: [ 12503 "BaseElement" 12504 ], 12505 properties: [ 12506 { 12507 name: "from", 12508 type: "Expression", 12509 xml: { 12510 serialize: "xsi:type" 12511 } 12512 }, 12513 { 12514 name: "to", 12515 type: "Expression", 12516 xml: { 12517 serialize: "xsi:type" 12518 } 12519 } 12520 ] 12521 }, 12522 { 12523 name: "DataStore", 12524 superClass: [ 12525 "RootElement", 12526 "ItemAwareElement" 12527 ], 12528 properties: [ 12529 { 12530 name: "name", 12531 isAttr: true, 12532 type: "String" 12533 }, 12534 { 12535 name: "capacity", 12536 isAttr: true, 12537 type: "Integer" 12538 }, 12539 { 12540 name: "isUnlimited", 12541 "default": true, 12542 isAttr: true, 12543 type: "Boolean" 12544 } 12545 ] 12546 }, 12547 { 12548 name: "DataStoreReference", 12549 superClass: [ 12550 "ItemAwareElement", 12551 "FlowElement" 12552 ], 12553 properties: [ 12554 { 12555 name: "dataStoreRef", 12556 type: "DataStore", 12557 isAttr: true, 12558 isReference: true 12559 } 12560 ] 12561 }, 12562 { 12563 name: "DataObjectReference", 12564 superClass: [ 12565 "ItemAwareElement", 12566 "FlowElement" 12567 ], 12568 properties: [ 12569 { 12570 name: "dataObjectRef", 12571 type: "DataObject", 12572 isAttr: true, 12573 isReference: true 12574 } 12575 ] 12576 }, 12577 { 12578 name: "ConversationLink", 12579 superClass: [ 12580 "BaseElement" 12581 ], 12582 properties: [ 12583 { 12584 name: "sourceRef", 12585 type: "InteractionNode", 12586 isAttr: true, 12587 isReference: true 12588 }, 12589 { 12590 name: "targetRef", 12591 type: "InteractionNode", 12592 isAttr: true, 12593 isReference: true 12594 }, 12595 { 12596 name: "name", 12597 isAttr: true, 12598 type: "String" 12599 } 12600 ] 12601 }, 12602 { 12603 name: "ConversationAssociation", 12604 superClass: [ 12605 "BaseElement" 12606 ], 12607 properties: [ 12608 { 12609 name: "innerConversationNodeRef", 12610 type: "ConversationNode", 12611 isAttr: true, 12612 isReference: true 12613 }, 12614 { 12615 name: "outerConversationNodeRef", 12616 type: "ConversationNode", 12617 isAttr: true, 12618 isReference: true 12619 } 12620 ] 12621 }, 12622 { 12623 name: "CallConversation", 12624 superClass: [ 12625 "ConversationNode" 12626 ], 12627 properties: [ 12628 { 12629 name: "calledCollaborationRef", 12630 type: "Collaboration", 12631 isAttr: true, 12632 isReference: true 12633 }, 12634 { 12635 name: "participantAssociations", 12636 type: "ParticipantAssociation", 12637 isMany: true 12638 } 12639 ] 12640 }, 12641 { 12642 name: "Conversation", 12643 superClass: [ 12644 "ConversationNode" 12645 ] 12646 }, 12647 { 12648 name: "SubConversation", 12649 superClass: [ 12650 "ConversationNode" 12651 ], 12652 properties: [ 12653 { 12654 name: "conversationNodes", 12655 type: "ConversationNode", 12656 isMany: true 12657 } 12658 ] 12659 }, 12660 { 12661 name: "ConversationNode", 12662 isAbstract: true, 12663 superClass: [ 12664 "InteractionNode", 12665 "BaseElement" 12666 ], 12667 properties: [ 12668 { 12669 name: "name", 12670 isAttr: true, 12671 type: "String" 12672 }, 12673 { 12674 name: "participantRef", 12675 type: "Participant", 12676 isMany: true, 12677 isReference: true 12678 }, 12679 { 12680 name: "messageFlowRefs", 12681 type: "MessageFlow", 12682 isMany: true, 12683 isReference: true 12684 }, 12685 { 12686 name: "correlationKeys", 12687 type: "CorrelationKey", 12688 isMany: true 12689 } 12690 ] 12691 }, 12692 { 12693 name: "GlobalConversation", 12694 superClass: [ 12695 "Collaboration" 12696 ] 12697 }, 12698 { 12699 name: "PartnerEntity", 12700 superClass: [ 12701 "RootElement" 12702 ], 12703 properties: [ 12704 { 12705 name: "name", 12706 isAttr: true, 12707 type: "String" 12708 }, 12709 { 12710 name: "participantRef", 12711 type: "Participant", 12712 isMany: true, 12713 isReference: true 12714 } 12715 ] 12716 }, 12717 { 12718 name: "PartnerRole", 12719 superClass: [ 12720 "RootElement" 12721 ], 12722 properties: [ 12723 { 12724 name: "name", 12725 isAttr: true, 12726 type: "String" 12727 }, 12728 { 12729 name: "participantRef", 12730 type: "Participant", 12731 isMany: true, 12732 isReference: true 12733 } 12734 ] 12735 }, 12736 { 12737 name: "CorrelationProperty", 12738 superClass: [ 12739 "RootElement" 12740 ], 12741 properties: [ 12742 { 12743 name: "correlationPropertyRetrievalExpression", 12744 type: "CorrelationPropertyRetrievalExpression", 12745 isMany: true 12746 }, 12747 { 12748 name: "name", 12749 isAttr: true, 12750 type: "String" 12751 }, 12752 { 12753 name: "type", 12754 type: "ItemDefinition", 12755 isAttr: true, 12756 isReference: true 12757 } 12758 ] 12759 }, 12760 { 12761 name: "Error", 12762 superClass: [ 12763 "RootElement" 12764 ], 12765 properties: [ 12766 { 12767 name: "structureRef", 12768 type: "ItemDefinition", 12769 isAttr: true, 12770 isReference: true 12771 }, 12772 { 12773 name: "name", 12774 isAttr: true, 12775 type: "String" 12776 }, 12777 { 12778 name: "errorCode", 12779 isAttr: true, 12780 type: "String" 12781 } 12782 ] 12783 }, 12784 { 12785 name: "CorrelationKey", 12786 superClass: [ 12787 "BaseElement" 12788 ], 12789 properties: [ 12790 { 12791 name: "correlationPropertyRef", 12792 type: "CorrelationProperty", 12793 isMany: true, 12794 isReference: true 12795 }, 12796 { 12797 name: "name", 12798 isAttr: true, 12799 type: "String" 12800 } 12801 ] 12802 }, 12803 { 12804 name: "Expression", 12805 superClass: [ 12806 "BaseElement" 12807 ], 12808 isAbstract: false, 12809 properties: [ 12810 { 12811 name: "body", 12812 isBody: true, 12813 type: "String" 12814 } 12815 ] 12816 }, 12817 { 12818 name: "FormalExpression", 12819 superClass: [ 12820 "Expression" 12821 ], 12822 properties: [ 12823 { 12824 name: "language", 12825 isAttr: true, 12826 type: "String" 12827 }, 12828 { 12829 name: "evaluatesToTypeRef", 12830 type: "ItemDefinition", 12831 isAttr: true, 12832 isReference: true 12833 } 12834 ] 12835 }, 12836 { 12837 name: "Message", 12838 superClass: [ 12839 "RootElement" 12840 ], 12841 properties: [ 12842 { 12843 name: "name", 12844 isAttr: true, 12845 type: "String" 12846 }, 12847 { 12848 name: "itemRef", 12849 type: "ItemDefinition", 12850 isAttr: true, 12851 isReference: true 12852 } 12853 ] 12854 }, 12855 { 12856 name: "ItemDefinition", 12857 superClass: [ 12858 "RootElement" 12859 ], 12860 properties: [ 12861 { 12862 name: "itemKind", 12863 type: "ItemKind", 12864 isAttr: true 12865 }, 12866 { 12867 name: "structureRef", 12868 isAttr: true, 12869 type: "String" 12870 }, 12871 { 12872 name: "isCollection", 12873 "default": false, 12874 isAttr: true, 12875 type: "Boolean" 12876 }, 12877 { 12878 name: "import", 12879 type: "Import", 12880 isAttr: true, 12881 isReference: true 12882 } 12883 ] 12884 }, 12885 { 12886 name: "FlowElement", 12887 isAbstract: true, 12888 superClass: [ 12889 "BaseElement" 12890 ], 12891 properties: [ 12892 { 12893 name: "name", 12894 isAttr: true, 12895 type: "String" 12896 }, 12897 { 12898 name: "auditing", 12899 type: "Auditing" 12900 }, 12901 { 12902 name: "monitoring", 12903 type: "Monitoring" 12904 }, 12905 { 12906 name: "categoryValueRef", 12907 type: "CategoryValue", 12908 isMany: true, 12909 isReference: true 12910 } 12911 ] 12912 }, 12913 { 12914 name: "SequenceFlow", 12915 superClass: [ 12916 "FlowElement" 12917 ], 12918 properties: [ 12919 { 12920 name: "isImmediate", 12921 isAttr: true, 12922 type: "Boolean" 12923 }, 12924 { 12925 name: "conditionExpression", 12926 type: "Expression", 12927 xml: { 12928 serialize: "xsi:type" 12929 } 12930 }, 12931 { 12932 name: "sourceRef", 12933 type: "FlowNode", 12934 isAttr: true, 12935 isReference: true 12936 }, 12937 { 12938 name: "targetRef", 12939 type: "FlowNode", 12940 isAttr: true, 12941 isReference: true 12942 } 12943 ] 12944 }, 12945 { 12946 name: "FlowElementsContainer", 12947 isAbstract: true, 12948 superClass: [ 12949 "BaseElement" 12950 ], 12951 properties: [ 12952 { 12953 name: "laneSets", 12954 type: "LaneSet", 12955 isMany: true 12956 }, 12957 { 12958 name: "flowElements", 12959 type: "FlowElement", 12960 isMany: true 12961 } 12962 ] 12963 }, 12964 { 12965 name: "CallableElement", 12966 isAbstract: true, 12967 superClass: [ 12968 "RootElement" 12969 ], 12970 properties: [ 12971 { 12972 name: "name", 12973 isAttr: true, 12974 type: "String" 12975 }, 12976 { 12977 name: "ioSpecification", 12978 type: "InputOutputSpecification", 12979 xml: { 12980 serialize: "property" 12981 } 12982 }, 12983 { 12984 name: "supportedInterfaceRef", 12985 type: "Interface", 12986 isMany: true, 12987 isReference: true 12988 }, 12989 { 12990 name: "ioBinding", 12991 type: "InputOutputBinding", 12992 isMany: true, 12993 xml: { 12994 serialize: "property" 12995 } 12996 } 12997 ] 12998 }, 12999 { 13000 name: "FlowNode", 13001 isAbstract: true, 13002 superClass: [ 13003 "FlowElement" 13004 ], 13005 properties: [ 13006 { 13007 name: "incoming", 13008 type: "SequenceFlow", 13009 isMany: true, 13010 isReference: true 13011 }, 13012 { 13013 name: "outgoing", 13014 type: "SequenceFlow", 13015 isMany: true, 13016 isReference: true 13017 }, 13018 { 13019 name: "lanes", 13020 type: "Lane", 13021 isMany: true, 13022 isVirtual: true, 13023 isReference: true 13024 } 13025 ] 13026 }, 13027 { 13028 name: "CorrelationPropertyRetrievalExpression", 13029 superClass: [ 13030 "BaseElement" 13031 ], 13032 properties: [ 13033 { 13034 name: "messagePath", 13035 type: "FormalExpression" 13036 }, 13037 { 13038 name: "messageRef", 13039 type: "Message", 13040 isAttr: true, 13041 isReference: true 13042 } 13043 ] 13044 }, 13045 { 13046 name: "CorrelationPropertyBinding", 13047 superClass: [ 13048 "BaseElement" 13049 ], 13050 properties: [ 13051 { 13052 name: "dataPath", 13053 type: "FormalExpression" 13054 }, 13055 { 13056 name: "correlationPropertyRef", 13057 type: "CorrelationProperty", 13058 isAttr: true, 13059 isReference: true 13060 } 13061 ] 13062 }, 13063 { 13064 name: "Resource", 13065 superClass: [ 13066 "RootElement" 13067 ], 13068 properties: [ 13069 { 13070 name: "name", 13071 isAttr: true, 13072 type: "String" 13073 }, 13074 { 13075 name: "resourceParameters", 13076 type: "ResourceParameter", 13077 isMany: true 13078 } 13079 ] 13080 }, 13081 { 13082 name: "ResourceParameter", 13083 superClass: [ 13084 "BaseElement" 13085 ], 13086 properties: [ 13087 { 13088 name: "name", 13089 isAttr: true, 13090 type: "String" 13091 }, 13092 { 13093 name: "isRequired", 13094 isAttr: true, 13095 type: "Boolean" 13096 }, 13097 { 13098 name: "type", 13099 type: "ItemDefinition", 13100 isAttr: true, 13101 isReference: true 13102 } 13103 ] 13104 }, 13105 { 13106 name: "CorrelationSubscription", 13107 superClass: [ 13108 "BaseElement" 13109 ], 13110 properties: [ 13111 { 13112 name: "correlationKeyRef", 13113 type: "CorrelationKey", 13114 isAttr: true, 13115 isReference: true 13116 }, 13117 { 13118 name: "correlationPropertyBinding", 13119 type: "CorrelationPropertyBinding", 13120 isMany: true 13121 } 13122 ] 13123 }, 13124 { 13125 name: "MessageFlow", 13126 superClass: [ 13127 "BaseElement" 13128 ], 13129 properties: [ 13130 { 13131 name: "name", 13132 isAttr: true, 13133 type: "String" 13134 }, 13135 { 13136 name: "sourceRef", 13137 type: "InteractionNode", 13138 isAttr: true, 13139 isReference: true 13140 }, 13141 { 13142 name: "targetRef", 13143 type: "InteractionNode", 13144 isAttr: true, 13145 isReference: true 13146 }, 13147 { 13148 name: "messageRef", 13149 type: "Message", 13150 isAttr: true, 13151 isReference: true 13152 } 13153 ] 13154 }, 13155 { 13156 name: "MessageFlowAssociation", 13157 superClass: [ 13158 "BaseElement" 13159 ], 13160 properties: [ 13161 { 13162 name: "innerMessageFlowRef", 13163 type: "MessageFlow", 13164 isAttr: true, 13165 isReference: true 13166 }, 13167 { 13168 name: "outerMessageFlowRef", 13169 type: "MessageFlow", 13170 isAttr: true, 13171 isReference: true 13172 } 13173 ] 13174 }, 13175 { 13176 name: "InteractionNode", 13177 isAbstract: true, 13178 properties: [ 13179 { 13180 name: "incomingConversationLinks", 13181 type: "ConversationLink", 13182 isMany: true, 13183 isVirtual: true, 13184 isReference: true 13185 }, 13186 { 13187 name: "outgoingConversationLinks", 13188 type: "ConversationLink", 13189 isMany: true, 13190 isVirtual: true, 13191 isReference: true 13192 } 13193 ] 13194 }, 13195 { 13196 name: "Participant", 13197 superClass: [ 13198 "InteractionNode", 13199 "BaseElement" 13200 ], 13201 properties: [ 13202 { 13203 name: "name", 13204 isAttr: true, 13205 type: "String" 13206 }, 13207 { 13208 name: "interfaceRef", 13209 type: "Interface", 13210 isMany: true, 13211 isReference: true 13212 }, 13213 { 13214 name: "participantMultiplicity", 13215 type: "ParticipantMultiplicity" 13216 }, 13217 { 13218 name: "endPointRefs", 13219 type: "EndPoint", 13220 isMany: true, 13221 isReference: true 13222 }, 13223 { 13224 name: "processRef", 13225 type: "Process", 13226 isAttr: true, 13227 isReference: true 13228 } 13229 ] 13230 }, 13231 { 13232 name: "ParticipantAssociation", 13233 superClass: [ 13234 "BaseElement" 13235 ], 13236 properties: [ 13237 { 13238 name: "innerParticipantRef", 13239 type: "Participant", 13240 isAttr: true, 13241 isReference: true 13242 }, 13243 { 13244 name: "outerParticipantRef", 13245 type: "Participant", 13246 isAttr: true, 13247 isReference: true 13248 } 13249 ] 13250 }, 13251 { 13252 name: "ParticipantMultiplicity", 13253 properties: [ 13254 { 13255 name: "minimum", 13256 "default": 0, 13257 isAttr: true, 13258 type: "Integer" 13259 }, 13260 { 13261 name: "maximum", 13262 "default": 1, 13263 isAttr: true, 13264 type: "Integer" 13265 } 13266 ], 13267 superClass: [ 13268 "BaseElement" 13269 ] 13270 }, 13271 { 13272 name: "Collaboration", 13273 superClass: [ 13274 "RootElement" 13275 ], 13276 properties: [ 13277 { 13278 name: "name", 13279 isAttr: true, 13280 type: "String" 13281 }, 13282 { 13283 name: "isClosed", 13284 isAttr: true, 13285 type: "Boolean" 13286 }, 13287 { 13288 name: "participants", 13289 type: "Participant", 13290 isMany: true 13291 }, 13292 { 13293 name: "messageFlows", 13294 type: "MessageFlow", 13295 isMany: true 13296 }, 13297 { 13298 name: "artifacts", 13299 type: "Artifact", 13300 isMany: true 13301 }, 13302 { 13303 name: "conversations", 13304 type: "ConversationNode", 13305 isMany: true 13306 }, 13307 { 13308 name: "conversationAssociations", 13309 type: "ConversationAssociation" 13310 }, 13311 { 13312 name: "participantAssociations", 13313 type: "ParticipantAssociation", 13314 isMany: true 13315 }, 13316 { 13317 name: "messageFlowAssociations", 13318 type: "MessageFlowAssociation", 13319 isMany: true 13320 }, 13321 { 13322 name: "correlationKeys", 13323 type: "CorrelationKey", 13324 isMany: true 13325 }, 13326 { 13327 name: "choreographyRef", 13328 type: "Choreography", 13329 isMany: true, 13330 isReference: true 13331 }, 13332 { 13333 name: "conversationLinks", 13334 type: "ConversationLink", 13335 isMany: true 13336 } 13337 ] 13338 }, 13339 { 13340 name: "ChoreographyActivity", 13341 isAbstract: true, 13342 superClass: [ 13343 "FlowNode" 13344 ], 13345 properties: [ 13346 { 13347 name: "participantRef", 13348 type: "Participant", 13349 isMany: true, 13350 isReference: true 13351 }, 13352 { 13353 name: "initiatingParticipantRef", 13354 type: "Participant", 13355 isAttr: true, 13356 isReference: true 13357 }, 13358 { 13359 name: "correlationKeys", 13360 type: "CorrelationKey", 13361 isMany: true 13362 }, 13363 { 13364 name: "loopType", 13365 type: "ChoreographyLoopType", 13366 "default": "None", 13367 isAttr: true 13368 } 13369 ] 13370 }, 13371 { 13372 name: "CallChoreography", 13373 superClass: [ 13374 "ChoreographyActivity" 13375 ], 13376 properties: [ 13377 { 13378 name: "calledChoreographyRef", 13379 type: "Choreography", 13380 isAttr: true, 13381 isReference: true 13382 }, 13383 { 13384 name: "participantAssociations", 13385 type: "ParticipantAssociation", 13386 isMany: true 13387 } 13388 ] 13389 }, 13390 { 13391 name: "SubChoreography", 13392 superClass: [ 13393 "ChoreographyActivity", 13394 "FlowElementsContainer" 13395 ], 13396 properties: [ 13397 { 13398 name: "artifacts", 13399 type: "Artifact", 13400 isMany: true 13401 } 13402 ] 13403 }, 13404 { 13405 name: "ChoreographyTask", 13406 superClass: [ 13407 "ChoreographyActivity" 13408 ], 13409 properties: [ 13410 { 13411 name: "messageFlowRef", 13412 type: "MessageFlow", 13413 isMany: true, 13414 isReference: true 13415 } 13416 ] 13417 }, 13418 { 13419 name: "Choreography", 13420 superClass: [ 13421 "Collaboration", 13422 "FlowElementsContainer" 13423 ] 13424 }, 13425 { 13426 name: "GlobalChoreographyTask", 13427 superClass: [ 13428 "Choreography" 13429 ], 13430 properties: [ 13431 { 13432 name: "initiatingParticipantRef", 13433 type: "Participant", 13434 isAttr: true, 13435 isReference: true 13436 } 13437 ] 13438 }, 13439 { 13440 name: "TextAnnotation", 13441 superClass: [ 13442 "Artifact" 13443 ], 13444 properties: [ 13445 { 13446 name: "text", 13447 type: "String" 13448 }, 13449 { 13450 name: "textFormat", 13451 "default": "text/plain", 13452 isAttr: true, 13453 type: "String" 13454 } 13455 ] 13456 }, 13457 { 13458 name: "Group", 13459 superClass: [ 13460 "Artifact" 13461 ], 13462 properties: [ 13463 { 13464 name: "categoryValueRef", 13465 type: "CategoryValue", 13466 isAttr: true, 13467 isReference: true 13468 } 13469 ] 13470 }, 13471 { 13472 name: "Association", 13473 superClass: [ 13474 "Artifact" 13475 ], 13476 properties: [ 13477 { 13478 name: "associationDirection", 13479 type: "AssociationDirection", 13480 isAttr: true 13481 }, 13482 { 13483 name: "sourceRef", 13484 type: "BaseElement", 13485 isAttr: true, 13486 isReference: true 13487 }, 13488 { 13489 name: "targetRef", 13490 type: "BaseElement", 13491 isAttr: true, 13492 isReference: true 13493 } 13494 ] 13495 }, 13496 { 13497 name: "Category", 13498 superClass: [ 13499 "RootElement" 13500 ], 13501 properties: [ 13502 { 13503 name: "categoryValue", 13504 type: "CategoryValue", 13505 isMany: true 13506 }, 13507 { 13508 name: "name", 13509 isAttr: true, 13510 type: "String" 13511 } 13512 ] 13513 }, 13514 { 13515 name: "Artifact", 13516 isAbstract: true, 13517 superClass: [ 13518 "BaseElement" 13519 ] 13520 }, 13521 { 13522 name: "CategoryValue", 13523 superClass: [ 13524 "BaseElement" 13525 ], 13526 properties: [ 13527 { 13528 name: "categorizedFlowElements", 13529 type: "FlowElement", 13530 isMany: true, 13531 isVirtual: true, 13532 isReference: true 13533 }, 13534 { 13535 name: "value", 13536 isAttr: true, 13537 type: "String" 13538 } 13539 ] 13540 }, 13541 { 13542 name: "Activity", 13543 isAbstract: true, 13544 superClass: [ 13545 "FlowNode" 13546 ], 13547 properties: [ 13548 { 13549 name: "isForCompensation", 13550 "default": false, 13551 isAttr: true, 13552 type: "Boolean" 13553 }, 13554 { 13555 name: "default", 13556 type: "SequenceFlow", 13557 isAttr: true, 13558 isReference: true 13559 }, 13560 { 13561 name: "ioSpecification", 13562 type: "InputOutputSpecification", 13563 xml: { 13564 serialize: "property" 13565 } 13566 }, 13567 { 13568 name: "boundaryEventRefs", 13569 type: "BoundaryEvent", 13570 isMany: true, 13571 isReference: true 13572 }, 13573 { 13574 name: "properties", 13575 type: "Property", 13576 isMany: true 13577 }, 13578 { 13579 name: "dataInputAssociations", 13580 type: "DataInputAssociation", 13581 isMany: true 13582 }, 13583 { 13584 name: "dataOutputAssociations", 13585 type: "DataOutputAssociation", 13586 isMany: true 13587 }, 13588 { 13589 name: "startQuantity", 13590 "default": 1, 13591 isAttr: true, 13592 type: "Integer" 13593 }, 13594 { 13595 name: "resources", 13596 type: "ResourceRole", 13597 isMany: true 13598 }, 13599 { 13600 name: "completionQuantity", 13601 "default": 1, 13602 isAttr: true, 13603 type: "Integer" 13604 }, 13605 { 13606 name: "loopCharacteristics", 13607 type: "LoopCharacteristics" 13608 } 13609 ] 13610 }, 13611 { 13612 name: "ServiceTask", 13613 superClass: [ 13614 "Task" 13615 ], 13616 properties: [ 13617 { 13618 name: "implementation", 13619 isAttr: true, 13620 type: "String" 13621 }, 13622 { 13623 name: "operationRef", 13624 type: "Operation", 13625 isAttr: true, 13626 isReference: true 13627 } 13628 ] 13629 }, 13630 { 13631 name: "SubProcess", 13632 superClass: [ 13633 "Activity", 13634 "FlowElementsContainer", 13635 "InteractionNode" 13636 ], 13637 properties: [ 13638 { 13639 name: "triggeredByEvent", 13640 "default": false, 13641 isAttr: true, 13642 type: "Boolean" 13643 }, 13644 { 13645 name: "artifacts", 13646 type: "Artifact", 13647 isMany: true 13648 } 13649 ] 13650 }, 13651 { 13652 name: "LoopCharacteristics", 13653 isAbstract: true, 13654 superClass: [ 13655 "BaseElement" 13656 ] 13657 }, 13658 { 13659 name: "MultiInstanceLoopCharacteristics", 13660 superClass: [ 13661 "LoopCharacteristics" 13662 ], 13663 properties: [ 13664 { 13665 name: "isSequential", 13666 "default": false, 13667 isAttr: true, 13668 type: "Boolean" 13669 }, 13670 { 13671 name: "behavior", 13672 type: "MultiInstanceBehavior", 13673 "default": "All", 13674 isAttr: true 13675 }, 13676 { 13677 name: "loopCardinality", 13678 type: "Expression", 13679 xml: { 13680 serialize: "xsi:type" 13681 } 13682 }, 13683 { 13684 name: "loopDataInputRef", 13685 type: "ItemAwareElement", 13686 isReference: true 13687 }, 13688 { 13689 name: "loopDataOutputRef", 13690 type: "ItemAwareElement", 13691 isReference: true 13692 }, 13693 { 13694 name: "inputDataItem", 13695 type: "DataInput", 13696 xml: { 13697 serialize: "property" 13698 } 13699 }, 13700 { 13701 name: "outputDataItem", 13702 type: "DataOutput", 13703 xml: { 13704 serialize: "property" 13705 } 13706 }, 13707 { 13708 name: "complexBehaviorDefinition", 13709 type: "ComplexBehaviorDefinition", 13710 isMany: true 13711 }, 13712 { 13713 name: "completionCondition", 13714 type: "Expression", 13715 xml: { 13716 serialize: "xsi:type" 13717 } 13718 }, 13719 { 13720 name: "oneBehaviorEventRef", 13721 type: "EventDefinition", 13722 isAttr: true, 13723 isReference: true 13724 }, 13725 { 13726 name: "noneBehaviorEventRef", 13727 type: "EventDefinition", 13728 isAttr: true, 13729 isReference: true 13730 } 13731 ] 13732 }, 13733 { 13734 name: "StandardLoopCharacteristics", 13735 superClass: [ 13736 "LoopCharacteristics" 13737 ], 13738 properties: [ 13739 { 13740 name: "testBefore", 13741 "default": false, 13742 isAttr: true, 13743 type: "Boolean" 13744 }, 13745 { 13746 name: "loopCondition", 13747 type: "Expression", 13748 xml: { 13749 serialize: "xsi:type" 13750 } 13751 }, 13752 { 13753 name: "loopMaximum", 13754 type: "Integer", 13755 isAttr: true 13756 } 13757 ] 13758 }, 13759 { 13760 name: "CallActivity", 13761 superClass: [ 13762 "Activity", 13763 "InteractionNode" 13764 ], 13765 properties: [ 13766 { 13767 name: "calledElement", 13768 type: "String", 13769 isAttr: true 13770 } 13771 ] 13772 }, 13773 { 13774 name: "Task", 13775 superClass: [ 13776 "Activity", 13777 "InteractionNode" 13778 ] 13779 }, 13780 { 13781 name: "SendTask", 13782 superClass: [ 13783 "Task" 13784 ], 13785 properties: [ 13786 { 13787 name: "implementation", 13788 isAttr: true, 13789 type: "String" 13790 }, 13791 { 13792 name: "operationRef", 13793 type: "Operation", 13794 isAttr: true, 13795 isReference: true 13796 }, 13797 { 13798 name: "messageRef", 13799 type: "Message", 13800 isAttr: true, 13801 isReference: true 13802 } 13803 ] 13804 }, 13805 { 13806 name: "ReceiveTask", 13807 superClass: [ 13808 "Task" 13809 ], 13810 properties: [ 13811 { 13812 name: "implementation", 13813 isAttr: true, 13814 type: "String" 13815 }, 13816 { 13817 name: "instantiate", 13818 "default": false, 13819 isAttr: true, 13820 type: "Boolean" 13821 }, 13822 { 13823 name: "operationRef", 13824 type: "Operation", 13825 isAttr: true, 13826 isReference: true 13827 }, 13828 { 13829 name: "messageRef", 13830 type: "Message", 13831 isAttr: true, 13832 isReference: true 13833 } 13834 ] 13835 }, 13836 { 13837 name: "ScriptTask", 13838 superClass: [ 13839 "Task" 13840 ], 13841 properties: [ 13842 { 13843 name: "scriptFormat", 13844 isAttr: true, 13845 type: "String" 13846 }, 13847 { 13848 name: "script", 13849 type: "String" 13850 } 13851 ] 13852 }, 13853 { 13854 name: "BusinessRuleTask", 13855 superClass: [ 13856 "Task" 13857 ], 13858 properties: [ 13859 { 13860 name: "implementation", 13861 isAttr: true, 13862 type: "String" 13863 } 13864 ] 13865 }, 13866 { 13867 name: "AdHocSubProcess", 13868 superClass: [ 13869 "SubProcess" 13870 ], 13871 properties: [ 13872 { 13873 name: "completionCondition", 13874 type: "Expression", 13875 xml: { 13876 serialize: "xsi:type" 13877 } 13878 }, 13879 { 13880 name: "ordering", 13881 type: "AdHocOrdering", 13882 isAttr: true 13883 }, 13884 { 13885 name: "cancelRemainingInstances", 13886 "default": true, 13887 isAttr: true, 13888 type: "Boolean" 13889 } 13890 ] 13891 }, 13892 { 13893 name: "Transaction", 13894 superClass: [ 13895 "SubProcess" 13896 ], 13897 properties: [ 13898 { 13899 name: "protocol", 13900 isAttr: true, 13901 type: "String" 13902 }, 13903 { 13904 name: "method", 13905 isAttr: true, 13906 type: "String" 13907 } 13908 ] 13909 }, 13910 { 13911 name: "GlobalScriptTask", 13912 superClass: [ 13913 "GlobalTask" 13914 ], 13915 properties: [ 13916 { 13917 name: "scriptLanguage", 13918 isAttr: true, 13919 type: "String" 13920 }, 13921 { 13922 name: "script", 13923 isAttr: true, 13924 type: "String" 13925 } 13926 ] 13927 }, 13928 { 13929 name: "GlobalBusinessRuleTask", 13930 superClass: [ 13931 "GlobalTask" 13932 ], 13933 properties: [ 13934 { 13935 name: "implementation", 13936 isAttr: true, 13937 type: "String" 13938 } 13939 ] 13940 }, 13941 { 13942 name: "ComplexBehaviorDefinition", 13943 superClass: [ 13944 "BaseElement" 13945 ], 13946 properties: [ 13947 { 13948 name: "condition", 13949 type: "FormalExpression" 13950 }, 13951 { 13952 name: "event", 13953 type: "ImplicitThrowEvent" 13954 } 13955 ] 13956 }, 13957 { 13958 name: "ResourceRole", 13959 superClass: [ 13960 "BaseElement" 13961 ], 13962 properties: [ 13963 { 13964 name: "resourceRef", 13965 type: "Resource", 13966 isReference: true 13967 }, 13968 { 13969 name: "resourceParameterBindings", 13970 type: "ResourceParameterBinding", 13971 isMany: true 13972 }, 13973 { 13974 name: "resourceAssignmentExpression", 13975 type: "ResourceAssignmentExpression" 13976 }, 13977 { 13978 name: "name", 13979 isAttr: true, 13980 type: "String" 13981 } 13982 ] 13983 }, 13984 { 13985 name: "ResourceParameterBinding", 13986 properties: [ 13987 { 13988 name: "expression", 13989 type: "Expression", 13990 xml: { 13991 serialize: "xsi:type" 13992 } 13993 }, 13994 { 13995 name: "parameterRef", 13996 type: "ResourceParameter", 13997 isAttr: true, 13998 isReference: true 13999 } 14000 ], 14001 superClass: [ 14002 "BaseElement" 14003 ] 14004 }, 14005 { 14006 name: "ResourceAssignmentExpression", 14007 properties: [ 14008 { 14009 name: "expression", 14010 type: "Expression", 14011 xml: { 14012 serialize: "xsi:type" 14013 } 14014 } 14015 ], 14016 superClass: [ 14017 "BaseElement" 14018 ] 14019 }, 14020 { 14021 name: "Import", 14022 properties: [ 14023 { 14024 name: "importType", 14025 isAttr: true, 14026 type: "String" 14027 }, 14028 { 14029 name: "location", 14030 isAttr: true, 14031 type: "String" 14032 }, 14033 { 14034 name: "namespace", 14035 isAttr: true, 14036 type: "String" 14037 } 14038 ] 14039 }, 14040 { 14041 name: "Definitions", 14042 superClass: [ 14043 "BaseElement" 14044 ], 14045 properties: [ 14046 { 14047 name: "name", 14048 isAttr: true, 14049 type: "String" 14050 }, 14051 { 14052 name: "targetNamespace", 14053 isAttr: true, 14054 type: "String" 14055 }, 14056 { 14057 name: "expressionLanguage", 14058 "default": "http://www.w3.org/1999/XPath", 14059 isAttr: true, 14060 type: "String" 14061 }, 14062 { 14063 name: "typeLanguage", 14064 "default": "http://www.w3.org/2001/XMLSchema", 14065 isAttr: true, 14066 type: "String" 14067 }, 14068 { 14069 name: "imports", 14070 type: "Import", 14071 isMany: true 14072 }, 14073 { 14074 name: "extensions", 14075 type: "Extension", 14076 isMany: true 14077 }, 14078 { 14079 name: "rootElements", 14080 type: "RootElement", 14081 isMany: true 14082 }, 14083 { 14084 name: "diagrams", 14085 isMany: true, 14086 type: "bpmndi:BPMNDiagram" 14087 }, 14088 { 14089 name: "exporter", 14090 isAttr: true, 14091 type: "String" 14092 }, 14093 { 14094 name: "relationships", 14095 type: "Relationship", 14096 isMany: true 14097 }, 14098 { 14099 name: "exporterVersion", 14100 isAttr: true, 14101 type: "String" 14102 } 14103 ] 14104 } 14105 ]; 14106 var enumerations = [ 14107 { 14108 name: "ProcessType", 14109 literalValues: [ 14110 { 14111 name: "None" 14112 }, 14113 { 14114 name: "Public" 14115 }, 14116 { 14117 name: "Private" 14118 } 14119 ] 14120 }, 14121 { 14122 name: "GatewayDirection", 14123 literalValues: [ 14124 { 14125 name: "Unspecified" 14126 }, 14127 { 14128 name: "Converging" 14129 }, 14130 { 14131 name: "Diverging" 14132 }, 14133 { 14134 name: "Mixed" 14135 } 14136 ] 14137 }, 14138 { 14139 name: "EventBasedGatewayType", 14140 literalValues: [ 14141 { 14142 name: "Parallel" 14143 }, 14144 { 14145 name: "Exclusive" 14146 } 14147 ] 14148 }, 14149 { 14150 name: "RelationshipDirection", 14151 literalValues: [ 14152 { 14153 name: "None" 14154 }, 14155 { 14156 name: "Forward" 14157 }, 14158 { 14159 name: "Backward" 14160 }, 14161 { 14162 name: "Both" 14163 } 14164 ] 14165 }, 14166 { 14167 name: "ItemKind", 14168 literalValues: [ 14169 { 14170 name: "Physical" 14171 }, 14172 { 14173 name: "Information" 14174 } 14175 ] 14176 }, 14177 { 14178 name: "ChoreographyLoopType", 14179 literalValues: [ 14180 { 14181 name: "None" 14182 }, 14183 { 14184 name: "Standard" 14185 }, 14186 { 14187 name: "MultiInstanceSequential" 14188 }, 14189 { 14190 name: "MultiInstanceParallel" 14191 } 14192 ] 14193 }, 14194 { 14195 name: "AssociationDirection", 14196 literalValues: [ 14197 { 14198 name: "None" 14199 }, 14200 { 14201 name: "One" 14202 }, 14203 { 14204 name: "Both" 14205 } 14206 ] 14207 }, 14208 { 14209 name: "MultiInstanceBehavior", 14210 literalValues: [ 14211 { 14212 name: "None" 14213 }, 14214 { 14215 name: "One" 14216 }, 14217 { 14218 name: "All" 14219 }, 14220 { 14221 name: "Complex" 14222 } 14223 ] 14224 }, 14225 { 14226 name: "AdHocOrdering", 14227 literalValues: [ 14228 { 14229 name: "Parallel" 14230 }, 14231 { 14232 name: "Sequential" 14233 } 14234 ] 14235 } 14236 ]; 14237 var xml = { 14238 tagAlias: "lowerCase", 14239 typePrefix: "t" 14240 }; 14241 var BpmnPackage = { 14242 name: name, 14243 uri: uri, 14244 prefix: prefix, 14245 associations: associations, 14246 types: types, 14247 enumerations: enumerations, 14248 xml: xml 14249 }; 14250 14251 var name$1 = "BPMNDI"; 14252 var uri$1 = "http://www.omg.org/spec/BPMN/20100524/DI"; 14253 var prefix$1 = "bpmndi"; 14254 var types$1 = [ 14255 { 14256 name: "BPMNDiagram", 14257 properties: [ 14258 { 14259 name: "plane", 14260 type: "BPMNPlane", 14261 redefines: "di:Diagram#rootElement" 14262 }, 14263 { 14264 name: "labelStyle", 14265 type: "BPMNLabelStyle", 14266 isMany: true 14267 } 14268 ], 14269 superClass: [ 14270 "di:Diagram" 14271 ] 14272 }, 14273 { 14274 name: "BPMNPlane", 14275 properties: [ 14276 { 14277 name: "bpmnElement", 14278 isAttr: true, 14279 isReference: true, 14280 type: "bpmn:BaseElement", 14281 redefines: "di:DiagramElement#modelElement" 14282 } 14283 ], 14284 superClass: [ 14285 "di:Plane" 14286 ] 14287 }, 14288 { 14289 name: "BPMNShape", 14290 properties: [ 14291 { 14292 name: "bpmnElement", 14293 isAttr: true, 14294 isReference: true, 14295 type: "bpmn:BaseElement", 14296 redefines: "di:DiagramElement#modelElement" 14297 }, 14298 { 14299 name: "isHorizontal", 14300 isAttr: true, 14301 type: "Boolean" 14302 }, 14303 { 14304 name: "isExpanded", 14305 isAttr: true, 14306 type: "Boolean" 14307 }, 14308 { 14309 name: "isMarkerVisible", 14310 isAttr: true, 14311 type: "Boolean" 14312 }, 14313 { 14314 name: "label", 14315 type: "BPMNLabel" 14316 }, 14317 { 14318 name: "isMessageVisible", 14319 isAttr: true, 14320 type: "Boolean" 14321 }, 14322 { 14323 name: "participantBandKind", 14324 type: "ParticipantBandKind", 14325 isAttr: true 14326 }, 14327 { 14328 name: "choreographyActivityShape", 14329 type: "BPMNShape", 14330 isAttr: true, 14331 isReference: true 14332 } 14333 ], 14334 superClass: [ 14335 "di:LabeledShape" 14336 ] 14337 }, 14338 { 14339 name: "BPMNEdge", 14340 properties: [ 14341 { 14342 name: "label", 14343 type: "BPMNLabel" 14344 }, 14345 { 14346 name: "bpmnElement", 14347 isAttr: true, 14348 isReference: true, 14349 type: "bpmn:BaseElement", 14350 redefines: "di:DiagramElement#modelElement" 14351 }, 14352 { 14353 name: "sourceElement", 14354 isAttr: true, 14355 isReference: true, 14356 type: "di:DiagramElement", 14357 redefines: "di:Edge#source" 14358 }, 14359 { 14360 name: "targetElement", 14361 isAttr: true, 14362 isReference: true, 14363 type: "di:DiagramElement", 14364 redefines: "di:Edge#target" 14365 }, 14366 { 14367 name: "messageVisibleKind", 14368 type: "MessageVisibleKind", 14369 isAttr: true, 14370 "default": "initiating" 14371 } 14372 ], 14373 superClass: [ 14374 "di:LabeledEdge" 14375 ] 14376 }, 14377 { 14378 name: "BPMNLabel", 14379 properties: [ 14380 { 14381 name: "labelStyle", 14382 type: "BPMNLabelStyle", 14383 isAttr: true, 14384 isReference: true, 14385 redefines: "di:DiagramElement#style" 14386 } 14387 ], 14388 superClass: [ 14389 "di:Label" 14390 ] 14391 }, 14392 { 14393 name: "BPMNLabelStyle", 14394 properties: [ 14395 { 14396 name: "font", 14397 type: "dc:Font" 14398 } 14399 ], 14400 superClass: [ 14401 "di:Style" 14402 ] 14403 } 14404 ]; 14405 var enumerations$1 = [ 14406 { 14407 name: "ParticipantBandKind", 14408 literalValues: [ 14409 { 14410 name: "top_initiating" 14411 }, 14412 { 14413 name: "middle_initiating" 14414 }, 14415 { 14416 name: "bottom_initiating" 14417 }, 14418 { 14419 name: "top_non_initiating" 14420 }, 14421 { 14422 name: "middle_non_initiating" 14423 }, 14424 { 14425 name: "bottom_non_initiating" 14426 } 14427 ] 14428 }, 14429 { 14430 name: "MessageVisibleKind", 14431 literalValues: [ 14432 { 14433 name: "initiating" 14434 }, 14435 { 14436 name: "non_initiating" 14437 } 14438 ] 14439 } 14440 ]; 14441 var associations$1 = [ 14442 ]; 14443 var BpmnDiPackage = { 14444 name: name$1, 14445 uri: uri$1, 14446 prefix: prefix$1, 14447 types: types$1, 14448 enumerations: enumerations$1, 14449 associations: associations$1 14450 }; 14451 14452 var name$2 = "DC"; 14453 var uri$2 = "http://www.omg.org/spec/DD/20100524/DC"; 14454 var prefix$2 = "dc"; 14455 var types$2 = [ 14456 { 14457 name: "Boolean" 14458 }, 14459 { 14460 name: "Integer" 14461 }, 14462 { 14463 name: "Real" 14464 }, 14465 { 14466 name: "String" 14467 }, 14468 { 14469 name: "Font", 14470 properties: [ 14471 { 14472 name: "name", 14473 type: "String", 14474 isAttr: true 14475 }, 14476 { 14477 name: "size", 14478 type: "Real", 14479 isAttr: true 14480 }, 14481 { 14482 name: "isBold", 14483 type: "Boolean", 14484 isAttr: true 14485 }, 14486 { 14487 name: "isItalic", 14488 type: "Boolean", 14489 isAttr: true 14490 }, 14491 { 14492 name: "isUnderline", 14493 type: "Boolean", 14494 isAttr: true 14495 }, 14496 { 14497 name: "isStrikeThrough", 14498 type: "Boolean", 14499 isAttr: true 14500 } 14501 ] 14502 }, 14503 { 14504 name: "Point", 14505 properties: [ 14506 { 14507 name: "x", 14508 type: "Real", 14509 "default": "0", 14510 isAttr: true 14511 }, 14512 { 14513 name: "y", 14514 type: "Real", 14515 "default": "0", 14516 isAttr: true 14517 } 14518 ] 14519 }, 14520 { 14521 name: "Bounds", 14522 properties: [ 14523 { 14524 name: "x", 14525 type: "Real", 14526 "default": "0", 14527 isAttr: true 14528 }, 14529 { 14530 name: "y", 14531 type: "Real", 14532 "default": "0", 14533 isAttr: true 14534 }, 14535 { 14536 name: "width", 14537 type: "Real", 14538 isAttr: true 14539 }, 14540 { 14541 name: "height", 14542 type: "Real", 14543 isAttr: true 14544 } 14545 ] 14546 } 14547 ]; 14548 var associations$2 = [ 14549 ]; 14550 var DcPackage = { 14551 name: name$2, 14552 uri: uri$2, 14553 prefix: prefix$2, 14554 types: types$2, 14555 associations: associations$2 14556 }; 14557 14558 var name$3 = "DI"; 14559 var uri$3 = "http://www.omg.org/spec/DD/20100524/DI"; 14560 var prefix$3 = "di"; 14561 var types$3 = [ 14562 { 14563 name: "DiagramElement", 14564 isAbstract: true, 14565 properties: [ 14566 { 14567 name: "id", 14568 isAttr: true, 14569 isId: true, 14570 type: "String" 14571 }, 14572 { 14573 name: "extension", 14574 type: "Extension" 14575 }, 14576 { 14577 name: "owningDiagram", 14578 type: "Diagram", 14579 isReadOnly: true, 14580 isVirtual: true, 14581 isReference: true 14582 }, 14583 { 14584 name: "owningElement", 14585 type: "DiagramElement", 14586 isReadOnly: true, 14587 isVirtual: true, 14588 isReference: true 14589 }, 14590 { 14591 name: "modelElement", 14592 isReadOnly: true, 14593 isVirtual: true, 14594 isReference: true, 14595 type: "Element" 14596 }, 14597 { 14598 name: "style", 14599 type: "Style", 14600 isReadOnly: true, 14601 isVirtual: true, 14602 isReference: true 14603 }, 14604 { 14605 name: "ownedElement", 14606 type: "DiagramElement", 14607 isReadOnly: true, 14608 isMany: true, 14609 isVirtual: true 14610 } 14611 ] 14612 }, 14613 { 14614 name: "Node", 14615 isAbstract: true, 14616 superClass: [ 14617 "DiagramElement" 14618 ] 14619 }, 14620 { 14621 name: "Edge", 14622 isAbstract: true, 14623 superClass: [ 14624 "DiagramElement" 14625 ], 14626 properties: [ 14627 { 14628 name: "source", 14629 type: "DiagramElement", 14630 isReadOnly: true, 14631 isVirtual: true, 14632 isReference: true 14633 }, 14634 { 14635 name: "target", 14636 type: "DiagramElement", 14637 isReadOnly: true, 14638 isVirtual: true, 14639 isReference: true 14640 }, 14641 { 14642 name: "waypoint", 14643 isUnique: false, 14644 isMany: true, 14645 type: "dc:Point", 14646 xml: { 14647 serialize: "xsi:type" 14648 } 14649 } 14650 ] 14651 }, 14652 { 14653 name: "Diagram", 14654 isAbstract: true, 14655 properties: [ 14656 { 14657 name: "id", 14658 isAttr: true, 14659 isId: true, 14660 type: "String" 14661 }, 14662 { 14663 name: "rootElement", 14664 type: "DiagramElement", 14665 isReadOnly: true, 14666 isVirtual: true 14667 }, 14668 { 14669 name: "name", 14670 isAttr: true, 14671 type: "String" 14672 }, 14673 { 14674 name: "documentation", 14675 isAttr: true, 14676 type: "String" 14677 }, 14678 { 14679 name: "resolution", 14680 isAttr: true, 14681 type: "Real" 14682 }, 14683 { 14684 name: "ownedStyle", 14685 type: "Style", 14686 isReadOnly: true, 14687 isMany: true, 14688 isVirtual: true 14689 } 14690 ] 14691 }, 14692 { 14693 name: "Shape", 14694 isAbstract: true, 14695 superClass: [ 14696 "Node" 14697 ], 14698 properties: [ 14699 { 14700 name: "bounds", 14701 type: "dc:Bounds" 14702 } 14703 ] 14704 }, 14705 { 14706 name: "Plane", 14707 isAbstract: true, 14708 superClass: [ 14709 "Node" 14710 ], 14711 properties: [ 14712 { 14713 name: "planeElement", 14714 type: "DiagramElement", 14715 subsettedProperty: "DiagramElement-ownedElement", 14716 isMany: true 14717 } 14718 ] 14719 }, 14720 { 14721 name: "LabeledEdge", 14722 isAbstract: true, 14723 superClass: [ 14724 "Edge" 14725 ], 14726 properties: [ 14727 { 14728 name: "ownedLabel", 14729 type: "Label", 14730 isReadOnly: true, 14731 subsettedProperty: "DiagramElement-ownedElement", 14732 isMany: true, 14733 isVirtual: true 14734 } 14735 ] 14736 }, 14737 { 14738 name: "LabeledShape", 14739 isAbstract: true, 14740 superClass: [ 14741 "Shape" 14742 ], 14743 properties: [ 14744 { 14745 name: "ownedLabel", 14746 type: "Label", 14747 isReadOnly: true, 14748 subsettedProperty: "DiagramElement-ownedElement", 14749 isMany: true, 14750 isVirtual: true 14751 } 14752 ] 14753 }, 14754 { 14755 name: "Label", 14756 isAbstract: true, 14757 superClass: [ 14758 "Node" 14759 ], 14760 properties: [ 14761 { 14762 name: "bounds", 14763 type: "dc:Bounds" 14764 } 14765 ] 14766 }, 14767 { 14768 name: "Style", 14769 isAbstract: true, 14770 properties: [ 14771 { 14772 name: "id", 14773 isAttr: true, 14774 isId: true, 14775 type: "String" 14776 } 14777 ] 14778 }, 14779 { 14780 name: "Extension", 14781 properties: [ 14782 { 14783 name: "values", 14784 isMany: true, 14785 type: "Element" 14786 } 14787 ] 14788 } 14789 ]; 14790 var associations$3 = [ 14791 ]; 14792 var xml$1 = { 14793 tagAlias: "lowerCase" 14794 }; 14795 var DiPackage = { 14796 name: name$3, 14797 uri: uri$3, 14798 prefix: prefix$3, 14799 types: types$3, 14800 associations: associations$3, 14801 xml: xml$1 14802 }; 14803 14804 var name$4 = "bpmn.io colors for BPMN"; 14805 var uri$4 = "http://bpmn.io/schema/bpmn/biocolor/1.0"; 14806 var prefix$4 = "bioc"; 14807 var types$4 = [ 14808 { 14809 name: "ColoredShape", 14810 "extends": [ 14811 "bpmndi:BPMNShape" 14812 ], 14813 properties: [ 14814 { 14815 name: "stroke", 14816 isAttr: true, 14817 type: "String" 14818 }, 14819 { 14820 name: "fill", 14821 isAttr: true, 14822 type: "String" 14823 } 14824 ] 14825 }, 14826 { 14827 name: "ColoredEdge", 14828 "extends": [ 14829 "bpmndi:BPMNEdge" 14830 ], 14831 properties: [ 14832 { 14833 name: "stroke", 14834 isAttr: true, 14835 type: "String" 14836 }, 14837 { 14838 name: "fill", 14839 isAttr: true, 14840 type: "String" 14841 } 14842 ] 14843 } 14844 ]; 14845 var enumerations$2 = [ 14846 ]; 14847 var associations$4 = [ 14848 ]; 14849 var BiocPackage = { 14850 name: name$4, 14851 uri: uri$4, 14852 prefix: prefix$4, 14853 types: types$4, 14854 enumerations: enumerations$2, 14855 associations: associations$4 14856 }; 14857 14858 var name$5 = "BPMN in Color"; 14859 var uri$5 = "http://www.omg.org/spec/BPMN/non-normative/color/1.0"; 14860 var prefix$5 = "color"; 14861 var types$5 = [ 14862 { 14863 name: "ColoredLabel", 14864 "extends": [ 14865 "bpmndi:BPMNLabel" 14866 ], 14867 properties: [ 14868 { 14869 name: "color", 14870 isAttr: true, 14871 type: "String" 14872 } 14873 ] 14874 }, 14875 { 14876 name: "ColoredShape", 14877 "extends": [ 14878 "bpmndi:BPMNShape" 14879 ], 14880 properties: [ 14881 { 14882 name: "background-color", 14883 isAttr: true, 14884 type: "String" 14885 }, 14886 { 14887 name: "border-color", 14888 isAttr: true, 14889 type: "String" 14890 } 14891 ] 14892 }, 14893 { 14894 name: "ColoredEdge", 14895 "extends": [ 14896 "bpmndi:BPMNEdge" 14897 ], 14898 properties: [ 14899 { 14900 name: "border-color", 14901 isAttr: true, 14902 type: "String" 14903 } 14904 ] 14905 } 14906 ]; 14907 var enumerations$3 = [ 14908 ]; 14909 var associations$5 = [ 14910 ]; 14911 var BpmnInColorPackage = { 14912 name: name$5, 14913 uri: uri$5, 14914 prefix: prefix$5, 14915 types: types$5, 14916 enumerations: enumerations$3, 14917 associations: associations$5 14918 }; 14919 14920 var packages = { 14921 bpmn: BpmnPackage, 14922 bpmndi: BpmnDiPackage, 14923 dc: DcPackage, 14924 di: DiPackage, 14925 bioc: BiocPackage, 14926 color: BpmnInColorPackage 14927 }; 14928 14929 function simple(additionalPackages, options) { 14930 var pks = assign({}, packages, additionalPackages); 14931 14932 return new BpmnModdle(pks, options); 14933 } 14934 14935 function elementToString(e) { 14936 if (!e) { 14937 return '<null>'; 14938 } 14939 14940 return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />'; 14941 } 14942 14943 var diRefs = new Refs( 14944 { name: 'bpmnElement', enumerable: true }, 14945 { name: 'di', configurable: true } 14946 ); 14947 14948 /** 14949 * Returns true if an element has the given meta-model type 14950 * 14951 * @param {ModdleElement} element 14952 * @param {string} type 14953 * 14954 * @return {boolean} 14955 */ 14956 function is$2(element, type) { 14957 return element.$instanceOf(type); 14958 } 14959 14960 14961 /** 14962 * Find a suitable display candidate for definitions where the DI does not 14963 * correctly specify one. 14964 */ 14965 function findDisplayCandidate(definitions) { 14966 return find(definitions.rootElements, function(e) { 14967 return is$2(e, 'bpmn:Process') || is$2(e, 'bpmn:Collaboration'); 14968 }); 14969 } 14970 14971 14972 function BpmnTreeWalker(handler, translate) { 14973 14974 // list of containers already walked 14975 var handledElements = {}; 14976 14977 // list of elements to handle deferred to ensure 14978 // prerequisites are drawn 14979 var deferred = []; 14980 14981 // Helpers ////////////////////// 14982 14983 function contextual(fn, ctx) { 14984 return function(e) { 14985 fn(e, ctx); 14986 }; 14987 } 14988 14989 function handled(element) { 14990 handledElements[element.id] = element; 14991 } 14992 14993 function isHandled(element) { 14994 return handledElements[element.id]; 14995 } 14996 14997 function visit(element, ctx) { 14998 14999 var gfx = element.gfx; 15000 15001 // avoid multiple rendering of elements 15002 if (gfx) { 15003 throw new Error( 15004 translate('already rendered {element}', { element: elementToString(element) }) 15005 ); 15006 } 15007 15008 // call handler 15009 return handler.element(element, ctx); 15010 } 15011 15012 function visitRoot(element, diagram) { 15013 return handler.root(element, diagram); 15014 } 15015 15016 function visitIfDi(element, ctx) { 15017 15018 try { 15019 var gfx = element.di && visit(element, ctx); 15020 15021 handled(element); 15022 15023 return gfx; 15024 } catch (e) { 15025 logError(e.message, { element: element, error: e }); 15026 15027 console.error(translate('failed to import {element}', { element: elementToString(element) })); 15028 console.error(e); 15029 } 15030 } 15031 15032 function logError(message, context) { 15033 handler.error(message, context); 15034 } 15035 15036 // DI handling ////////////////////// 15037 15038 function registerDi(di) { 15039 var bpmnElement = di.bpmnElement; 15040 15041 if (bpmnElement) { 15042 if (bpmnElement.di) { 15043 logError( 15044 translate('multiple DI elements defined for {element}', { 15045 element: elementToString(bpmnElement) 15046 }), 15047 { element: bpmnElement } 15048 ); 15049 } else { 15050 diRefs.bind(bpmnElement, 'di'); 15051 bpmnElement.di = di; 15052 } 15053 } else { 15054 logError( 15055 translate('no bpmnElement referenced in {element}', { 15056 element: elementToString(di) 15057 }), 15058 { element: di } 15059 ); 15060 } 15061 } 15062 15063 function handleDiagram(diagram) { 15064 handlePlane(diagram.plane); 15065 } 15066 15067 function handlePlane(plane) { 15068 registerDi(plane); 15069 15070 forEach(plane.planeElement, handlePlaneElement); 15071 } 15072 15073 function handlePlaneElement(planeElement) { 15074 registerDi(planeElement); 15075 } 15076 15077 15078 // Semantic handling ////////////////////// 15079 15080 /** 15081 * Handle definitions and return the rendered diagram (if any) 15082 * 15083 * @param {ModdleElement} definitions to walk and import 15084 * @param {ModdleElement} [diagram] specific diagram to import and display 15085 * 15086 * @throws {Error} if no diagram to display could be found 15087 */ 15088 function handleDefinitions(definitions, diagram) { 15089 15090 // make sure we walk the correct bpmnElement 15091 15092 var diagrams = definitions.diagrams; 15093 15094 if (diagram && diagrams.indexOf(diagram) === -1) { 15095 throw new Error(translate('diagram not part of bpmn:Definitions')); 15096 } 15097 15098 if (!diagram && diagrams && diagrams.length) { 15099 diagram = diagrams[0]; 15100 } 15101 15102 // no diagram -> nothing to import 15103 if (!diagram) { 15104 throw new Error(translate('no diagram to display')); 15105 } 15106 15107 // load DI from selected diagram only 15108 handleDiagram(diagram); 15109 15110 15111 var plane = diagram.plane; 15112 15113 if (!plane) { 15114 throw new Error(translate( 15115 'no plane for {element}', 15116 { element: elementToString(diagram) } 15117 )); 15118 } 15119 15120 var rootElement = plane.bpmnElement; 15121 15122 // ensure we default to a suitable display candidate (process or collaboration), 15123 // even if non is specified in DI 15124 if (!rootElement) { 15125 rootElement = findDisplayCandidate(definitions); 15126 15127 if (!rootElement) { 15128 throw new Error(translate('no process or collaboration to display')); 15129 } else { 15130 15131 logError( 15132 translate('correcting missing bpmnElement on {plane} to {rootElement}', { 15133 plane: elementToString(plane), 15134 rootElement: elementToString(rootElement) 15135 }) 15136 ); 15137 15138 // correct DI on the fly 15139 plane.bpmnElement = rootElement; 15140 registerDi(plane); 15141 } 15142 } 15143 15144 15145 var ctx = visitRoot(rootElement, plane); 15146 15147 if (is$2(rootElement, 'bpmn:Process')) { 15148 handleProcess(rootElement, ctx); 15149 } else if (is$2(rootElement, 'bpmn:Collaboration')) { 15150 handleCollaboration(rootElement); 15151 15152 // force drawing of everything not yet drawn that is part of the target DI 15153 handleUnhandledProcesses(definitions.rootElements, ctx); 15154 } else { 15155 throw new Error( 15156 translate('unsupported bpmnElement for {plane}: {rootElement}', { 15157 plane: elementToString(plane), 15158 rootElement: elementToString(rootElement) 15159 }) 15160 ); 15161 } 15162 15163 // handle all deferred elements 15164 handleDeferred(); 15165 } 15166 15167 function handleDeferred() { 15168 15169 var fn; 15170 15171 // drain deferred until empty 15172 while (deferred.length) { 15173 fn = deferred.shift(); 15174 15175 fn(); 15176 } 15177 } 15178 15179 function handleProcess(process, context) { 15180 handleFlowElementsContainer(process, context); 15181 handleIoSpecification(process.ioSpecification, context); 15182 15183 handleArtifacts(process.artifacts, context); 15184 15185 // log process handled 15186 handled(process); 15187 } 15188 15189 function handleUnhandledProcesses(rootElements, ctx) { 15190 15191 // walk through all processes that have not yet been drawn and draw them 15192 // if they contain lanes with DI information. 15193 // we do this to pass the free-floating lane test cases in the MIWG test suite 15194 var processes = filter(rootElements, function(e) { 15195 return !isHandled(e) && is$2(e, 'bpmn:Process') && e.laneSets; 15196 }); 15197 15198 processes.forEach(contextual(handleProcess, ctx)); 15199 } 15200 15201 function handleMessageFlow(messageFlow, context) { 15202 visitIfDi(messageFlow, context); 15203 } 15204 15205 function handleMessageFlows(messageFlows, context) { 15206 forEach(messageFlows, contextual(handleMessageFlow, context)); 15207 } 15208 15209 function handleDataAssociation(association, context) { 15210 visitIfDi(association, context); 15211 } 15212 15213 function handleDataInput(dataInput, context) { 15214 visitIfDi(dataInput, context); 15215 } 15216 15217 function handleDataOutput(dataOutput, context) { 15218 visitIfDi(dataOutput, context); 15219 } 15220 15221 function handleArtifact(artifact, context) { 15222 15223 // bpmn:TextAnnotation 15224 // bpmn:Group 15225 // bpmn:Association 15226 15227 visitIfDi(artifact, context); 15228 } 15229 15230 function handleArtifacts(artifacts, context) { 15231 15232 forEach(artifacts, function(e) { 15233 if (is$2(e, 'bpmn:Association')) { 15234 deferred.push(function() { 15235 handleArtifact(e, context); 15236 }); 15237 } else { 15238 handleArtifact(e, context); 15239 } 15240 }); 15241 } 15242 15243 function handleIoSpecification(ioSpecification, context) { 15244 15245 if (!ioSpecification) { 15246 return; 15247 } 15248 15249 forEach(ioSpecification.dataInputs, contextual(handleDataInput, context)); 15250 forEach(ioSpecification.dataOutputs, contextual(handleDataOutput, context)); 15251 } 15252 15253 function handleSubProcess(subProcess, context) { 15254 handleFlowElementsContainer(subProcess, context); 15255 handleArtifacts(subProcess.artifacts, context); 15256 } 15257 15258 function handleFlowNode(flowNode, context) { 15259 var childCtx = visitIfDi(flowNode, context); 15260 15261 if (is$2(flowNode, 'bpmn:SubProcess')) { 15262 handleSubProcess(flowNode, childCtx || context); 15263 } 15264 15265 if (is$2(flowNode, 'bpmn:Activity')) { 15266 handleIoSpecification(flowNode.ioSpecification, context); 15267 } 15268 15269 // defer handling of associations 15270 // affected types: 15271 // 15272 // * bpmn:Activity 15273 // * bpmn:ThrowEvent 15274 // * bpmn:CatchEvent 15275 // 15276 deferred.push(function() { 15277 forEach(flowNode.dataInputAssociations, contextual(handleDataAssociation, context)); 15278 forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context)); 15279 }); 15280 } 15281 15282 function handleSequenceFlow(sequenceFlow, context) { 15283 visitIfDi(sequenceFlow, context); 15284 } 15285 15286 function handleDataElement(dataObject, context) { 15287 visitIfDi(dataObject, context); 15288 } 15289 15290 function handleLane(lane, context) { 15291 15292 deferred.push(function() { 15293 15294 var newContext = visitIfDi(lane, context); 15295 15296 if (lane.childLaneSet) { 15297 handleLaneSet(lane.childLaneSet, newContext || context); 15298 } 15299 15300 wireFlowNodeRefs(lane); 15301 }); 15302 } 15303 15304 function handleLaneSet(laneSet, context) { 15305 forEach(laneSet.lanes, contextual(handleLane, context)); 15306 } 15307 15308 function handleLaneSets(laneSets, context) { 15309 forEach(laneSets, contextual(handleLaneSet, context)); 15310 } 15311 15312 function handleFlowElementsContainer(container, context) { 15313 handleFlowElements(container.flowElements, context); 15314 15315 if (container.laneSets) { 15316 handleLaneSets(container.laneSets, context); 15317 } 15318 } 15319 15320 function handleFlowElements(flowElements, context) { 15321 forEach(flowElements, function(e) { 15322 if (is$2(e, 'bpmn:SequenceFlow')) { 15323 deferred.push(function() { 15324 handleSequenceFlow(e, context); 15325 }); 15326 } else if (is$2(e, 'bpmn:BoundaryEvent')) { 15327 deferred.unshift(function() { 15328 handleFlowNode(e, context); 15329 }); 15330 } else if (is$2(e, 'bpmn:FlowNode')) { 15331 handleFlowNode(e, context); 15332 } else if (is$2(e, 'bpmn:DataObject')) ; else if (is$2(e, 'bpmn:DataStoreReference')) { 15333 handleDataElement(e, context); 15334 } else if (is$2(e, 'bpmn:DataObjectReference')) { 15335 handleDataElement(e, context); 15336 } else { 15337 logError( 15338 translate('unrecognized flowElement {element} in context {context}', { 15339 element: elementToString(e), 15340 context: (context ? elementToString(context.businessObject) : 'null') 15341 }), 15342 { element: e, context: context } 15343 ); 15344 } 15345 }); 15346 } 15347 15348 function handleParticipant(participant, context) { 15349 var newCtx = visitIfDi(participant, context); 15350 15351 var process = participant.processRef; 15352 if (process) { 15353 handleProcess(process, newCtx || context); 15354 } 15355 } 15356 15357 function handleCollaboration(collaboration) { 15358 15359 forEach(collaboration.participants, contextual(handleParticipant)); 15360 15361 handleArtifacts(collaboration.artifacts); 15362 15363 // handle message flows latest in the process 15364 deferred.push(function() { 15365 handleMessageFlows(collaboration.messageFlows); 15366 }); 15367 } 15368 15369 15370 function wireFlowNodeRefs(lane) { 15371 15372 // wire the virtual flowNodeRefs <-> relationship 15373 forEach(lane.flowNodeRef, function(flowNode) { 15374 var lanes = flowNode.get('lanes'); 15375 15376 if (lanes) { 15377 lanes.push(lane); 15378 } 15379 }); 15380 } 15381 15382 // API ////////////////////// 15383 15384 return { 15385 handleDeferred: handleDeferred, 15386 handleDefinitions: handleDefinitions, 15387 handleSubProcess: handleSubProcess, 15388 registerDi: registerDi 15389 }; 15390 } 15391 15392 /** 15393 * The importBpmnDiagram result. 15394 * 15395 * @typedef {Object} ImportBPMNDiagramResult 15396 * 15397 * @property {Array<string>} warnings 15398 */ 15399 15400 /** 15401 * The importBpmnDiagram error. 15402 * 15403 * @typedef {Error} ImportBPMNDiagramError 15404 * 15405 * @property {Array<string>} warnings 15406 */ 15407 15408 /** 15409 * Import the definitions into a diagram. 15410 * 15411 * Errors and warnings are reported through the specified callback. 15412 * 15413 * @param {djs.Diagram} diagram 15414 * @param {ModdleElement<Definitions>} definitions 15415 * @param {ModdleElement<BPMNDiagram>} [bpmnDiagram] the diagram to be rendered 15416 * (if not provided, the first one will be rendered) 15417 * 15418 * Returns {Promise<ImportBPMNDiagramResult, ImportBPMNDiagramError>} 15419 */ 15420 function importBpmnDiagram(diagram, definitions, bpmnDiagram) { 15421 15422 var importer, 15423 eventBus, 15424 translate; 15425 15426 var error, 15427 warnings = []; 15428 15429 /** 15430 * Walk the diagram semantically, importing (=drawing) 15431 * all elements you encounter. 15432 * 15433 * @param {ModdleElement<Definitions>} definitions 15434 * @param {ModdleElement<BPMNDiagram>} bpmnDiagram 15435 */ 15436 function render(definitions, bpmnDiagram) { 15437 15438 var visitor = { 15439 15440 root: function(element) { 15441 return importer.add(element); 15442 }, 15443 15444 element: function(element, parentShape) { 15445 return importer.add(element, parentShape); 15446 }, 15447 15448 error: function(message, context) { 15449 warnings.push({ message: message, context: context }); 15450 } 15451 }; 15452 15453 var walker = new BpmnTreeWalker(visitor, translate); 15454 15455 // traverse BPMN 2.0 document model, 15456 // starting at definitions 15457 walker.handleDefinitions(definitions, bpmnDiagram); 15458 } 15459 15460 return new Promise(function(resolve, reject) { 15461 try { 15462 importer = diagram.get('bpmnImporter'); 15463 eventBus = diagram.get('eventBus'); 15464 translate = diagram.get('translate'); 15465 15466 eventBus.fire('import.render.start', { definitions: definitions }); 15467 15468 render(definitions, bpmnDiagram); 15469 15470 eventBus.fire('import.render.complete', { 15471 error: error, 15472 warnings: warnings 15473 }); 15474 15475 return resolve({ warnings: warnings }); 15476 } catch (e) { 15477 15478 e.warnings = warnings; 15479 return reject(e); 15480 } 15481 }); 15482 } 15483 15484 // TODO(nikku): remove with future bpmn-js version 15485 15486 /** 15487 * Wraps APIs to check: 15488 * 15489 * 1) If a callback is passed -> Warn users about callback deprecation. 15490 * 2) If Promise class is implemented in current environment. 15491 * 15492 * @private 15493 */ 15494 function wrapForCompatibility(api) { 15495 15496 return function() { 15497 15498 if (!window.Promise) { 15499 throw new Error('Promises is not supported in this environment. Please polyfill Promise.'); 15500 } 15501 15502 var argLen = arguments.length; 15503 if (argLen >= 1 && isFunction(arguments[argLen - 1])) { 15504 15505 var callback = arguments[argLen - 1]; 15506 15507 console.warn(new Error( 15508 'Passing callbacks to ' + api.name + ' is deprecated and will be removed in a future major release. ' + 15509 'Please switch to promises: https://bpmn.io/l/moving-to-promises.html' 15510 )); 15511 15512 var argsWithoutCallback = Array.prototype.slice.call(arguments, 0, -1); 15513 15514 api.apply(this, argsWithoutCallback).then(function(result) { 15515 15516 var firstKey = Object.keys(result)[0]; 15517 15518 // The APIs we are wrapping all resolve a single item depending on the API. 15519 // For instance, importXML resolves { warnings } and saveXML returns { xml }. 15520 // That's why we can call the callback with the first item of result. 15521 return callback(null, result[firstKey]); 15522 15523 // Passing a second paramter instead of catch because we don't want to 15524 // catch errors thrown by callback(). 15525 }, function(err) { 15526 15527 return callback(err, err.warnings); 15528 }); 15529 } else { 15530 15531 return api.apply(this, arguments); 15532 } 15533 }; 15534 } 15535 15536 /** 15537 * This file must not be changed or exchanged. 15538 * 15539 * @see http://bpmn.io/license for more information. 15540 */ 15541 15542 15543 // inlined ../../resources/logo.svg 15544 var BPMNIO_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14.02 5.57" width="53" height="21" style="vertical-align:middle"><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>'; 15545 15546 var BPMNIO_IMG = BPMNIO_LOGO_SVG; 15547 15548 function css(attrs) { 15549 return attrs.join(';'); 15550 } 15551 15552 var LINK_STYLES = css([ 15553 'color: #404040' 15554 ]); 15555 15556 var LIGHTBOX_STYLES = css([ 15557 'z-index: 1001', 15558 'position: fixed', 15559 'top: 0', 15560 'left: 0', 15561 'right: 0', 15562 'bottom: 0' 15563 ]); 15564 15565 var BACKDROP_STYLES = css([ 15566 'width: 100%', 15567 'height: 100%', 15568 'background: rgba(40,40,40,0.2)' 15569 ]); 15570 15571 var NOTICE_STYLES = css([ 15572 'position: absolute', 15573 'left: 50%', 15574 'top: 40%', 15575 'transform: translate(-50%)', 15576 'width: 260px', 15577 'padding: 10px', 15578 'background: white', 15579 'box-shadow: 0 1px 4px rgba(0,0,0,0.3)', 15580 'font-family: Helvetica, Arial, sans-serif', 15581 'font-size: 14px', 15582 'display: flex', 15583 'line-height: 1.3' 15584 ]); 15585 15586 var LIGHTBOX_MARKUP = 15587 '<div class="bjs-powered-by-lightbox" style="' + LIGHTBOX_STYLES + '">' + 15588 '<div class="backdrop" style="' + BACKDROP_STYLES + '"></div>' + 15589 '<div class="notice" style="' + NOTICE_STYLES + '">' + 15590 '<a href="https://bpmn.io" target="_blank" rel="noopener" style="margin: 15px 20px 15px 10px; align-self: center;' + LINK_STYLES + '">' + 15591 BPMNIO_IMG + 15592 '</a>' + 15593 '<span>' + 15594 'Web-based tooling for BPMN, DMN and CMMN diagrams ' + 15595 'powered by <a href="https://bpmn.io" target="_blank" rel="noopener">bpmn.io</a>.' + 15596 '</span>' + 15597 '</div>' + 15598 '</div>'; 15599 15600 15601 var lightbox; 15602 15603 function open() { 15604 15605 if (!lightbox) { 15606 lightbox = domify(LIGHTBOX_MARKUP); 15607 15608 delegate.bind(lightbox, '.backdrop', 'click', function(event) { 15609 document.body.removeChild(lightbox); 15610 }); 15611 } 15612 15613 document.body.appendChild(lightbox); 15614 } 15615 15616 /** 15617 * The code in the <project-logo></project-logo> area 15618 * must not be changed. 15619 * 15620 * @see http://bpmn.io/license for more information. 15621 */ 15622 15623 /** 15624 * A base viewer for BPMN 2.0 diagrams. 15625 * 15626 * Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for 15627 * bundles that include actual features. 15628 * 15629 * @param {Object} [options] configuration options to pass to the viewer 15630 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body. 15631 * @param {string|number} [options.width] the width of the viewer 15632 * @param {string|number} [options.height] the height of the viewer 15633 * @param {Object} [options.moddleExtensions] extension packages to provide 15634 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules 15635 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules 15636 */ 15637 function BaseViewer(options) { 15638 15639 options = assign({}, DEFAULT_OPTIONS, options); 15640 15641 this._moddle = this._createModdle(options); 15642 15643 this._container = this._createContainer(options); 15644 15645 /* <project-logo> */ 15646 15647 addProjectLogo(this._container); 15648 15649 /* </project-logo> */ 15650 15651 this._init(this._container, this._moddle, options); 15652 } 15653 15654 inherits$1(BaseViewer, Diagram); 15655 15656 /** 15657 * The importXML result. 15658 * 15659 * @typedef {Object} ImportXMLResult 15660 * 15661 * @property {Array<string>} warnings 15662 */ 15663 15664 /** 15665 * The importXML error. 15666 * 15667 * @typedef {Error} ImportXMLError 15668 * 15669 * @property {Array<string>} warnings 15670 */ 15671 15672 /** 15673 * Parse and render a BPMN 2.0 diagram. 15674 * 15675 * Once finished the viewer reports back the result to the 15676 * provided callback function with (err, warnings). 15677 * 15678 * ## Life-Cycle Events 15679 * 15680 * During import the viewer will fire life-cycle events: 15681 * 15682 * * import.parse.start (about to read model from xml) 15683 * * import.parse.complete (model read; may have worked or not) 15684 * * import.render.start (graphical import start) 15685 * * import.render.complete (graphical import finished) 15686 * * import.done (everything done) 15687 * 15688 * You can use these events to hook into the life-cycle. 15689 * 15690 * @param {string} xml the BPMN 2.0 xml 15691 * @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered) 15692 * 15693 * Returns {Promise<ImportXMLResult, ImportXMLError>} 15694 */ 15695 BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bpmnDiagram) { 15696 15697 var self = this; 15698 15699 function ParseCompleteEvent(data) { 15700 15701 var event = self.get('eventBus').createEvent(data); 15702 15703 // TODO(nikku): remove with future bpmn-js version 15704 Object.defineProperty(event, 'context', { 15705 enumerable: true, 15706 get: function() { 15707 15708 console.warn(new Error( 15709 'import.parse.complete <context> is deprecated ' + 15710 'and will be removed in future library versions' 15711 )); 15712 15713 return { 15714 warnings: data.warnings, 15715 references: data.references, 15716 elementsById: data.elementsById 15717 }; 15718 } 15719 }); 15720 15721 return event; 15722 } 15723 15724 return new Promise(function(resolve, reject) { 15725 15726 // hook in pre-parse listeners + 15727 // allow xml manipulation 15728 xml = self._emit('import.parse.start', { xml: xml }) || xml; 15729 15730 self._moddle.fromXML(xml, 'bpmn:Definitions').then(function(result) { 15731 var definitions = result.rootElement; 15732 var references = result.references; 15733 var parseWarnings = result.warnings; 15734 var elementsById = result.elementsById; 15735 15736 // hook in post parse listeners + 15737 // allow definitions manipulation 15738 definitions = self._emit('import.parse.complete', ParseCompleteEvent({ 15739 error: null, 15740 definitions: definitions, 15741 elementsById: elementsById, 15742 references: references, 15743 warnings: parseWarnings 15744 })) || definitions; 15745 15746 self.importDefinitions(definitions, bpmnDiagram).then(function(result) { 15747 var allWarnings = [].concat(parseWarnings, result.warnings || []); 15748 15749 self._emit('import.done', { error: null, warnings: allWarnings }); 15750 15751 return resolve({ warnings: allWarnings }); 15752 }).catch(function(err) { 15753 var allWarnings = [].concat(parseWarnings, err.warnings || []); 15754 15755 self._emit('import.done', { error: err, warnings: allWarnings }); 15756 15757 return reject(addWarningsToError(err, allWarnings)); 15758 }); 15759 }).catch(function(err) { 15760 15761 self._emit('import.parse.complete', { 15762 error: err 15763 }); 15764 15765 err = checkValidationError(err); 15766 15767 self._emit('import.done', { error: err, warnings: err.warnings }); 15768 15769 return reject(err); 15770 }); 15771 }); 15772 }); 15773 15774 /** 15775 * The importDefinitions result. 15776 * 15777 * @typedef {Object} ImportDefinitionsResult 15778 * 15779 * @property {Array<string>} warnings 15780 */ 15781 15782 /** 15783 * The importDefinitions error. 15784 * 15785 * @typedef {Error} ImportDefinitionsError 15786 * 15787 * @property {Array<string>} warnings 15788 */ 15789 15790 /** 15791 * Import parsed definitions and render a BPMN 2.0 diagram. 15792 * 15793 * Once finished the viewer reports back the result to the 15794 * provided callback function with (err, warnings). 15795 * 15796 * ## Life-Cycle Events 15797 * 15798 * During import the viewer will fire life-cycle events: 15799 * 15800 * * import.render.start (graphical import start) 15801 * * import.render.complete (graphical import finished) 15802 * 15803 * You can use these events to hook into the life-cycle. 15804 * 15805 * @param {ModdleElement<Definitions>} definitions parsed BPMN 2.0 definitions 15806 * @param {ModdleElement<BPMNDiagram>|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered) 15807 * 15808 * Returns {Promise<ImportDefinitionsResult, ImportDefinitionsError>} 15809 */ 15810 BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDefinitions(definitions, bpmnDiagram) { 15811 15812 var self = this; 15813 15814 return new Promise(function(resolve, reject) { 15815 15816 self._setDefinitions(definitions); 15817 15818 self.open(bpmnDiagram).then(function(result) { 15819 15820 var warnings = result.warnings; 15821 15822 return resolve({ warnings: warnings }); 15823 }).catch(function(err) { 15824 15825 return reject(err); 15826 }); 15827 }); 15828 }); 15829 15830 /** 15831 * The open result. 15832 * 15833 * @typedef {Object} OpenResult 15834 * 15835 * @property {Array<string>} warnings 15836 */ 15837 15838 /** 15839 * The open error. 15840 * 15841 * @typedef {Error} OpenError 15842 * 15843 * @property {Array<string>} warnings 15844 */ 15845 15846 /** 15847 * Open diagram of previously imported XML. 15848 * 15849 * Once finished the viewer reports back the result to the 15850 * provided callback function with (err, warnings). 15851 * 15852 * ## Life-Cycle Events 15853 * 15854 * During switch the viewer will fire life-cycle events: 15855 * 15856 * * import.render.start (graphical import start) 15857 * * import.render.complete (graphical import finished) 15858 * 15859 * You can use these events to hook into the life-cycle. 15860 * 15861 * @param {string|ModdleElement<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open 15862 * 15863 * Returns {Promise<OpenResult, OpenError>} 15864 */ 15865 BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId) { 15866 15867 var definitions = this._definitions; 15868 var bpmnDiagram = bpmnDiagramOrId; 15869 15870 var self = this; 15871 15872 return new Promise(function(resolve, reject) { 15873 if (!definitions) { 15874 var err1 = new Error('no XML imported'); 15875 15876 return reject(addWarningsToError(err1, [])); 15877 } 15878 15879 if (typeof bpmnDiagramOrId === 'string') { 15880 bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId); 15881 15882 if (!bpmnDiagram) { 15883 var err2 = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found'); 15884 15885 return reject(addWarningsToError(err2, [])); 15886 } 15887 } 15888 15889 // clear existing rendered diagram 15890 // catch synchronous exceptions during #clear() 15891 try { 15892 self.clear(); 15893 } catch (error) { 15894 15895 return reject(addWarningsToError(error, [])); 15896 } 15897 15898 // perform graphical import 15899 importBpmnDiagram(self, definitions, bpmnDiagram).then(function(result) { 15900 15901 var warnings = result.warnings; 15902 15903 return resolve({ warnings: warnings }); 15904 }).catch(function(err) { 15905 15906 return reject(err); 15907 }); 15908 }); 15909 }); 15910 15911 /** 15912 * The saveXML result. 15913 * 15914 * @typedef {Object} SaveXMLResult 15915 * 15916 * @property {string} xml 15917 */ 15918 15919 /** 15920 * Export the currently displayed BPMN 2.0 diagram as 15921 * a BPMN 2.0 XML document. 15922 * 15923 * ## Life-Cycle Events 15924 * 15925 * During XML saving the viewer will fire life-cycle events: 15926 * 15927 * * saveXML.start (before serialization) 15928 * * saveXML.serialized (after xml generation) 15929 * * saveXML.done (everything done) 15930 * 15931 * You can use these events to hook into the life-cycle. 15932 * 15933 * @param {Object} [options] export options 15934 * @param {boolean} [options.format=false] output formatted XML 15935 * @param {boolean} [options.preamble=true] output preamble 15936 * 15937 * Returns {Promise<SaveXMLResult, Error>} 15938 */ 15939 BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options) { 15940 15941 options = options || {}; 15942 15943 var self = this; 15944 15945 var definitions = this._definitions; 15946 15947 return new Promise(function(resolve) { 15948 15949 if (!definitions) { 15950 return resolve({ 15951 error: new Error('no definitions loaded') 15952 }); 15953 } 15954 15955 // allow to fiddle around with definitions 15956 definitions = self._emit('saveXML.start', { 15957 definitions: definitions 15958 }) || definitions; 15959 15960 self._moddle.toXML(definitions, options).then(function(result) { 15961 15962 var xml = result.xml; 15963 15964 xml = self._emit('saveXML.serialized', { 15965 xml: xml 15966 }) || xml; 15967 15968 return resolve({ 15969 xml: xml 15970 }); 15971 }); 15972 }).catch(function(error) { 15973 return { error: error }; 15974 }).then(function(result) { 15975 15976 self._emit('saveXML.done', result); 15977 15978 var error = result.error; 15979 15980 if (error) { 15981 return Promise.reject(error); 15982 } 15983 15984 return result; 15985 }); 15986 }); 15987 15988 /** 15989 * The saveSVG result. 15990 * 15991 * @typedef {Object} SaveSVGResult 15992 * 15993 * @property {string} svg 15994 */ 15995 15996 /** 15997 * Export the currently displayed BPMN 2.0 diagram as 15998 * an SVG image. 15999 * 16000 * ## Life-Cycle Events 16001 * 16002 * During SVG saving the viewer will fire life-cycle events: 16003 * 16004 * * saveSVG.start (before serialization) 16005 * * saveSVG.done (everything done) 16006 * 16007 * You can use these events to hook into the life-cycle. 16008 * 16009 * @param {Object} [options] 16010 * 16011 * Returns {Promise<SaveSVGResult, Error>} 16012 */ 16013 BaseViewer.prototype.saveSVG = wrapForCompatibility(function saveSVG(options) { 16014 16015 var self = this; 16016 16017 return new Promise(function(resolve, reject) { 16018 16019 self._emit('saveSVG.start'); 16020 16021 var svg, err; 16022 16023 try { 16024 var canvas = self.get('canvas'); 16025 16026 var contentNode = canvas.getDefaultLayer(), 16027 defsNode = query('defs', canvas._svg); 16028 16029 var contents = innerSVG(contentNode), 16030 defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : ''; 16031 16032 var bbox = contentNode.getBBox(); 16033 16034 svg = 16035 '<?xml version="1.0" encoding="utf-8"?>\n' + 16036 '<!-- created with bpmn-js / http://bpmn.io -->\n' + 16037 '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' + 16038 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' + 16039 'width="' + bbox.width + '" height="' + bbox.height + '" ' + 16040 'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' + 16041 defs + contents + 16042 '</svg>'; 16043 } catch (e) { 16044 err = e; 16045 } 16046 16047 self._emit('saveSVG.done', { 16048 error: err, 16049 svg: svg 16050 }); 16051 16052 if (!err) { 16053 return resolve({ svg: svg }); 16054 } 16055 16056 return reject(err); 16057 }); 16058 }); 16059 16060 /** 16061 * Get a named diagram service. 16062 * 16063 * @example 16064 * 16065 * var elementRegistry = viewer.get('elementRegistry'); 16066 * var startEventShape = elementRegistry.get('StartEvent_1'); 16067 * 16068 * @param {string} name 16069 * 16070 * @return {Object} diagram service instance 16071 * 16072 * @method BaseViewer#get 16073 */ 16074 16075 /** 16076 * Invoke a function in the context of this viewer. 16077 * 16078 * @example 16079 * 16080 * viewer.invoke(function(elementRegistry) { 16081 * var startEventShape = elementRegistry.get('StartEvent_1'); 16082 * }); 16083 * 16084 * @param {Function} fn to be invoked 16085 * 16086 * @return {Object} the functions return value 16087 * 16088 * @method BaseViewer#invoke 16089 */ 16090 16091 16092 BaseViewer.prototype._setDefinitions = function(definitions) { 16093 this._definitions = definitions; 16094 }; 16095 16096 BaseViewer.prototype.getModules = function() { 16097 return this._modules; 16098 }; 16099 16100 /** 16101 * Remove all drawn elements from the viewer. 16102 * 16103 * After calling this method the viewer can still 16104 * be reused for opening another diagram. 16105 * 16106 * @method BaseViewer#clear 16107 */ 16108 BaseViewer.prototype.clear = function() { 16109 if (!this.getDefinitions()) { 16110 16111 // no diagram to clear 16112 return; 16113 } 16114 16115 // remove businessObject#di binding 16116 // 16117 // this is necessary, as we establish the bindings 16118 // in the BpmnTreeWalker (and assume none are given 16119 // on reimport) 16120 this.get('elementRegistry').forEach(function(element) { 16121 var bo = element.businessObject; 16122 16123 if (bo && bo.di) { 16124 delete bo.di; 16125 } 16126 }); 16127 16128 // remove drawn elements 16129 Diagram.prototype.clear.call(this); 16130 }; 16131 16132 /** 16133 * Destroy the viewer instance and remove all its 16134 * remainders from the document tree. 16135 */ 16136 BaseViewer.prototype.destroy = function() { 16137 16138 // diagram destroy 16139 Diagram.prototype.destroy.call(this); 16140 16141 // dom detach 16142 remove$2(this._container); 16143 }; 16144 16145 /** 16146 * Register an event listener 16147 * 16148 * Remove a previously added listener via {@link #off(event, callback)}. 16149 * 16150 * @param {string} event 16151 * @param {number} [priority] 16152 * @param {Function} callback 16153 * @param {Object} [that] 16154 */ 16155 BaseViewer.prototype.on = function(event, priority, callback, target) { 16156 return this.get('eventBus').on(event, priority, callback, target); 16157 }; 16158 16159 /** 16160 * De-register an event listener 16161 * 16162 * @param {string} event 16163 * @param {Function} callback 16164 */ 16165 BaseViewer.prototype.off = function(event, callback) { 16166 this.get('eventBus').off(event, callback); 16167 }; 16168 16169 BaseViewer.prototype.attachTo = function(parentNode) { 16170 16171 if (!parentNode) { 16172 throw new Error('parentNode required'); 16173 } 16174 16175 // ensure we detach from the 16176 // previous, old parent 16177 this.detach(); 16178 16179 // unwrap jQuery if provided 16180 if (parentNode.get && parentNode.constructor.prototype.jquery) { 16181 parentNode = parentNode.get(0); 16182 } 16183 16184 if (typeof parentNode === 'string') { 16185 parentNode = query(parentNode); 16186 } 16187 16188 parentNode.appendChild(this._container); 16189 16190 this._emit('attach', {}); 16191 16192 this.get('canvas').resized(); 16193 }; 16194 16195 BaseViewer.prototype.getDefinitions = function() { 16196 return this._definitions; 16197 }; 16198 16199 BaseViewer.prototype.detach = function() { 16200 16201 var container = this._container, 16202 parentNode = container.parentNode; 16203 16204 if (!parentNode) { 16205 return; 16206 } 16207 16208 this._emit('detach', {}); 16209 16210 parentNode.removeChild(container); 16211 }; 16212 16213 BaseViewer.prototype._init = function(container, moddle, options) { 16214 16215 var baseModules = options.modules || this.getModules(), 16216 additionalModules = options.additionalModules || [], 16217 staticModules = [ 16218 { 16219 bpmnjs: [ 'value', this ], 16220 moddle: [ 'value', moddle ] 16221 } 16222 ]; 16223 16224 var diagramModules = [].concat(staticModules, baseModules, additionalModules); 16225 16226 var diagramOptions = assign(omit(options, [ 'additionalModules' ]), { 16227 canvas: assign({}, options.canvas, { container: container }), 16228 modules: diagramModules 16229 }); 16230 16231 // invoke diagram constructor 16232 Diagram.call(this, diagramOptions); 16233 16234 if (options && options.container) { 16235 this.attachTo(options.container); 16236 } 16237 }; 16238 16239 /** 16240 * Emit an event on the underlying {@link EventBus} 16241 * 16242 * @param {string} type 16243 * @param {Object} event 16244 * 16245 * @return {Object} event processing result (if any) 16246 */ 16247 BaseViewer.prototype._emit = function(type, event) { 16248 return this.get('eventBus').fire(type, event); 16249 }; 16250 16251 BaseViewer.prototype._createContainer = function(options) { 16252 16253 var container = domify('<div class="bjs-container"></div>'); 16254 16255 assign(container.style, { 16256 width: ensureUnit(options.width), 16257 height: ensureUnit(options.height), 16258 position: options.position 16259 }); 16260 16261 return container; 16262 }; 16263 16264 BaseViewer.prototype._createModdle = function(options) { 16265 var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions); 16266 16267 return new simple(moddleOptions); 16268 }; 16269 16270 BaseViewer.prototype._modules = []; 16271 16272 // helpers /////////////// 16273 16274 function addWarningsToError(err, warningsAry) { 16275 err.warnings = warningsAry; 16276 return err; 16277 } 16278 16279 function checkValidationError(err) { 16280 16281 // check if we can help the user by indicating wrong BPMN 2.0 xml 16282 // (in case he or the exporting tool did not get that right) 16283 16284 var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/; 16285 var match = pattern.exec(err.message); 16286 16287 if (match) { 16288 err.message = 16289 'unparsable content <' + match[1] + '> detected; ' + 16290 'this may indicate an invalid BPMN 2.0 diagram file' + match[2]; 16291 } 16292 16293 return err; 16294 } 16295 16296 var DEFAULT_OPTIONS = { 16297 width: '100%', 16298 height: '100%', 16299 position: 'relative' 16300 }; 16301 16302 16303 /** 16304 * Ensure the passed argument is a proper unit (defaulting to px) 16305 */ 16306 function ensureUnit(val) { 16307 return val + (isNumber(val) ? 'px' : ''); 16308 } 16309 16310 16311 /** 16312 * Find BPMNDiagram in definitions by ID 16313 * 16314 * @param {ModdleElement<Definitions>} definitions 16315 * @param {string} diagramId 16316 * 16317 * @return {ModdleElement<BPMNDiagram>|null} 16318 */ 16319 function findBPMNDiagram(definitions, diagramId) { 16320 if (!diagramId) { 16321 return null; 16322 } 16323 16324 return find(definitions.diagrams, function(element) { 16325 return element.id === diagramId; 16326 }) || null; 16327 } 16328 16329 /** 16330 * Adds the project logo to the diagram container as 16331 * required by the bpmn.io license. 16332 * 16333 * @see http://bpmn.io/license 16334 * 16335 * @param {Element} container 16336 */ 16337 function addProjectLogo(container) { 16338 var img = BPMNIO_IMG; 16339 16340 var linkMarkup = 16341 '<a href="http://bpmn.io" ' + 16342 'target="_blank" ' + 16343 'class="bjs-powered-by" ' + 16344 'title="Powered by bpmn.io" ' + 16345 'style="position: absolute; bottom: 15px; right: 15px; z-index: 100; ' + LINK_STYLES + '">' + 16346 img + 16347 '</a>'; 16348 16349 var linkElement = domify(linkMarkup); 16350 16351 container.appendChild(linkElement); 16352 16353 componentEvent.bind(linkElement, 'click', function(event) { 16354 open(); 16355 16356 event.preventDefault(); 16357 }); 16358 } 16359 16360 /* </project-logo> */ 16361 16362 /** 16363 * A base modeler for BPMN 2.0 diagrams. 16364 * 16365 * Have a look at {@link Modeler} for a bundle that includes actual features. 16366 * 16367 * @param {Object} [options] configuration options to pass to the viewer 16368 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body. 16369 * @param {string|number} [options.width] the width of the viewer 16370 * @param {string|number} [options.height] the height of the viewer 16371 * @param {Object} [options.moddleExtensions] extension packages to provide 16372 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules 16373 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules 16374 */ 16375 function BaseModeler(options) { 16376 BaseViewer.call(this, options); 16377 16378 // hook ID collection into the modeler 16379 this.on('import.parse.complete', function(event) { 16380 if (!event.error) { 16381 this._collectIds(event.definitions, event.elementsById); 16382 } 16383 }, this); 16384 16385 this.on('diagram.destroy', function() { 16386 this.get('moddle').ids.clear(); 16387 }, this); 16388 } 16389 16390 inherits$1(BaseModeler, BaseViewer); 16391 16392 16393 /** 16394 * Create a moddle instance, attaching ids to it. 16395 * 16396 * @param {Object} options 16397 */ 16398 BaseModeler.prototype._createModdle = function(options) { 16399 var moddle = BaseViewer.prototype._createModdle.call(this, options); 16400 16401 // attach ids to moddle to be able to track 16402 // and validated ids in the BPMN 2.0 XML document 16403 // tree 16404 moddle.ids = new Ids([ 32, 36, 1 ]); 16405 16406 return moddle; 16407 }; 16408 16409 /** 16410 * Collect ids processed during parsing of the 16411 * definitions object. 16412 * 16413 * @param {ModdleElement} definitions 16414 * @param {Context} context 16415 */ 16416 BaseModeler.prototype._collectIds = function(definitions, elementsById) { 16417 16418 var moddle = definitions.$model, 16419 ids = moddle.ids, 16420 id; 16421 16422 // remove references from previous import 16423 ids.clear(); 16424 16425 for (id in elementsById) { 16426 ids.claim(id, elementsById[id]); 16427 } 16428 }; 16429 16430 /** 16431 * Is an element of the given BPMN type? 16432 * 16433 * @param {djs.model.Base|ModdleElement} element 16434 * @param {string} type 16435 * 16436 * @return {boolean} 16437 */ 16438 function is$1(element, type) { 16439 var bo = getBusinessObject(element); 16440 16441 return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type); 16442 } 16443 16444 16445 /** 16446 * Return the business object for a given element. 16447 * 16448 * @param {djs.model.Base|ModdleElement} element 16449 * 16450 * @return {ModdleElement} 16451 */ 16452 function getBusinessObject(element) { 16453 return (element && element.businessObject) || element; 16454 } 16455 16456 function isExpanded(element) { 16457 16458 if (is$1(element, 'bpmn:CallActivity')) { 16459 return false; 16460 } 16461 16462 if (is$1(element, 'bpmn:SubProcess')) { 16463 return getBusinessObject(element).di && !!getBusinessObject(element).di.isExpanded; 16464 } 16465 16466 if (is$1(element, 'bpmn:Participant')) { 16467 return !!getBusinessObject(element).processRef; 16468 } 16469 16470 return true; 16471 } 16472 16473 function isInterrupting(element) { 16474 return element && getBusinessObject(element).isInterrupting !== false; 16475 } 16476 16477 function isEventSubProcess(element) { 16478 return element && !!getBusinessObject(element).triggeredByEvent; 16479 } 16480 16481 function hasEventDefinition$2(element, eventType) { 16482 var bo = getBusinessObject(element), 16483 hasEventDefinition = false; 16484 16485 if (bo.eventDefinitions) { 16486 forEach(bo.eventDefinitions, function(event) { 16487 if (is$1(event, eventType)) { 16488 hasEventDefinition = true; 16489 } 16490 }); 16491 } 16492 16493 return hasEventDefinition; 16494 } 16495 16496 function hasErrorEventDefinition(element) { 16497 return hasEventDefinition$2(element, 'bpmn:ErrorEventDefinition'); 16498 } 16499 16500 function hasEscalationEventDefinition(element) { 16501 return hasEventDefinition$2(element, 'bpmn:EscalationEventDefinition'); 16502 } 16503 16504 function hasCompensateEventDefinition(element) { 16505 return hasEventDefinition$2(element, 'bpmn:CompensateEventDefinition'); 16506 } 16507 16508 function getLabelAttr(semantic) { 16509 if ( 16510 is$1(semantic, 'bpmn:FlowElement') || 16511 is$1(semantic, 'bpmn:Participant') || 16512 is$1(semantic, 'bpmn:Lane') || 16513 is$1(semantic, 'bpmn:SequenceFlow') || 16514 is$1(semantic, 'bpmn:MessageFlow') || 16515 is$1(semantic, 'bpmn:DataInput') || 16516 is$1(semantic, 'bpmn:DataOutput') 16517 ) { 16518 return 'name'; 16519 } 16520 16521 if (is$1(semantic, 'bpmn:TextAnnotation')) { 16522 return 'text'; 16523 } 16524 16525 if (is$1(semantic, 'bpmn:Group')) { 16526 return 'categoryValueRef'; 16527 } 16528 } 16529 16530 function getCategoryValue(semantic) { 16531 var categoryValueRef = semantic['categoryValueRef']; 16532 16533 if (!categoryValueRef) { 16534 return ''; 16535 } 16536 16537 16538 return categoryValueRef.value || ''; 16539 } 16540 16541 function getLabel(element) { 16542 var semantic = element.businessObject, 16543 attr = getLabelAttr(semantic); 16544 16545 if (attr) { 16546 16547 if (attr === 'categoryValueRef') { 16548 16549 return getCategoryValue(semantic); 16550 } 16551 16552 return semantic[attr] || ''; 16553 } 16554 } 16555 16556 16557 function setLabel(element, text, isExternal) { 16558 var semantic = element.businessObject, 16559 attr = getLabelAttr(semantic); 16560 16561 if (attr) { 16562 16563 if (attr === 'categoryValueRef') { 16564 semantic['categoryValueRef'].value = text; 16565 } else { 16566 semantic[attr] = text; 16567 } 16568 16569 } 16570 16571 return element; 16572 } 16573 16574 // element utils ////////////////////// 16575 16576 /** 16577 * Checks if eventDefinition of the given element matches with semantic type. 16578 * 16579 * @return {boolean} true if element is of the given semantic type 16580 */ 16581 function isTypedEvent(event, eventDefinitionType, filter) { 16582 16583 function matches(definition, filter) { 16584 return every(filter, function(val, key) { 16585 16586 // we want a == conversion here, to be able to catch 16587 // undefined == false and friends 16588 /* jshint -W116 */ 16589 return definition[key] == val; 16590 }); 16591 } 16592 16593 return some(event.eventDefinitions, function(definition) { 16594 return definition.$type === eventDefinitionType && matches(event, filter); 16595 }); 16596 } 16597 16598 function isThrowEvent(event) { 16599 return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent'); 16600 } 16601 16602 function isCollection(element) { 16603 var dataObject = element.dataObjectRef; 16604 16605 return element.isCollection || (dataObject && dataObject.isCollection); 16606 } 16607 16608 function getDi(element) { 16609 return element.businessObject.di; 16610 } 16611 16612 function getSemantic(element) { 16613 return element.businessObject; 16614 } 16615 16616 16617 // color access ////////////////////// 16618 16619 function getFillColor(element, defaultColor) { 16620 var di = getDi(element); 16621 16622 return di.get('color:background-color') || di.get('bioc:fill') || defaultColor || 'white'; 16623 } 16624 16625 function getStrokeColor$1(element, defaultColor) { 16626 var di = getDi(element); 16627 16628 return di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || 'black'; 16629 } 16630 16631 function getLabelColor(element, defaultColor, defaultStrokeColor) { 16632 var di = getDi(element), 16633 label = di.get('label'); 16634 16635 return label && label.get('color:color') || defaultColor || 16636 getStrokeColor$1(element, defaultStrokeColor); 16637 } 16638 16639 // cropping path customizations ////////////////////// 16640 16641 function getCirclePath(shape) { 16642 16643 var cx = shape.x + shape.width / 2, 16644 cy = shape.y + shape.height / 2, 16645 radius = shape.width / 2; 16646 16647 var circlePath = [ 16648 ['M', cx, cy], 16649 ['m', 0, -radius], 16650 ['a', radius, radius, 0, 1, 1, 0, 2 * radius], 16651 ['a', radius, radius, 0, 1, 1, 0, -2 * radius], 16652 ['z'] 16653 ]; 16654 16655 return componentsToPath(circlePath); 16656 } 16657 16658 function getRoundRectPath(shape, borderRadius) { 16659 16660 var x = shape.x, 16661 y = shape.y, 16662 width = shape.width, 16663 height = shape.height; 16664 16665 var roundRectPath = [ 16666 ['M', x + borderRadius, y], 16667 ['l', width - borderRadius * 2, 0], 16668 ['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius], 16669 ['l', 0, height - borderRadius * 2], 16670 ['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius], 16671 ['l', borderRadius * 2 - width, 0], 16672 ['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius], 16673 ['l', 0, borderRadius * 2 - height], 16674 ['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius], 16675 ['z'] 16676 ]; 16677 16678 return componentsToPath(roundRectPath); 16679 } 16680 16681 function getDiamondPath(shape) { 16682 16683 var width = shape.width, 16684 height = shape.height, 16685 x = shape.x, 16686 y = shape.y, 16687 halfWidth = width / 2, 16688 halfHeight = height / 2; 16689 16690 var diamondPath = [ 16691 ['M', x + halfWidth, y], 16692 ['l', halfWidth, halfHeight], 16693 ['l', -halfWidth, halfHeight], 16694 ['l', -halfWidth, -halfHeight], 16695 ['z'] 16696 ]; 16697 16698 return componentsToPath(diamondPath); 16699 } 16700 16701 function getRectPath(shape) { 16702 var x = shape.x, 16703 y = shape.y, 16704 width = shape.width, 16705 height = shape.height; 16706 16707 var rectPath = [ 16708 ['M', x, y], 16709 ['l', width, 0], 16710 ['l', 0, height], 16711 ['l', -width, 0], 16712 ['z'] 16713 ]; 16714 16715 return componentsToPath(rectPath); 16716 } 16717 16718 var RENDERER_IDS = new Ids(); 16719 16720 var TASK_BORDER_RADIUS = 10; 16721 var INNER_OUTER_DIST = 3; 16722 16723 var DEFAULT_FILL_OPACITY = .95, 16724 HIGH_FILL_OPACITY = .35; 16725 16726 var ELEMENT_LABEL_DISTANCE$1 = 10; 16727 16728 function BpmnRenderer( 16729 config, eventBus, styles, pathMap, 16730 canvas, textRenderer, priority) { 16731 16732 BaseRenderer.call(this, eventBus, priority); 16733 16734 var defaultFillColor = config && config.defaultFillColor, 16735 defaultStrokeColor = config && config.defaultStrokeColor, 16736 defaultLabelColor = config && config.defaultLabelColor; 16737 16738 var rendererId = RENDERER_IDS.next(); 16739 16740 var markers = {}; 16741 16742 var computeStyle = styles.computeStyle; 16743 16744 function addMarker(id, options) { 16745 var attrs = assign({ 16746 fill: 'black', 16747 strokeWidth: 1, 16748 strokeLinecap: 'round', 16749 strokeDasharray: 'none' 16750 }, options.attrs); 16751 16752 var ref = options.ref || { x: 0, y: 0 }; 16753 16754 var scale = options.scale || 1; 16755 16756 // fix for safari / chrome / firefox bug not correctly 16757 // resetting stroke dash array 16758 if (attrs.strokeDasharray === 'none') { 16759 attrs.strokeDasharray = [10000, 1]; 16760 } 16761 16762 var marker = create$1('marker'); 16763 16764 attr(options.element, attrs); 16765 16766 append(marker, options.element); 16767 16768 attr(marker, { 16769 id: id, 16770 viewBox: '0 0 20 20', 16771 refX: ref.x, 16772 refY: ref.y, 16773 markerWidth: 20 * scale, 16774 markerHeight: 20 * scale, 16775 orient: 'auto' 16776 }); 16777 16778 var defs = query('defs', canvas._svg); 16779 16780 if (!defs) { 16781 defs = create$1('defs'); 16782 16783 append(canvas._svg, defs); 16784 } 16785 16786 append(defs, marker); 16787 16788 markers[id] = marker; 16789 } 16790 16791 function colorEscape(str) { 16792 16793 // only allow characters and numbers 16794 return str.replace(/[^0-9a-zA-z]+/g, '_'); 16795 } 16796 16797 function marker(type, fill, stroke) { 16798 var id = type + '-' + colorEscape(fill) + '-' + colorEscape(stroke) + '-' + rendererId; 16799 16800 if (!markers[id]) { 16801 createMarker(id, type, fill, stroke); 16802 } 16803 16804 return 'url(#' + id + ')'; 16805 } 16806 16807 function createMarker(id, type, fill, stroke) { 16808 16809 if (type === 'sequenceflow-end') { 16810 var sequenceflowEnd = create$1('path'); 16811 attr(sequenceflowEnd, { d: 'M 1 5 L 11 10 L 1 15 Z' }); 16812 16813 addMarker(id, { 16814 element: sequenceflowEnd, 16815 ref: { x: 11, y: 10 }, 16816 scale: 0.5, 16817 attrs: { 16818 fill: stroke, 16819 stroke: stroke 16820 } 16821 }); 16822 } 16823 16824 if (type === 'messageflow-start') { 16825 var messageflowStart = create$1('circle'); 16826 attr(messageflowStart, { cx: 6, cy: 6, r: 3.5 }); 16827 16828 addMarker(id, { 16829 element: messageflowStart, 16830 attrs: { 16831 fill: fill, 16832 stroke: stroke 16833 }, 16834 ref: { x: 6, y: 6 } 16835 }); 16836 } 16837 16838 if (type === 'messageflow-end') { 16839 var messageflowEnd = create$1('path'); 16840 attr(messageflowEnd, { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z' }); 16841 16842 addMarker(id, { 16843 element: messageflowEnd, 16844 attrs: { 16845 fill: fill, 16846 stroke: stroke, 16847 strokeLinecap: 'butt' 16848 }, 16849 ref: { x: 8.5, y: 5 } 16850 }); 16851 } 16852 16853 if (type === 'association-start') { 16854 var associationStart = create$1('path'); 16855 attr(associationStart, { d: 'M 11 5 L 1 10 L 11 15' }); 16856 16857 addMarker(id, { 16858 element: associationStart, 16859 attrs: { 16860 fill: 'none', 16861 stroke: stroke, 16862 strokeWidth: 1.5 16863 }, 16864 ref: { x: 1, y: 10 }, 16865 scale: 0.5 16866 }); 16867 } 16868 16869 if (type === 'association-end') { 16870 var associationEnd = create$1('path'); 16871 attr(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' }); 16872 16873 addMarker(id, { 16874 element: associationEnd, 16875 attrs: { 16876 fill: 'none', 16877 stroke: stroke, 16878 strokeWidth: 1.5 16879 }, 16880 ref: { x: 12, y: 10 }, 16881 scale: 0.5 16882 }); 16883 } 16884 16885 if (type === 'conditional-flow-marker') { 16886 var conditionalflowMarker = create$1('path'); 16887 attr(conditionalflowMarker, { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z' }); 16888 16889 addMarker(id, { 16890 element: conditionalflowMarker, 16891 attrs: { 16892 fill: fill, 16893 stroke: stroke 16894 }, 16895 ref: { x: -1, y: 10 }, 16896 scale: 0.5 16897 }); 16898 } 16899 16900 if (type === 'conditional-default-flow-marker') { 16901 var conditionaldefaultflowMarker = create$1('path'); 16902 attr(conditionaldefaultflowMarker, { d: 'M 6 4 L 10 16' }); 16903 16904 addMarker(id, { 16905 element: conditionaldefaultflowMarker, 16906 attrs: { 16907 stroke: stroke 16908 }, 16909 ref: { x: 0, y: 10 }, 16910 scale: 0.5 16911 }); 16912 } 16913 } 16914 16915 function drawCircle(parentGfx, width, height, offset, attrs) { 16916 16917 if (isObject(offset)) { 16918 attrs = offset; 16919 offset = 0; 16920 } 16921 16922 offset = offset || 0; 16923 16924 attrs = computeStyle(attrs, { 16925 stroke: 'black', 16926 strokeWidth: 2, 16927 fill: 'white' 16928 }); 16929 16930 if (attrs.fill === 'none') { 16931 delete attrs.fillOpacity; 16932 } 16933 16934 var cx = width / 2, 16935 cy = height / 2; 16936 16937 var circle = create$1('circle'); 16938 attr(circle, { 16939 cx: cx, 16940 cy: cy, 16941 r: Math.round((width + height) / 4 - offset) 16942 }); 16943 attr(circle, attrs); 16944 16945 append(parentGfx, circle); 16946 16947 return circle; 16948 } 16949 16950 function drawRect(parentGfx, width, height, r, offset, attrs) { 16951 16952 if (isObject(offset)) { 16953 attrs = offset; 16954 offset = 0; 16955 } 16956 16957 offset = offset || 0; 16958 16959 attrs = computeStyle(attrs, { 16960 stroke: 'black', 16961 strokeWidth: 2, 16962 fill: 'white' 16963 }); 16964 16965 var rect = create$1('rect'); 16966 attr(rect, { 16967 x: offset, 16968 y: offset, 16969 width: width - offset * 2, 16970 height: height - offset * 2, 16971 rx: r, 16972 ry: r 16973 }); 16974 attr(rect, attrs); 16975 16976 append(parentGfx, rect); 16977 16978 return rect; 16979 } 16980 16981 function drawDiamond(parentGfx, width, height, attrs) { 16982 16983 var x_2 = width / 2; 16984 var y_2 = height / 2; 16985 16986 var points = [{ x: x_2, y: 0 }, { x: width, y: y_2 }, { x: x_2, y: height }, { x: 0, y: y_2 }]; 16987 16988 var pointsString = points.map(function(point) { 16989 return point.x + ',' + point.y; 16990 }).join(' '); 16991 16992 attrs = computeStyle(attrs, { 16993 stroke: 'black', 16994 strokeWidth: 2, 16995 fill: 'white' 16996 }); 16997 16998 var polygon = create$1('polygon'); 16999 attr(polygon, { 17000 points: pointsString 17001 }); 17002 attr(polygon, attrs); 17003 17004 append(parentGfx, polygon); 17005 17006 return polygon; 17007 } 17008 17009 function drawLine(parentGfx, waypoints, attrs) { 17010 attrs = computeStyle(attrs, [ 'no-fill' ], { 17011 stroke: 'black', 17012 strokeWidth: 2, 17013 fill: 'none' 17014 }); 17015 17016 var line = createLine(waypoints, attrs); 17017 17018 append(parentGfx, line); 17019 17020 return line; 17021 } 17022 17023 function drawPath(parentGfx, d, attrs) { 17024 17025 attrs = computeStyle(attrs, [ 'no-fill' ], { 17026 strokeWidth: 2, 17027 stroke: 'black' 17028 }); 17029 17030 var path = create$1('path'); 17031 attr(path, { d: d }); 17032 attr(path, attrs); 17033 17034 append(parentGfx, path); 17035 17036 return path; 17037 } 17038 17039 function drawMarker(type, parentGfx, path, attrs) { 17040 return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs)); 17041 } 17042 17043 function as(type) { 17044 return function(parentGfx, element) { 17045 return handlers[type](parentGfx, element); 17046 }; 17047 } 17048 17049 function renderer(type) { 17050 return handlers[type]; 17051 } 17052 17053 function renderEventContent(element, parentGfx) { 17054 17055 var event = getSemantic(element); 17056 var isThrowing = isThrowEvent(event); 17057 17058 if (event.eventDefinitions && event.eventDefinitions.length>1) { 17059 if (event.parallelMultiple) { 17060 return renderer('bpmn:ParallelMultipleEventDefinition')(parentGfx, element, isThrowing); 17061 } 17062 else { 17063 return renderer('bpmn:MultipleEventDefinition')(parentGfx, element, isThrowing); 17064 } 17065 } 17066 17067 if (isTypedEvent(event, 'bpmn:MessageEventDefinition')) { 17068 return renderer('bpmn:MessageEventDefinition')(parentGfx, element, isThrowing); 17069 } 17070 17071 if (isTypedEvent(event, 'bpmn:TimerEventDefinition')) { 17072 return renderer('bpmn:TimerEventDefinition')(parentGfx, element, isThrowing); 17073 } 17074 17075 if (isTypedEvent(event, 'bpmn:ConditionalEventDefinition')) { 17076 return renderer('bpmn:ConditionalEventDefinition')(parentGfx, element); 17077 } 17078 17079 if (isTypedEvent(event, 'bpmn:SignalEventDefinition')) { 17080 return renderer('bpmn:SignalEventDefinition')(parentGfx, element, isThrowing); 17081 } 17082 17083 if (isTypedEvent(event, 'bpmn:EscalationEventDefinition')) { 17084 return renderer('bpmn:EscalationEventDefinition')(parentGfx, element, isThrowing); 17085 } 17086 17087 if (isTypedEvent(event, 'bpmn:LinkEventDefinition')) { 17088 return renderer('bpmn:LinkEventDefinition')(parentGfx, element, isThrowing); 17089 } 17090 17091 if (isTypedEvent(event, 'bpmn:ErrorEventDefinition')) { 17092 return renderer('bpmn:ErrorEventDefinition')(parentGfx, element, isThrowing); 17093 } 17094 17095 if (isTypedEvent(event, 'bpmn:CancelEventDefinition')) { 17096 return renderer('bpmn:CancelEventDefinition')(parentGfx, element, isThrowing); 17097 } 17098 17099 if (isTypedEvent(event, 'bpmn:CompensateEventDefinition')) { 17100 return renderer('bpmn:CompensateEventDefinition')(parentGfx, element, isThrowing); 17101 } 17102 17103 if (isTypedEvent(event, 'bpmn:TerminateEventDefinition')) { 17104 return renderer('bpmn:TerminateEventDefinition')(parentGfx, element, isThrowing); 17105 } 17106 17107 return null; 17108 } 17109 17110 function renderLabel(parentGfx, label, options) { 17111 17112 options = assign({ 17113 size: { 17114 width: 100 17115 } 17116 }, options); 17117 17118 var text = textRenderer.createText(label || '', options); 17119 17120 classes(text).add('djs-label'); 17121 17122 append(parentGfx, text); 17123 17124 return text; 17125 } 17126 17127 function renderEmbeddedLabel(parentGfx, element, align) { 17128 var semantic = getSemantic(element); 17129 17130 return renderLabel(parentGfx, semantic.name, { 17131 box: element, 17132 align: align, 17133 padding: 5, 17134 style: { 17135 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) 17136 } 17137 }); 17138 } 17139 17140 function renderExternalLabel(parentGfx, element) { 17141 17142 var box = { 17143 width: 90, 17144 height: 30, 17145 x: element.width / 2 + element.x, 17146 y: element.height / 2 + element.y 17147 }; 17148 17149 return renderLabel(parentGfx, getLabel(element), { 17150 box: box, 17151 fitBox: true, 17152 style: assign( 17153 {}, 17154 textRenderer.getExternalStyle(), 17155 { 17156 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) 17157 } 17158 ) 17159 }); 17160 } 17161 17162 function renderLaneLabel(parentGfx, text, element) { 17163 var textBox = renderLabel(parentGfx, text, { 17164 box: { 17165 height: 30, 17166 width: element.height 17167 }, 17168 align: 'center-middle', 17169 style: { 17170 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) 17171 } 17172 }); 17173 17174 var top = -1 * element.height; 17175 17176 transform(textBox, 0, -top, 270); 17177 } 17178 17179 function createPathFromConnection(connection) { 17180 var waypoints = connection.waypoints; 17181 17182 var pathData = 'm ' + waypoints[0].x + ',' + waypoints[0].y; 17183 for (var i = 1; i < waypoints.length; i++) { 17184 pathData += 'L' + waypoints[i].x + ',' + waypoints[i].y + ' '; 17185 } 17186 return pathData; 17187 } 17188 17189 var handlers = this.handlers = { 17190 'bpmn:Event': function(parentGfx, element, attrs) { 17191 17192 if (!('fillOpacity' in attrs)) { 17193 attrs.fillOpacity = DEFAULT_FILL_OPACITY; 17194 } 17195 17196 return drawCircle(parentGfx, element.width, element.height, attrs); 17197 }, 17198 'bpmn:StartEvent': function(parentGfx, element) { 17199 var attrs = { 17200 fill: getFillColor(element, defaultFillColor), 17201 stroke: getStrokeColor$1(element, defaultStrokeColor) 17202 }; 17203 17204 var semantic = getSemantic(element); 17205 17206 if (!semantic.isInterrupting) { 17207 attrs = { 17208 strokeDasharray: '6', 17209 strokeLinecap: 'round', 17210 fill: getFillColor(element, defaultFillColor), 17211 stroke: getStrokeColor$1(element, defaultStrokeColor) 17212 }; 17213 } 17214 17215 var circle = renderer('bpmn:Event')(parentGfx, element, attrs); 17216 17217 renderEventContent(element, parentGfx); 17218 17219 return circle; 17220 }, 17221 'bpmn:MessageEventDefinition': function(parentGfx, element, isThrowing) { 17222 var pathData = pathMap.getScaledPath('EVENT_MESSAGE', { 17223 xScaleFactor: 0.9, 17224 yScaleFactor: 0.9, 17225 containerWidth: element.width, 17226 containerHeight: element.height, 17227 position: { 17228 mx: 0.235, 17229 my: 0.315 17230 } 17231 }); 17232 17233 var fill = isThrowing ? getStrokeColor$1(element, defaultStrokeColor) : getFillColor(element, defaultFillColor); 17234 var stroke = isThrowing ? getFillColor(element, defaultFillColor) : getStrokeColor$1(element, defaultStrokeColor); 17235 17236 var messagePath = drawPath(parentGfx, pathData, { 17237 strokeWidth: 1, 17238 fill: fill, 17239 stroke: stroke 17240 }); 17241 17242 return messagePath; 17243 }, 17244 'bpmn:TimerEventDefinition': function(parentGfx, element) { 17245 var circle = drawCircle(parentGfx, element.width, element.height, 0.2 * element.height, { 17246 strokeWidth: 2, 17247 fill: getFillColor(element, defaultFillColor), 17248 stroke: getStrokeColor$1(element, defaultStrokeColor) 17249 }); 17250 17251 var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', { 17252 xScaleFactor: 0.75, 17253 yScaleFactor: 0.75, 17254 containerWidth: element.width, 17255 containerHeight: element.height, 17256 position: { 17257 mx: 0.5, 17258 my: 0.5 17259 } 17260 }); 17261 17262 drawPath(parentGfx, pathData, { 17263 strokeWidth: 2, 17264 strokeLinecap: 'square', 17265 stroke: getStrokeColor$1(element, defaultStrokeColor) 17266 }); 17267 17268 for (var i = 0;i < 12; i++) { 17269 17270 var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', { 17271 xScaleFactor: 0.75, 17272 yScaleFactor: 0.75, 17273 containerWidth: element.width, 17274 containerHeight: element.height, 17275 position: { 17276 mx: 0.5, 17277 my: 0.5 17278 } 17279 }); 17280 17281 var width = element.width / 2; 17282 var height = element.height / 2; 17283 17284 drawPath(parentGfx, linePathData, { 17285 strokeWidth: 1, 17286 strokeLinecap: 'square', 17287 transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')', 17288 stroke: getStrokeColor$1(element, defaultStrokeColor) 17289 }); 17290 } 17291 17292 return circle; 17293 }, 17294 'bpmn:EscalationEventDefinition': function(parentGfx, event, isThrowing) { 17295 var pathData = pathMap.getScaledPath('EVENT_ESCALATION', { 17296 xScaleFactor: 1, 17297 yScaleFactor: 1, 17298 containerWidth: event.width, 17299 containerHeight: event.height, 17300 position: { 17301 mx: 0.5, 17302 my: 0.2 17303 } 17304 }); 17305 17306 var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none'; 17307 17308 return drawPath(parentGfx, pathData, { 17309 strokeWidth: 1, 17310 fill: fill, 17311 stroke: getStrokeColor$1(event, defaultStrokeColor) 17312 }); 17313 }, 17314 'bpmn:ConditionalEventDefinition': function(parentGfx, event) { 17315 var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', { 17316 xScaleFactor: 1, 17317 yScaleFactor: 1, 17318 containerWidth: event.width, 17319 containerHeight: event.height, 17320 position: { 17321 mx: 0.5, 17322 my: 0.222 17323 } 17324 }); 17325 17326 return drawPath(parentGfx, pathData, { 17327 strokeWidth: 1, 17328 stroke: getStrokeColor$1(event, defaultStrokeColor) 17329 }); 17330 }, 17331 'bpmn:LinkEventDefinition': function(parentGfx, event, isThrowing) { 17332 var pathData = pathMap.getScaledPath('EVENT_LINK', { 17333 xScaleFactor: 1, 17334 yScaleFactor: 1, 17335 containerWidth: event.width, 17336 containerHeight: event.height, 17337 position: { 17338 mx: 0.57, 17339 my: 0.263 17340 } 17341 }); 17342 17343 var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none'; 17344 17345 return drawPath(parentGfx, pathData, { 17346 strokeWidth: 1, 17347 fill: fill, 17348 stroke: getStrokeColor$1(event, defaultStrokeColor) 17349 }); 17350 }, 17351 'bpmn:ErrorEventDefinition': function(parentGfx, event, isThrowing) { 17352 var pathData = pathMap.getScaledPath('EVENT_ERROR', { 17353 xScaleFactor: 1.1, 17354 yScaleFactor: 1.1, 17355 containerWidth: event.width, 17356 containerHeight: event.height, 17357 position: { 17358 mx: 0.2, 17359 my: 0.722 17360 } 17361 }); 17362 17363 var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none'; 17364 17365 return drawPath(parentGfx, pathData, { 17366 strokeWidth: 1, 17367 fill: fill, 17368 stroke: getStrokeColor$1(event, defaultStrokeColor) 17369 }); 17370 }, 17371 'bpmn:CancelEventDefinition': function(parentGfx, event, isThrowing) { 17372 var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', { 17373 xScaleFactor: 1.0, 17374 yScaleFactor: 1.0, 17375 containerWidth: event.width, 17376 containerHeight: event.height, 17377 position: { 17378 mx: 0.638, 17379 my: -0.055 17380 } 17381 }); 17382 17383 var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none'; 17384 17385 var path = drawPath(parentGfx, pathData, { 17386 strokeWidth: 1, 17387 fill: fill, 17388 stroke: getStrokeColor$1(event, defaultStrokeColor) 17389 }); 17390 17391 rotate(path, 45); 17392 17393 return path; 17394 }, 17395 'bpmn:CompensateEventDefinition': function(parentGfx, event, isThrowing) { 17396 var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', { 17397 xScaleFactor: 1, 17398 yScaleFactor: 1, 17399 containerWidth: event.width, 17400 containerHeight: event.height, 17401 position: { 17402 mx: 0.22, 17403 my: 0.5 17404 } 17405 }); 17406 17407 var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none'; 17408 17409 return drawPath(parentGfx, pathData, { 17410 strokeWidth: 1, 17411 fill: fill, 17412 stroke: getStrokeColor$1(event, defaultStrokeColor) 17413 }); 17414 }, 17415 'bpmn:SignalEventDefinition': function(parentGfx, event, isThrowing) { 17416 var pathData = pathMap.getScaledPath('EVENT_SIGNAL', { 17417 xScaleFactor: 0.9, 17418 yScaleFactor: 0.9, 17419 containerWidth: event.width, 17420 containerHeight: event.height, 17421 position: { 17422 mx: 0.5, 17423 my: 0.2 17424 } 17425 }); 17426 17427 var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none'; 17428 17429 return drawPath(parentGfx, pathData, { 17430 strokeWidth: 1, 17431 fill: fill, 17432 stroke: getStrokeColor$1(event, defaultStrokeColor) 17433 }); 17434 }, 17435 'bpmn:MultipleEventDefinition': function(parentGfx, event, isThrowing) { 17436 var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', { 17437 xScaleFactor: 1.1, 17438 yScaleFactor: 1.1, 17439 containerWidth: event.width, 17440 containerHeight: event.height, 17441 position: { 17442 mx: 0.222, 17443 my: 0.36 17444 } 17445 }); 17446 17447 var fill = isThrowing ? getStrokeColor$1(event, defaultStrokeColor) : 'none'; 17448 17449 return drawPath(parentGfx, pathData, { 17450 strokeWidth: 1, 17451 fill: fill 17452 }); 17453 }, 17454 'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event) { 17455 var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', { 17456 xScaleFactor: 1.2, 17457 yScaleFactor: 1.2, 17458 containerWidth: event.width, 17459 containerHeight: event.height, 17460 position: { 17461 mx: 0.458, 17462 my: 0.194 17463 } 17464 }); 17465 17466 return drawPath(parentGfx, pathData, { 17467 strokeWidth: 1, 17468 fill: getStrokeColor$1(event, defaultStrokeColor), 17469 stroke: getStrokeColor$1(event, defaultStrokeColor) 17470 }); 17471 }, 17472 'bpmn:EndEvent': function(parentGfx, element) { 17473 var circle = renderer('bpmn:Event')(parentGfx, element, { 17474 strokeWidth: 4, 17475 fill: getFillColor(element, defaultFillColor), 17476 stroke: getStrokeColor$1(element, defaultStrokeColor) 17477 }); 17478 17479 renderEventContent(element, parentGfx); 17480 17481 return circle; 17482 }, 17483 'bpmn:TerminateEventDefinition': function(parentGfx, element) { 17484 var circle = drawCircle(parentGfx, element.width, element.height, 8, { 17485 strokeWidth: 4, 17486 fill: getStrokeColor$1(element, defaultStrokeColor), 17487 stroke: getStrokeColor$1(element, defaultStrokeColor) 17488 }); 17489 17490 return circle; 17491 }, 17492 'bpmn:IntermediateEvent': function(parentGfx, element) { 17493 var outer = renderer('bpmn:Event')(parentGfx, element, { 17494 strokeWidth: 1, 17495 fill: getFillColor(element, defaultFillColor), 17496 stroke: getStrokeColor$1(element, defaultStrokeColor) 17497 }); 17498 17499 /* inner */ 17500 drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, { 17501 strokeWidth: 1, 17502 fill: getFillColor(element, 'none'), 17503 stroke: getStrokeColor$1(element, defaultStrokeColor) 17504 }); 17505 17506 renderEventContent(element, parentGfx); 17507 17508 return outer; 17509 }, 17510 'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'), 17511 'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'), 17512 17513 'bpmn:Activity': function(parentGfx, element, attrs) { 17514 17515 attrs = attrs || {}; 17516 17517 if (!('fillOpacity' in attrs)) { 17518 attrs.fillOpacity = DEFAULT_FILL_OPACITY; 17519 } 17520 17521 return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, attrs); 17522 }, 17523 17524 'bpmn:Task': function(parentGfx, element) { 17525 var attrs = { 17526 fill: getFillColor(element, defaultFillColor), 17527 stroke: getStrokeColor$1(element, defaultStrokeColor) 17528 }; 17529 17530 var rect = renderer('bpmn:Activity')(parentGfx, element, attrs); 17531 17532 renderEmbeddedLabel(parentGfx, element, 'center-middle'); 17533 attachTaskMarkers(parentGfx, element); 17534 17535 return rect; 17536 }, 17537 'bpmn:ServiceTask': function(parentGfx, element) { 17538 var task = renderer('bpmn:Task')(parentGfx, element); 17539 17540 var pathDataBG = pathMap.getScaledPath('TASK_TYPE_SERVICE', { 17541 abspos: { 17542 x: 12, 17543 y: 18 17544 } 17545 }); 17546 17547 /* service bg */ drawPath(parentGfx, pathDataBG, { 17548 strokeWidth: 1, 17549 fill: getFillColor(element, defaultFillColor), 17550 stroke: getStrokeColor$1(element, defaultStrokeColor) 17551 }); 17552 17553 var fillPathData = pathMap.getScaledPath('TASK_TYPE_SERVICE_FILL', { 17554 abspos: { 17555 x: 17.2, 17556 y: 18 17557 } 17558 }); 17559 17560 /* service fill */ drawPath(parentGfx, fillPathData, { 17561 strokeWidth: 0, 17562 fill: getFillColor(element, defaultFillColor) 17563 }); 17564 17565 var pathData = pathMap.getScaledPath('TASK_TYPE_SERVICE', { 17566 abspos: { 17567 x: 17, 17568 y: 22 17569 } 17570 }); 17571 17572 /* service */ drawPath(parentGfx, pathData, { 17573 strokeWidth: 1, 17574 fill: getFillColor(element, defaultFillColor), 17575 stroke: getStrokeColor$1(element, defaultStrokeColor) 17576 }); 17577 17578 return task; 17579 }, 17580 'bpmn:UserTask': function(parentGfx, element) { 17581 var task = renderer('bpmn:Task')(parentGfx, element); 17582 17583 var x = 15; 17584 var y = 12; 17585 17586 var pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', { 17587 abspos: { 17588 x: x, 17589 y: y 17590 } 17591 }); 17592 17593 /* user path */ drawPath(parentGfx, pathData, { 17594 strokeWidth: 0.5, 17595 fill: getFillColor(element, defaultFillColor), 17596 stroke: getStrokeColor$1(element, defaultStrokeColor) 17597 }); 17598 17599 var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', { 17600 abspos: { 17601 x: x, 17602 y: y 17603 } 17604 }); 17605 17606 /* user2 path */ drawPath(parentGfx, pathData2, { 17607 strokeWidth: 0.5, 17608 fill: getFillColor(element, defaultFillColor), 17609 stroke: getStrokeColor$1(element, defaultStrokeColor) 17610 }); 17611 17612 var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', { 17613 abspos: { 17614 x: x, 17615 y: y 17616 } 17617 }); 17618 17619 /* user3 path */ drawPath(parentGfx, pathData3, { 17620 strokeWidth: 0.5, 17621 fill: getStrokeColor$1(element, defaultStrokeColor), 17622 stroke: getStrokeColor$1(element, defaultStrokeColor) 17623 }); 17624 17625 return task; 17626 }, 17627 'bpmn:ManualTask': function(parentGfx, element) { 17628 var task = renderer('bpmn:Task')(parentGfx, element); 17629 17630 var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', { 17631 abspos: { 17632 x: 17, 17633 y: 15 17634 } 17635 }); 17636 17637 /* manual path */ drawPath(parentGfx, pathData, { 17638 strokeWidth: 0.5, // 0.25, 17639 fill: getFillColor(element, defaultFillColor), 17640 stroke: getStrokeColor$1(element, defaultStrokeColor) 17641 }); 17642 17643 return task; 17644 }, 17645 'bpmn:SendTask': function(parentGfx, element) { 17646 var task = renderer('bpmn:Task')(parentGfx, element); 17647 17648 var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', { 17649 xScaleFactor: 1, 17650 yScaleFactor: 1, 17651 containerWidth: 21, 17652 containerHeight: 14, 17653 position: { 17654 mx: 0.285, 17655 my: 0.357 17656 } 17657 }); 17658 17659 /* send path */ drawPath(parentGfx, pathData, { 17660 strokeWidth: 1, 17661 fill: getStrokeColor$1(element, defaultStrokeColor), 17662 stroke: getFillColor(element, defaultFillColor) 17663 }); 17664 17665 return task; 17666 }, 17667 'bpmn:ReceiveTask' : function(parentGfx, element) { 17668 var semantic = getSemantic(element); 17669 17670 var task = renderer('bpmn:Task')(parentGfx, element); 17671 var pathData; 17672 17673 if (semantic.instantiate) { 17674 drawCircle(parentGfx, 28, 28, 20 * 0.22, { strokeWidth: 1 }); 17675 17676 pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', { 17677 abspos: { 17678 x: 7.77, 17679 y: 9.52 17680 } 17681 }); 17682 } else { 17683 17684 pathData = pathMap.getScaledPath('TASK_TYPE_SEND', { 17685 xScaleFactor: 0.9, 17686 yScaleFactor: 0.9, 17687 containerWidth: 21, 17688 containerHeight: 14, 17689 position: { 17690 mx: 0.3, 17691 my: 0.4 17692 } 17693 }); 17694 } 17695 17696 /* receive path */ drawPath(parentGfx, pathData, { 17697 strokeWidth: 1, 17698 fill: getFillColor(element, defaultFillColor), 17699 stroke: getStrokeColor$1(element, defaultStrokeColor) 17700 }); 17701 17702 return task; 17703 }, 17704 'bpmn:ScriptTask': function(parentGfx, element) { 17705 var task = renderer('bpmn:Task')(parentGfx, element); 17706 17707 var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', { 17708 abspos: { 17709 x: 15, 17710 y: 20 17711 } 17712 }); 17713 17714 /* script path */ drawPath(parentGfx, pathData, { 17715 strokeWidth: 1, 17716 stroke: getStrokeColor$1(element, defaultStrokeColor) 17717 }); 17718 17719 return task; 17720 }, 17721 'bpmn:BusinessRuleTask': function(parentGfx, element) { 17722 var task = renderer('bpmn:Task')(parentGfx, element); 17723 17724 var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', { 17725 abspos: { 17726 x: 8, 17727 y: 8 17728 } 17729 }); 17730 17731 var businessHeaderPath = drawPath(parentGfx, headerPathData); 17732 attr(businessHeaderPath, { 17733 strokeWidth: 1, 17734 fill: getFillColor(element, '#aaaaaa'), 17735 stroke: getStrokeColor$1(element, defaultStrokeColor) 17736 }); 17737 17738 var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', { 17739 abspos: { 17740 x: 8, 17741 y: 8 17742 } 17743 }); 17744 17745 var businessPath = drawPath(parentGfx, headerData); 17746 attr(businessPath, { 17747 strokeWidth: 1, 17748 stroke: getStrokeColor$1(element, defaultStrokeColor) 17749 }); 17750 17751 return task; 17752 }, 17753 'bpmn:SubProcess': function(parentGfx, element, attrs) { 17754 attrs = assign({ 17755 fill: getFillColor(element, defaultFillColor), 17756 stroke: getStrokeColor$1(element, defaultStrokeColor) 17757 }, attrs); 17758 17759 var rect = renderer('bpmn:Activity')(parentGfx, element, attrs); 17760 17761 var expanded = isExpanded(element); 17762 17763 if (isEventSubProcess(element)) { 17764 attr(rect, { 17765 strokeDasharray: '1,2' 17766 }); 17767 } 17768 17769 renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle'); 17770 17771 if (expanded) { 17772 attachTaskMarkers(parentGfx, element); 17773 } else { 17774 attachTaskMarkers(parentGfx, element, ['SubProcessMarker']); 17775 } 17776 17777 return rect; 17778 }, 17779 'bpmn:AdHocSubProcess': function(parentGfx, element) { 17780 return renderer('bpmn:SubProcess')(parentGfx, element); 17781 }, 17782 'bpmn:Transaction': function(parentGfx, element) { 17783 var outer = renderer('bpmn:SubProcess')(parentGfx, element); 17784 17785 var innerAttrs = styles.style([ 'no-fill', 'no-events' ], { 17786 stroke: getStrokeColor$1(element, defaultStrokeColor) 17787 }); 17788 17789 /* inner path */ drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST, innerAttrs); 17790 17791 return outer; 17792 }, 17793 'bpmn:CallActivity': function(parentGfx, element) { 17794 return renderer('bpmn:SubProcess')(parentGfx, element, { 17795 strokeWidth: 5 17796 }); 17797 }, 17798 'bpmn:Participant': function(parentGfx, element) { 17799 17800 var attrs = { 17801 fillOpacity: DEFAULT_FILL_OPACITY, 17802 fill: getFillColor(element, defaultFillColor), 17803 stroke: getStrokeColor$1(element, defaultStrokeColor) 17804 }; 17805 17806 var lane = renderer('bpmn:Lane')(parentGfx, element, attrs); 17807 17808 var expandedPool = isExpanded(element); 17809 17810 if (expandedPool) { 17811 drawLine(parentGfx, [ 17812 { x: 30, y: 0 }, 17813 { x: 30, y: element.height } 17814 ], { 17815 stroke: getStrokeColor$1(element, defaultStrokeColor) 17816 }); 17817 var text = getSemantic(element).name; 17818 renderLaneLabel(parentGfx, text, element); 17819 } else { 17820 17821 // Collapsed pool draw text inline 17822 var text2 = getSemantic(element).name; 17823 renderLabel(parentGfx, text2, { 17824 box: element, align: 'center-middle', 17825 style: { 17826 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) 17827 } 17828 }); 17829 } 17830 17831 var participantMultiplicity = !!(getSemantic(element).participantMultiplicity); 17832 17833 if (participantMultiplicity) { 17834 renderer('ParticipantMultiplicityMarker')(parentGfx, element); 17835 } 17836 17837 return lane; 17838 }, 17839 'bpmn:Lane': function(parentGfx, element, attrs) { 17840 var rect = drawRect(parentGfx, element.width, element.height, 0, assign({ 17841 fill: getFillColor(element, defaultFillColor), 17842 fillOpacity: HIGH_FILL_OPACITY, 17843 stroke: getStrokeColor$1(element, defaultStrokeColor) 17844 }, attrs)); 17845 17846 var semantic = getSemantic(element); 17847 17848 if (semantic.$type === 'bpmn:Lane') { 17849 var text = semantic.name; 17850 renderLaneLabel(parentGfx, text, element); 17851 } 17852 17853 return rect; 17854 }, 17855 'bpmn:InclusiveGateway': function(parentGfx, element) { 17856 var diamond = renderer('bpmn:Gateway')(parentGfx, element); 17857 17858 /* circle path */ 17859 drawCircle(parentGfx, element.width, element.height, element.height * 0.24, { 17860 strokeWidth: 2.5, 17861 fill: getFillColor(element, defaultFillColor), 17862 stroke: getStrokeColor$1(element, defaultStrokeColor) 17863 }); 17864 17865 return diamond; 17866 }, 17867 'bpmn:ExclusiveGateway': function(parentGfx, element) { 17868 var diamond = renderer('bpmn:Gateway')(parentGfx, element); 17869 17870 var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', { 17871 xScaleFactor: 0.4, 17872 yScaleFactor: 0.4, 17873 containerWidth: element.width, 17874 containerHeight: element.height, 17875 position: { 17876 mx: 0.32, 17877 my: 0.3 17878 } 17879 }); 17880 17881 if ((getDi(element).isMarkerVisible)) { 17882 drawPath(parentGfx, pathData, { 17883 strokeWidth: 1, 17884 fill: getStrokeColor$1(element, defaultStrokeColor), 17885 stroke: getStrokeColor$1(element, defaultStrokeColor) 17886 }); 17887 } 17888 17889 return diamond; 17890 }, 17891 'bpmn:ComplexGateway': function(parentGfx, element) { 17892 var diamond = renderer('bpmn:Gateway')(parentGfx, element); 17893 17894 var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', { 17895 xScaleFactor: 0.5, 17896 yScaleFactor:0.5, 17897 containerWidth: element.width, 17898 containerHeight: element.height, 17899 position: { 17900 mx: 0.46, 17901 my: 0.26 17902 } 17903 }); 17904 17905 /* complex path */ drawPath(parentGfx, pathData, { 17906 strokeWidth: 1, 17907 fill: getStrokeColor$1(element, defaultStrokeColor), 17908 stroke: getStrokeColor$1(element, defaultStrokeColor) 17909 }); 17910 17911 return diamond; 17912 }, 17913 'bpmn:ParallelGateway': function(parentGfx, element) { 17914 var diamond = renderer('bpmn:Gateway')(parentGfx, element); 17915 17916 var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', { 17917 xScaleFactor: 0.6, 17918 yScaleFactor:0.6, 17919 containerWidth: element.width, 17920 containerHeight: element.height, 17921 position: { 17922 mx: 0.46, 17923 my: 0.2 17924 } 17925 }); 17926 17927 /* parallel path */ drawPath(parentGfx, pathData, { 17928 strokeWidth: 1, 17929 fill: getStrokeColor$1(element, defaultStrokeColor), 17930 stroke: getStrokeColor$1(element, defaultStrokeColor) 17931 }); 17932 17933 return diamond; 17934 }, 17935 'bpmn:EventBasedGateway': function(parentGfx, element) { 17936 17937 var semantic = getSemantic(element); 17938 17939 var diamond = renderer('bpmn:Gateway')(parentGfx, element); 17940 17941 /* outer circle path */ drawCircle(parentGfx, element.width, element.height, element.height * 0.20, { 17942 strokeWidth: 1, 17943 fill: 'none', 17944 stroke: getStrokeColor$1(element, defaultStrokeColor) 17945 }); 17946 17947 var type = semantic.eventGatewayType; 17948 var instantiate = !!semantic.instantiate; 17949 17950 function drawEvent() { 17951 17952 var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', { 17953 xScaleFactor: 0.18, 17954 yScaleFactor: 0.18, 17955 containerWidth: element.width, 17956 containerHeight: element.height, 17957 position: { 17958 mx: 0.36, 17959 my: 0.44 17960 } 17961 }); 17962 17963 var attrs = { 17964 strokeWidth: 2, 17965 fill: getFillColor(element, 'none'), 17966 stroke: getStrokeColor$1(element, defaultStrokeColor) 17967 }; 17968 17969 /* event path */ drawPath(parentGfx, pathData, attrs); 17970 } 17971 17972 if (type === 'Parallel') { 17973 17974 var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', { 17975 xScaleFactor: 0.4, 17976 yScaleFactor:0.4, 17977 containerWidth: element.width, 17978 containerHeight: element.height, 17979 position: { 17980 mx: 0.474, 17981 my: 0.296 17982 } 17983 }); 17984 17985 var parallelPath = drawPath(parentGfx, pathData); 17986 attr(parallelPath, { 17987 strokeWidth: 1, 17988 fill: 'none' 17989 }); 17990 } else if (type === 'Exclusive') { 17991 17992 if (!instantiate) { 17993 var innerCircle = drawCircle(parentGfx, element.width, element.height, element.height * 0.26); 17994 attr(innerCircle, { 17995 strokeWidth: 1, 17996 fill: 'none', 17997 stroke: getStrokeColor$1(element, defaultStrokeColor) 17998 }); 17999 } 18000 18001 drawEvent(); 18002 } 18003 18004 18005 return diamond; 18006 }, 18007 'bpmn:Gateway': function(parentGfx, element) { 18008 var attrs = { 18009 fill: getFillColor(element, defaultFillColor), 18010 fillOpacity: DEFAULT_FILL_OPACITY, 18011 stroke: getStrokeColor$1(element, defaultStrokeColor) 18012 }; 18013 18014 return drawDiamond(parentGfx, element.width, element.height, attrs); 18015 }, 18016 'bpmn:SequenceFlow': function(parentGfx, element) { 18017 var pathData = createPathFromConnection(element); 18018 18019 var fill = getFillColor(element, defaultFillColor), 18020 stroke = getStrokeColor$1(element, defaultStrokeColor); 18021 18022 var attrs = { 18023 strokeLinejoin: 'round', 18024 markerEnd: marker('sequenceflow-end', fill, stroke), 18025 stroke: getStrokeColor$1(element, defaultStrokeColor) 18026 }; 18027 18028 var path = drawPath(parentGfx, pathData, attrs); 18029 18030 var sequenceFlow = getSemantic(element); 18031 18032 var source; 18033 18034 if (element.source) { 18035 source = element.source.businessObject; 18036 18037 // conditional flow marker 18038 if (sequenceFlow.conditionExpression && source.$instanceOf('bpmn:Activity')) { 18039 attr(path, { 18040 markerStart: marker('conditional-flow-marker', fill, stroke) 18041 }); 18042 } 18043 18044 // default marker 18045 if (source.default && (source.$instanceOf('bpmn:Gateway') || source.$instanceOf('bpmn:Activity')) && 18046 source.default === sequenceFlow) { 18047 attr(path, { 18048 markerStart: marker('conditional-default-flow-marker', fill, stroke) 18049 }); 18050 } 18051 } 18052 18053 return path; 18054 }, 18055 'bpmn:Association': function(parentGfx, element, attrs) { 18056 18057 var semantic = getSemantic(element); 18058 18059 var fill = getFillColor(element, defaultFillColor), 18060 stroke = getStrokeColor$1(element, defaultStrokeColor); 18061 18062 attrs = assign({ 18063 strokeDasharray: '0.5, 5', 18064 strokeLinecap: 'round', 18065 strokeLinejoin: 'round', 18066 stroke: getStrokeColor$1(element, defaultStrokeColor) 18067 }, attrs || {}); 18068 18069 if (semantic.associationDirection === 'One' || 18070 semantic.associationDirection === 'Both') { 18071 attrs.markerEnd = marker('association-end', fill, stroke); 18072 } 18073 18074 if (semantic.associationDirection === 'Both') { 18075 attrs.markerStart = marker('association-start', fill, stroke); 18076 } 18077 18078 return drawLine(parentGfx, element.waypoints, attrs); 18079 }, 18080 'bpmn:DataInputAssociation': function(parentGfx, element) { 18081 var fill = getFillColor(element, defaultFillColor), 18082 stroke = getStrokeColor$1(element, defaultStrokeColor); 18083 18084 return renderer('bpmn:Association')(parentGfx, element, { 18085 markerEnd: marker('association-end', fill, stroke) 18086 }); 18087 }, 18088 'bpmn:DataOutputAssociation': function(parentGfx, element) { 18089 var fill = getFillColor(element, defaultFillColor), 18090 stroke = getStrokeColor$1(element, defaultStrokeColor); 18091 18092 return renderer('bpmn:Association')(parentGfx, element, { 18093 markerEnd: marker('association-end', fill, stroke) 18094 }); 18095 }, 18096 'bpmn:MessageFlow': function(parentGfx, element) { 18097 18098 var semantic = getSemantic(element), 18099 di = getDi(element); 18100 18101 var fill = getFillColor(element, defaultFillColor), 18102 stroke = getStrokeColor$1(element, defaultStrokeColor); 18103 18104 var pathData = createPathFromConnection(element); 18105 18106 var attrs = { 18107 markerEnd: marker('messageflow-end', fill, stroke), 18108 markerStart: marker('messageflow-start', fill, stroke), 18109 strokeDasharray: '10, 12', 18110 strokeLinecap: 'round', 18111 strokeLinejoin: 'round', 18112 strokeWidth: '1.5px', 18113 stroke: getStrokeColor$1(element, defaultStrokeColor) 18114 }; 18115 18116 var path = drawPath(parentGfx, pathData, attrs); 18117 18118 if (semantic.messageRef) { 18119 var midPoint = path.getPointAtLength(path.getTotalLength() / 2); 18120 18121 var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', { 18122 abspos: { 18123 x: midPoint.x, 18124 y: midPoint.y 18125 } 18126 }); 18127 18128 var messageAttrs = { strokeWidth: 1 }; 18129 18130 if (di.messageVisibleKind === 'initiating') { 18131 messageAttrs.fill = 'white'; 18132 messageAttrs.stroke = 'black'; 18133 } else { 18134 messageAttrs.fill = '#888'; 18135 messageAttrs.stroke = 'white'; 18136 } 18137 18138 var message = drawPath(parentGfx, markerPathData, messageAttrs); 18139 18140 var labelText = semantic.messageRef.name; 18141 var label = renderLabel(parentGfx, labelText, { 18142 align: 'center-top', 18143 fitBox: true, 18144 style: { 18145 fill: getStrokeColor$1(element, defaultLabelColor) 18146 } 18147 }); 18148 18149 var messageBounds = message.getBBox(), 18150 labelBounds = label.getBBox(); 18151 18152 var translateX = midPoint.x - labelBounds.width / 2, 18153 translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE$1; 18154 18155 transform(label, translateX, translateY, 0); 18156 18157 } 18158 18159 return path; 18160 }, 18161 'bpmn:DataObject': function(parentGfx, element) { 18162 var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', { 18163 xScaleFactor: 1, 18164 yScaleFactor: 1, 18165 containerWidth: element.width, 18166 containerHeight: element.height, 18167 position: { 18168 mx: 0.474, 18169 my: 0.296 18170 } 18171 }); 18172 18173 var elementObject = drawPath(parentGfx, pathData, { 18174 fill: getFillColor(element, defaultFillColor), 18175 fillOpacity: DEFAULT_FILL_OPACITY, 18176 stroke: getStrokeColor$1(element, defaultStrokeColor) 18177 }); 18178 18179 var semantic = getSemantic(element); 18180 18181 if (isCollection(semantic)) { 18182 renderDataItemCollection(parentGfx, element); 18183 } 18184 18185 return elementObject; 18186 }, 18187 'bpmn:DataObjectReference': as('bpmn:DataObject'), 18188 'bpmn:DataInput': function(parentGfx, element) { 18189 18190 var arrowPathData = pathMap.getRawPath('DATA_ARROW'); 18191 18192 // page 18193 var elementObject = renderer('bpmn:DataObject')(parentGfx, element); 18194 18195 /* input arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1 }); 18196 18197 return elementObject; 18198 }, 18199 'bpmn:DataOutput': function(parentGfx, element) { 18200 var arrowPathData = pathMap.getRawPath('DATA_ARROW'); 18201 18202 // page 18203 var elementObject = renderer('bpmn:DataObject')(parentGfx, element); 18204 18205 /* output arrow path */ drawPath(parentGfx, arrowPathData, { 18206 strokeWidth: 1, 18207 fill: 'black' 18208 }); 18209 18210 return elementObject; 18211 }, 18212 'bpmn:DataStoreReference': function(parentGfx, element) { 18213 var DATA_STORE_PATH = pathMap.getScaledPath('DATA_STORE', { 18214 xScaleFactor: 1, 18215 yScaleFactor: 1, 18216 containerWidth: element.width, 18217 containerHeight: element.height, 18218 position: { 18219 mx: 0, 18220 my: 0.133 18221 } 18222 }); 18223 18224 var elementStore = drawPath(parentGfx, DATA_STORE_PATH, { 18225 strokeWidth: 2, 18226 fill: getFillColor(element, defaultFillColor), 18227 fillOpacity: DEFAULT_FILL_OPACITY, 18228 stroke: getStrokeColor$1(element, defaultStrokeColor) 18229 }); 18230 18231 return elementStore; 18232 }, 18233 'bpmn:BoundaryEvent': function(parentGfx, element) { 18234 18235 var semantic = getSemantic(element), 18236 cancel = semantic.cancelActivity; 18237 18238 var attrs = { 18239 strokeWidth: 1, 18240 fill: getFillColor(element, defaultFillColor), 18241 stroke: getStrokeColor$1(element, defaultStrokeColor) 18242 }; 18243 18244 if (!cancel) { 18245 attrs.strokeDasharray = '6'; 18246 attrs.strokeLinecap = 'round'; 18247 } 18248 18249 // apply fillOpacity 18250 var outerAttrs = assign({}, attrs, { 18251 fillOpacity: 1 18252 }); 18253 18254 // apply no-fill 18255 var innerAttrs = assign({}, attrs, { 18256 fill: 'none' 18257 }); 18258 18259 var outer = renderer('bpmn:Event')(parentGfx, element, outerAttrs); 18260 18261 /* inner path */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, innerAttrs); 18262 18263 renderEventContent(element, parentGfx); 18264 18265 return outer; 18266 }, 18267 'bpmn:Group': function(parentGfx, element) { 18268 18269 var group = drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, { 18270 stroke: getStrokeColor$1(element, defaultStrokeColor), 18271 strokeWidth: 1, 18272 strokeDasharray: '8,3,1,3', 18273 fill: 'none', 18274 pointerEvents: 'none' 18275 }); 18276 18277 return group; 18278 }, 18279 'label': function(parentGfx, element) { 18280 return renderExternalLabel(parentGfx, element); 18281 }, 18282 'bpmn:TextAnnotation': function(parentGfx, element) { 18283 var style = { 18284 'fill': 'none', 18285 'stroke': 'none' 18286 }; 18287 18288 var textElement = drawRect(parentGfx, element.width, element.height, 0, 0, style); 18289 18290 var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', { 18291 xScaleFactor: 1, 18292 yScaleFactor: 1, 18293 containerWidth: element.width, 18294 containerHeight: element.height, 18295 position: { 18296 mx: 0.0, 18297 my: 0.0 18298 } 18299 }); 18300 18301 drawPath(parentGfx, textPathData, { 18302 stroke: getStrokeColor$1(element, defaultStrokeColor) 18303 }); 18304 18305 var text = getSemantic(element).text || ''; 18306 renderLabel(parentGfx, text, { 18307 box: element, 18308 align: 'left-top', 18309 padding: 5, 18310 style: { 18311 fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) 18312 } 18313 }); 18314 18315 return textElement; 18316 }, 18317 'ParticipantMultiplicityMarker': function(parentGfx, element) { 18318 var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { 18319 xScaleFactor: 1, 18320 yScaleFactor: 1, 18321 containerWidth: element.width, 18322 containerHeight: element.height, 18323 position: { 18324 mx: ((element.width / 2) / element.width), 18325 my: (element.height - 15) / element.height 18326 } 18327 }); 18328 18329 drawMarker('participant-multiplicity', parentGfx, markerPath, { 18330 strokeWidth: 2, 18331 fill: getFillColor(element, defaultFillColor), 18332 stroke: getStrokeColor$1(element, defaultStrokeColor) 18333 }); 18334 }, 18335 'SubProcessMarker': function(parentGfx, element) { 18336 var markerRect = drawRect(parentGfx, 14, 14, 0, { 18337 strokeWidth: 1, 18338 fill: getFillColor(element, defaultFillColor), 18339 stroke: getStrokeColor$1(element, defaultStrokeColor) 18340 }); 18341 18342 // Process marker is placed in the middle of the box 18343 // therefore fixed values can be used here 18344 translate$2(markerRect, element.width / 2 - 7.5, element.height - 20); 18345 18346 var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', { 18347 xScaleFactor: 1.5, 18348 yScaleFactor: 1.5, 18349 containerWidth: element.width, 18350 containerHeight: element.height, 18351 position: { 18352 mx: (element.width / 2 - 7.5) / element.width, 18353 my: (element.height - 20) / element.height 18354 } 18355 }); 18356 18357 drawMarker('sub-process', parentGfx, markerPath, { 18358 fill: getFillColor(element, defaultFillColor), 18359 stroke: getStrokeColor$1(element, defaultStrokeColor) 18360 }); 18361 }, 18362 'ParallelMarker': function(parentGfx, element, position) { 18363 var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { 18364 xScaleFactor: 1, 18365 yScaleFactor: 1, 18366 containerWidth: element.width, 18367 containerHeight: element.height, 18368 position: { 18369 mx: ((element.width / 2 + position.parallel) / element.width), 18370 my: (element.height - 20) / element.height 18371 } 18372 }); 18373 18374 drawMarker('parallel', parentGfx, markerPath, { 18375 fill: getFillColor(element, defaultFillColor), 18376 stroke: getStrokeColor$1(element, defaultStrokeColor) 18377 }); 18378 }, 18379 'SequentialMarker': function(parentGfx, element, position) { 18380 var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', { 18381 xScaleFactor: 1, 18382 yScaleFactor: 1, 18383 containerWidth: element.width, 18384 containerHeight: element.height, 18385 position: { 18386 mx: ((element.width / 2 + position.seq) / element.width), 18387 my: (element.height - 19) / element.height 18388 } 18389 }); 18390 18391 drawMarker('sequential', parentGfx, markerPath, { 18392 fill: getFillColor(element, defaultFillColor), 18393 stroke: getStrokeColor$1(element, defaultStrokeColor) 18394 }); 18395 }, 18396 'CompensationMarker': function(parentGfx, element, position) { 18397 var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', { 18398 xScaleFactor: 1, 18399 yScaleFactor: 1, 18400 containerWidth: element.width, 18401 containerHeight: element.height, 18402 position: { 18403 mx: ((element.width / 2 + position.compensation) / element.width), 18404 my: (element.height - 13) / element.height 18405 } 18406 }); 18407 18408 drawMarker('compensation', parentGfx, markerMath, { 18409 strokeWidth: 1, 18410 fill: getFillColor(element, defaultFillColor), 18411 stroke: getStrokeColor$1(element, defaultStrokeColor) 18412 }); 18413 }, 18414 'LoopMarker': function(parentGfx, element, position) { 18415 var markerPath = pathMap.getScaledPath('MARKER_LOOP', { 18416 xScaleFactor: 1, 18417 yScaleFactor: 1, 18418 containerWidth: element.width, 18419 containerHeight: element.height, 18420 position: { 18421 mx: ((element.width / 2 + position.loop) / element.width), 18422 my: (element.height - 7) / element.height 18423 } 18424 }); 18425 18426 drawMarker('loop', parentGfx, markerPath, { 18427 strokeWidth: 1, 18428 fill: getFillColor(element, defaultFillColor), 18429 stroke: getStrokeColor$1(element, defaultStrokeColor), 18430 strokeLinecap: 'round', 18431 strokeMiterlimit: 0.5 18432 }); 18433 }, 18434 'AdhocMarker': function(parentGfx, element, position) { 18435 var markerPath = pathMap.getScaledPath('MARKER_ADHOC', { 18436 xScaleFactor: 1, 18437 yScaleFactor: 1, 18438 containerWidth: element.width, 18439 containerHeight: element.height, 18440 position: { 18441 mx: ((element.width / 2 + position.adhoc) / element.width), 18442 my: (element.height - 15) / element.height 18443 } 18444 }); 18445 18446 drawMarker('adhoc', parentGfx, markerPath, { 18447 strokeWidth: 1, 18448 fill: getStrokeColor$1(element, defaultStrokeColor), 18449 stroke: getStrokeColor$1(element, defaultStrokeColor) 18450 }); 18451 } 18452 }; 18453 18454 function attachTaskMarkers(parentGfx, element, taskMarkers) { 18455 var obj = getSemantic(element); 18456 18457 var subprocess = taskMarkers && taskMarkers.indexOf('SubProcessMarker') !== -1; 18458 var position; 18459 18460 if (subprocess) { 18461 position = { 18462 seq: -21, 18463 parallel: -22, 18464 compensation: -42, 18465 loop: -18, 18466 adhoc: 10 18467 }; 18468 } else { 18469 position = { 18470 seq: -3, 18471 parallel: -6, 18472 compensation: -27, 18473 loop: 0, 18474 adhoc: 10 18475 }; 18476 } 18477 18478 forEach(taskMarkers, function(marker) { 18479 renderer(marker)(parentGfx, element, position); 18480 }); 18481 18482 if (obj.isForCompensation) { 18483 renderer('CompensationMarker')(parentGfx, element, position); 18484 } 18485 18486 if (obj.$type === 'bpmn:AdHocSubProcess') { 18487 renderer('AdhocMarker')(parentGfx, element, position); 18488 } 18489 18490 var loopCharacteristics = obj.loopCharacteristics, 18491 isSequential = loopCharacteristics && loopCharacteristics.isSequential; 18492 18493 if (loopCharacteristics) { 18494 18495 if (isSequential === undefined) { 18496 renderer('LoopMarker')(parentGfx, element, position); 18497 } 18498 18499 if (isSequential === false) { 18500 renderer('ParallelMarker')(parentGfx, element, position); 18501 } 18502 18503 if (isSequential === true) { 18504 renderer('SequentialMarker')(parentGfx, element, position); 18505 } 18506 } 18507 } 18508 18509 function renderDataItemCollection(parentGfx, element) { 18510 18511 var yPosition = (element.height - 18) / element.height; 18512 18513 var pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', { 18514 xScaleFactor: 1, 18515 yScaleFactor: 1, 18516 containerWidth: element.width, 18517 containerHeight: element.height, 18518 position: { 18519 mx: 0.33, 18520 my: yPosition 18521 } 18522 }); 18523 18524 /* collection path */ drawPath(parentGfx, pathData, { 18525 strokeWidth: 2 18526 }); 18527 } 18528 18529 18530 // extension API, use at your own risk 18531 this._drawPath = drawPath; 18532 18533 } 18534 18535 18536 inherits$1(BpmnRenderer, BaseRenderer); 18537 18538 BpmnRenderer.$inject = [ 18539 'config.bpmnRenderer', 18540 'eventBus', 18541 'styles', 18542 'pathMap', 18543 'canvas', 18544 'textRenderer' 18545 ]; 18546 18547 18548 BpmnRenderer.prototype.canRender = function(element) { 18549 return is$1(element, 'bpmn:BaseElement'); 18550 }; 18551 18552 BpmnRenderer.prototype.drawShape = function(parentGfx, element) { 18553 var type = element.type; 18554 var h = this.handlers[type]; 18555 18556 /* jshint -W040 */ 18557 return h(parentGfx, element); 18558 }; 18559 18560 BpmnRenderer.prototype.drawConnection = function(parentGfx, element) { 18561 var type = element.type; 18562 var h = this.handlers[type]; 18563 18564 /* jshint -W040 */ 18565 return h(parentGfx, element); 18566 }; 18567 18568 BpmnRenderer.prototype.getShapePath = function(element) { 18569 18570 if (is$1(element, 'bpmn:Event')) { 18571 return getCirclePath(element); 18572 } 18573 18574 if (is$1(element, 'bpmn:Activity')) { 18575 return getRoundRectPath(element, TASK_BORDER_RADIUS); 18576 } 18577 18578 if (is$1(element, 'bpmn:Gateway')) { 18579 return getDiamondPath(element); 18580 } 18581 18582 return getRectPath(element); 18583 }; 18584 18585 var DEFAULT_BOX_PADDING = 0; 18586 18587 var DEFAULT_LABEL_SIZE$1 = { 18588 width: 150, 18589 height: 50 18590 }; 18591 18592 18593 function parseAlign(align) { 18594 18595 var parts = align.split('-'); 18596 18597 return { 18598 horizontal: parts[0] || 'center', 18599 vertical: parts[1] || 'top' 18600 }; 18601 } 18602 18603 function parsePadding(padding) { 18604 18605 if (isObject(padding)) { 18606 return assign({ top: 0, left: 0, right: 0, bottom: 0 }, padding); 18607 } else { 18608 return { 18609 top: padding, 18610 left: padding, 18611 right: padding, 18612 bottom: padding 18613 }; 18614 } 18615 } 18616 18617 function getTextBBox(text, fakeText) { 18618 18619 fakeText.textContent = text; 18620 18621 var textBBox; 18622 18623 try { 18624 var bbox, 18625 emptyLine = text === ''; 18626 18627 // add dummy text, when line is empty to 18628 // determine correct height 18629 fakeText.textContent = emptyLine ? 'dummy' : text; 18630 18631 textBBox = fakeText.getBBox(); 18632 18633 // take text rendering related horizontal 18634 // padding into account 18635 bbox = { 18636 width: textBBox.width + textBBox.x * 2, 18637 height: textBBox.height 18638 }; 18639 18640 if (emptyLine) { 18641 18642 // correct width 18643 bbox.width = 0; 18644 } 18645 18646 return bbox; 18647 } catch (e) { 18648 return { width: 0, height: 0 }; 18649 } 18650 } 18651 18652 18653 /** 18654 * Layout the next line and return the layouted element. 18655 * 18656 * Alters the lines passed. 18657 * 18658 * @param {Array<string>} lines 18659 * @return {Object} the line descriptor, an object { width, height, text } 18660 */ 18661 function layoutNext(lines, maxWidth, fakeText) { 18662 18663 var originalLine = lines.shift(), 18664 fitLine = originalLine; 18665 18666 var textBBox; 18667 18668 for (;;) { 18669 textBBox = getTextBBox(fitLine, fakeText); 18670 18671 textBBox.width = fitLine ? textBBox.width : 0; 18672 18673 // try to fit 18674 if (fitLine === ' ' || fitLine === '' || textBBox.width < Math.round(maxWidth) || fitLine.length < 2) { 18675 return fit(lines, fitLine, originalLine, textBBox); 18676 } 18677 18678 fitLine = shortenLine(fitLine, textBBox.width, maxWidth); 18679 } 18680 } 18681 18682 function fit(lines, fitLine, originalLine, textBBox) { 18683 if (fitLine.length < originalLine.length) { 18684 var remainder = originalLine.slice(fitLine.length).trim(); 18685 18686 lines.unshift(remainder); 18687 } 18688 18689 return { 18690 width: textBBox.width, 18691 height: textBBox.height, 18692 text: fitLine 18693 }; 18694 } 18695 18696 var SOFT_BREAK = '\u00AD'; 18697 18698 18699 /** 18700 * Shortens a line based on spacing and hyphens. 18701 * Returns the shortened result on success. 18702 * 18703 * @param {string} line 18704 * @param {number} maxLength the maximum characters of the string 18705 * @return {string} the shortened string 18706 */ 18707 function semanticShorten(line, maxLength) { 18708 18709 var parts = line.split(/(\s|-|\u00AD)/g), 18710 part, 18711 shortenedParts = [], 18712 length = 0; 18713 18714 // try to shorten via break chars 18715 if (parts.length > 1) { 18716 18717 while ((part = parts.shift())) { 18718 if (part.length + length < maxLength) { 18719 shortenedParts.push(part); 18720 length += part.length; 18721 } else { 18722 18723 // remove previous part, too if hyphen does not fit anymore 18724 if (part === '-' || part === SOFT_BREAK) { 18725 shortenedParts.pop(); 18726 } 18727 18728 break; 18729 } 18730 } 18731 } 18732 18733 var last = shortenedParts[shortenedParts.length - 1]; 18734 18735 // translate trailing soft break to actual hyphen 18736 if (last && last === SOFT_BREAK) { 18737 shortenedParts[shortenedParts.length - 1] = '-'; 18738 } 18739 18740 return shortenedParts.join(''); 18741 } 18742 18743 18744 function shortenLine(line, width, maxWidth) { 18745 var length = Math.max(line.length * (maxWidth / width), 1); 18746 18747 // try to shorten semantically (i.e. based on spaces and hyphens) 18748 var shortenedLine = semanticShorten(line, length); 18749 18750 if (!shortenedLine) { 18751 18752 // force shorten by cutting the long word 18753 shortenedLine = line.slice(0, Math.max(Math.round(length - 1), 1)); 18754 } 18755 18756 return shortenedLine; 18757 } 18758 18759 18760 function getHelperSvg() { 18761 var helperSvg = document.getElementById('helper-svg'); 18762 18763 if (!helperSvg) { 18764 helperSvg = create$1('svg'); 18765 18766 attr(helperSvg, { 18767 id: 'helper-svg', 18768 width: 0, 18769 height: 0, 18770 style: 'visibility: hidden; position: fixed' 18771 }); 18772 18773 document.body.appendChild(helperSvg); 18774 } 18775 18776 return helperSvg; 18777 } 18778 18779 18780 /** 18781 * Creates a new label utility 18782 * 18783 * @param {Object} config 18784 * @param {Dimensions} config.size 18785 * @param {number} config.padding 18786 * @param {Object} config.style 18787 * @param {string} config.align 18788 */ 18789 function Text(config) { 18790 18791 this._config = assign({}, { 18792 size: DEFAULT_LABEL_SIZE$1, 18793 padding: DEFAULT_BOX_PADDING, 18794 style: {}, 18795 align: 'center-top' 18796 }, config || {}); 18797 } 18798 18799 /** 18800 * Returns the layouted text as an SVG element. 18801 * 18802 * @param {string} text 18803 * @param {Object} options 18804 * 18805 * @return {SVGElement} 18806 */ 18807 Text.prototype.createText = function(text, options) { 18808 return this.layoutText(text, options).element; 18809 }; 18810 18811 /** 18812 * Returns a labels layouted dimensions. 18813 * 18814 * @param {string} text to layout 18815 * @param {Object} options 18816 * 18817 * @return {Dimensions} 18818 */ 18819 Text.prototype.getDimensions = function(text, options) { 18820 return this.layoutText(text, options).dimensions; 18821 }; 18822 18823 /** 18824 * Creates and returns a label and its bounding box. 18825 * 18826 * @method Text#createText 18827 * 18828 * @param {string} text the text to render on the label 18829 * @param {Object} options 18830 * @param {string} options.align how to align in the bounding box. 18831 * Any of { 'center-middle', 'center-top' }, 18832 * defaults to 'center-top'. 18833 * @param {string} options.style style to be applied to the text 18834 * @param {boolean} options.fitBox indicates if box will be recalculated to 18835 * fit text 18836 * 18837 * @return {Object} { element, dimensions } 18838 */ 18839 Text.prototype.layoutText = function(text, options) { 18840 var box = assign({}, this._config.size, options.box), 18841 style = assign({}, this._config.style, options.style), 18842 align = parseAlign(options.align || this._config.align), 18843 padding = parsePadding(options.padding !== undefined ? options.padding : this._config.padding), 18844 fitBox = options.fitBox || false; 18845 18846 var lineHeight = getLineHeight(style); 18847 18848 // we split text by lines and normalize 18849 // {soft break} + {line break} => { line break } 18850 var lines = text.split(/\u00AD?\r?\n/), 18851 layouted = []; 18852 18853 var maxWidth = box.width - padding.left - padding.right; 18854 18855 // ensure correct rendering by attaching helper text node to invisible SVG 18856 var helperText = create$1('text'); 18857 attr(helperText, { x: 0, y: 0 }); 18858 attr(helperText, style); 18859 18860 var helperSvg = getHelperSvg(); 18861 18862 append(helperSvg, helperText); 18863 18864 while (lines.length) { 18865 layouted.push(layoutNext(lines, maxWidth, helperText)); 18866 } 18867 18868 if (align.vertical === 'middle') { 18869 padding.top = padding.bottom = 0; 18870 } 18871 18872 var totalHeight = reduce(layouted, function(sum, line, idx) { 18873 return sum + (lineHeight || line.height); 18874 }, 0) + padding.top + padding.bottom; 18875 18876 var maxLineWidth = reduce(layouted, function(sum, line, idx) { 18877 return line.width > sum ? line.width : sum; 18878 }, 0); 18879 18880 // the y position of the next line 18881 var y = padding.top; 18882 18883 if (align.vertical === 'middle') { 18884 y += (box.height - totalHeight) / 2; 18885 } 18886 18887 // magic number initial offset 18888 y -= (lineHeight || layouted[0].height) / 4; 18889 18890 18891 var textElement = create$1('text'); 18892 18893 attr(textElement, style); 18894 18895 // layout each line taking into account that parent 18896 // shape might resize to fit text size 18897 forEach(layouted, function(line) { 18898 18899 var x; 18900 18901 y += (lineHeight || line.height); 18902 18903 switch (align.horizontal) { 18904 case 'left': 18905 x = padding.left; 18906 break; 18907 18908 case 'right': 18909 x = ((fitBox ? maxLineWidth : maxWidth) 18910 - padding.right - line.width); 18911 break; 18912 18913 default: 18914 18915 // aka center 18916 x = Math.max((((fitBox ? maxLineWidth : maxWidth) 18917 - line.width) / 2 + padding.left), 0); 18918 } 18919 18920 var tspan = create$1('tspan'); 18921 attr(tspan, { x: x, y: y }); 18922 18923 tspan.textContent = line.text; 18924 18925 append(textElement, tspan); 18926 }); 18927 18928 remove$1(helperText); 18929 18930 var dimensions = { 18931 width: maxLineWidth, 18932 height: totalHeight 18933 }; 18934 18935 return { 18936 dimensions: dimensions, 18937 element: textElement 18938 }; 18939 }; 18940 18941 18942 function getLineHeight(style) { 18943 if ('fontSize' in style && 'lineHeight' in style) { 18944 return style.lineHeight * parseInt(style.fontSize, 10); 18945 } 18946 } 18947 18948 var DEFAULT_FONT_SIZE = 12; 18949 var LINE_HEIGHT_RATIO = 1.2; 18950 18951 var MIN_TEXT_ANNOTATION_HEIGHT = 30; 18952 18953 18954 function TextRenderer(config) { 18955 18956 var defaultStyle = assign({ 18957 fontFamily: 'Arial, sans-serif', 18958 fontSize: DEFAULT_FONT_SIZE, 18959 fontWeight: 'normal', 18960 lineHeight: LINE_HEIGHT_RATIO 18961 }, config && config.defaultStyle || {}); 18962 18963 var fontSize = parseInt(defaultStyle.fontSize, 10) - 1; 18964 18965 var externalStyle = assign({}, defaultStyle, { 18966 fontSize: fontSize 18967 }, config && config.externalStyle || {}); 18968 18969 var textUtil = new Text({ 18970 style: defaultStyle 18971 }); 18972 18973 /** 18974 * Get the new bounds of an externally rendered, 18975 * layouted label. 18976 * 18977 * @param {Bounds} bounds 18978 * @param {string} text 18979 * 18980 * @return {Bounds} 18981 */ 18982 this.getExternalLabelBounds = function(bounds, text) { 18983 18984 var layoutedDimensions = textUtil.getDimensions(text, { 18985 box: { 18986 width: 90, 18987 height: 30, 18988 x: bounds.width / 2 + bounds.x, 18989 y: bounds.height / 2 + bounds.y 18990 }, 18991 style: externalStyle 18992 }); 18993 18994 // resize label shape to fit label text 18995 return { 18996 x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2), 18997 y: Math.round(bounds.y), 18998 width: Math.ceil(layoutedDimensions.width), 18999 height: Math.ceil(layoutedDimensions.height) 19000 }; 19001 19002 }; 19003 19004 /** 19005 * Get the new bounds of text annotation. 19006 * 19007 * @param {Bounds} bounds 19008 * @param {string} text 19009 * 19010 * @return {Bounds} 19011 */ 19012 this.getTextAnnotationBounds = function(bounds, text) { 19013 19014 var layoutedDimensions = textUtil.getDimensions(text, { 19015 box: bounds, 19016 style: defaultStyle, 19017 align: 'left-top', 19018 padding: 5 19019 }); 19020 19021 return { 19022 x: bounds.x, 19023 y: bounds.y, 19024 width: bounds.width, 19025 height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height)) 19026 }; 19027 }; 19028 19029 /** 19030 * Create a layouted text element. 19031 * 19032 * @param {string} text 19033 * @param {Object} [options] 19034 * 19035 * @return {SVGElement} rendered text 19036 */ 19037 this.createText = function(text, options) { 19038 return textUtil.createText(text, options || {}); 19039 }; 19040 19041 /** 19042 * Get default text style. 19043 */ 19044 this.getDefaultStyle = function() { 19045 return defaultStyle; 19046 }; 19047 19048 /** 19049 * Get the external text style. 19050 */ 19051 this.getExternalStyle = function() { 19052 return externalStyle; 19053 }; 19054 19055 } 19056 19057 TextRenderer.$inject = [ 19058 'config.textRenderer' 19059 ]; 19060 19061 /** 19062 * Map containing SVG paths needed by BpmnRenderer. 19063 */ 19064 19065 function PathMap() { 19066 19067 /** 19068 * Contains a map of path elements 19069 * 19070 * <h1>Path definition</h1> 19071 * A parameterized path is defined like this: 19072 * <pre> 19073 * 'GATEWAY_PARALLEL': { 19074 * d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' + 19075 '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z', 19076 * height: 17.5, 19077 * width: 17.5, 19078 * heightElements: [2.5, 7.5], 19079 * widthElements: [2.5, 7.5] 19080 * } 19081 * </pre> 19082 * <p>It's important to specify a correct <b>height and width</b> for the path as the scaling 19083 * is based on the ratio between the specified height and width in this object and the 19084 * height and width that is set as scale target (Note x,y coordinates will be scaled with 19085 * individual ratios).</p> 19086 * <p>The '<b>heightElements</b>' and '<b>widthElements</b>' array must contain the values that will be scaled. 19087 * The scaling is based on the computed ratios. 19088 * Coordinates on the y axis should be in the <b>heightElement</b>'s array, they will be scaled using 19089 * the computed ratio coefficient. 19090 * In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets. 19091 * <ul> 19092 * <li>The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....</li> 19093 * <li>The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....</li> 19094 * </ul> 19095 * The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index. 19096 * </p> 19097 */ 19098 this.pathMap = { 19099 'EVENT_MESSAGE': { 19100 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}', 19101 height: 36, 19102 width: 36, 19103 heightElements: [6, 14], 19104 widthElements: [10.5, 21] 19105 }, 19106 'EVENT_SIGNAL': { 19107 d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z', 19108 height: 36, 19109 width: 36, 19110 heightElements: [18], 19111 widthElements: [10, 20] 19112 }, 19113 'EVENT_ESCALATION': { 19114 d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z', 19115 height: 36, 19116 width: 36, 19117 heightElements: [20, 7], 19118 widthElements: [8] 19119 }, 19120 'EVENT_CONDITIONAL': { 19121 d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' + 19122 'M {e.x2},{e.y3} l {e.x0},0 ' + 19123 'M {e.x2},{e.y4} l {e.x0},0 ' + 19124 'M {e.x2},{e.y5} l {e.x0},0 ' + 19125 'M {e.x2},{e.y6} l {e.x0},0 ' + 19126 'M {e.x2},{e.y7} l {e.x0},0 ' + 19127 'M {e.x2},{e.y8} l {e.x0},0 ', 19128 height: 36, 19129 width: 36, 19130 heightElements: [8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5], 19131 widthElements: [10.5, 14.5, 12.5] 19132 }, 19133 'EVENT_LINK': { 19134 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', 19135 height: 36, 19136 width: 36, 19137 heightElements: [4.4375, 6.75, 7.8125], 19138 widthElements: [9.84375, 13.5] 19139 }, 19140 'EVENT_ERROR': { 19141 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', 19142 height: 36, 19143 width: 36, 19144 heightElements: [0.023, 8.737, 8.151, 16.564, 10.591, 8.714], 19145 widthElements: [0.085, 6.672, 6.97, 4.273, 5.337, 6.636] 19146 }, 19147 'EVENT_CANCEL_45': { 19148 d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' + 19149 '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z', 19150 height: 36, 19151 width: 36, 19152 heightElements: [4.75, 8.5], 19153 widthElements: [4.75, 8.5] 19154 }, 19155 'EVENT_COMPENSATION': { 19156 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', 19157 height: 36, 19158 width: 36, 19159 heightElements: [6.5, 13, 0.4, 6.1], 19160 widthElements: [9, 9.3, 8.7] 19161 }, 19162 'EVENT_TIMER_WH': { 19163 d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ', 19164 height: 36, 19165 width: 36, 19166 heightElements: [10, 2], 19167 widthElements: [3, 7] 19168 }, 19169 'EVENT_TIMER_LINE': { 19170 d: 'M {mx},{my} ' + 19171 'm {e.x0},{e.y0} l -{e.x1},{e.y1} ', 19172 height: 36, 19173 width: 36, 19174 heightElements: [10, 3], 19175 widthElements: [0, 0] 19176 }, 19177 'EVENT_MULTIPLE': { 19178 d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z', 19179 height: 36, 19180 width: 36, 19181 heightElements: [6.28099, 12.56199], 19182 widthElements: [3.1405, 9.42149, 12.56198] 19183 }, 19184 'EVENT_PARALLEL_MULTIPLE': { 19185 d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' + 19186 '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z', 19187 height: 36, 19188 width: 36, 19189 heightElements: [2.56228, 7.68683], 19190 widthElements: [2.56228, 7.68683] 19191 }, 19192 'GATEWAY_EXCLUSIVE': { 19193 d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' + 19194 '{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' + 19195 '{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z', 19196 height: 17.5, 19197 width: 17.5, 19198 heightElements: [8.5, 6.5312, -6.5312, -8.5], 19199 widthElements: [6.5, -6.5, 3, -3, 5, -5] 19200 }, 19201 'GATEWAY_PARALLEL': { 19202 d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' + 19203 '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z', 19204 height: 30, 19205 width: 30, 19206 heightElements: [5, 12.5], 19207 widthElements: [5, 12.5] 19208 }, 19209 'GATEWAY_EVENT_BASED': { 19210 d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z', 19211 height: 11, 19212 width: 11, 19213 heightElements: [-6, 6, 12, -12], 19214 widthElements: [9, -3, -12] 19215 }, 19216 'GATEWAY_COMPLEX': { 19217 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} ' + 19218 '{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} ' + 19219 '{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} ' + 19220 '-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z', 19221 height: 17.125, 19222 width: 17.125, 19223 heightElements: [4.875, 3.4375, 2.125, 3], 19224 widthElements: [3.4375, 2.125, 4.875, 3] 19225 }, 19226 'DATA_OBJECT_PATH': { 19227 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', 19228 height: 61, 19229 width: 51, 19230 heightElements: [10, 50, 60], 19231 widthElements: [10, 40, 50, 60] 19232 }, 19233 'DATA_OBJECT_COLLECTION_PATH': { 19234 d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10', 19235 height: 10, 19236 width: 10, 19237 heightElements: [], 19238 widthElements: [] 19239 }, 19240 'DATA_ARROW': { 19241 d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z', 19242 height: 61, 19243 width: 51, 19244 heightElements: [], 19245 widthElements: [] 19246 }, 19247 'DATA_STORE': { 19248 d:'m {mx},{my} ' + 19249 'l 0,{e.y2} ' + 19250 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' + 19251 'l 0,-{e.y2} ' + 19252 'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' + 19253 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' + 19254 'm -{e.x2},{e.y0}' + 19255 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' + 19256 'm -{e.x2},{e.y0}' + 19257 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0', 19258 height: 61, 19259 width: 61, 19260 heightElements: [7, 10, 45], 19261 widthElements: [2, 58, 60] 19262 }, 19263 'TEXT_ANNOTATION': { 19264 d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0', 19265 height: 30, 19266 width: 10, 19267 heightElements: [30], 19268 widthElements: [10] 19269 }, 19270 'MARKER_SUB_PROCESS': { 19271 d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0', 19272 height: 10, 19273 width: 10, 19274 heightElements: [], 19275 widthElements: [] 19276 }, 19277 'MARKER_PARALLEL': { 19278 d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10', 19279 height: 10, 19280 width: 10, 19281 heightElements: [], 19282 widthElements: [] 19283 }, 19284 'MARKER_SEQUENTIAL': { 19285 d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0', 19286 height: 10, 19287 width: 10, 19288 heightElements: [], 19289 widthElements: [] 19290 }, 19291 'MARKER_COMPENSATION': { 19292 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', 19293 height: 10, 19294 width: 21, 19295 heightElements: [], 19296 widthElements: [] 19297 }, 19298 'MARKER_LOOP': { 19299 d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' + 19300 '-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' + 19301 '0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' + 19302 'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902', 19303 height: 13.9, 19304 width: 13.7, 19305 heightElements: [], 19306 widthElements: [] 19307 }, 19308 'MARKER_ADHOC': { 19309 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 ' + 19310 '3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' + 19311 '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 ' + 19312 '-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' + 19313 '-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z', 19314 height: 4, 19315 width: 15, 19316 heightElements: [], 19317 widthElements: [] 19318 }, 19319 'TASK_TYPE_SEND': { 19320 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}', 19321 height: 14, 19322 width: 21, 19323 heightElements: [6, 14], 19324 widthElements: [10.5, 21] 19325 }, 19326 'TASK_TYPE_SCRIPT': { 19327 d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' + 19328 'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' + 19329 'm -7,-12 l 5,0 ' + 19330 'm -4.5,3 l 4.5,0 ' + 19331 'm -3,3 l 5,0' + 19332 'm -4,3 l 5,0', 19333 height: 15, 19334 width: 12.6, 19335 heightElements: [6, 14], 19336 widthElements: [10.5, 21] 19337 }, 19338 'TASK_TYPE_USER_1': { 19339 d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' + 19340 '-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' + 19341 '0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' + 19342 'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' + 19343 'm -8,6 l 0,5.5 m 11,0 l 0,-5' 19344 }, 19345 'TASK_TYPE_USER_2': { 19346 d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' + 19347 '-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 ' 19348 }, 19349 'TASK_TYPE_USER_3': { 19350 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 ' + 19351 '4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' + 19352 '-4.20799998,3.36699999 -4.20699998,4.34799999 z' 19353 }, 19354 'TASK_TYPE_MANUAL': { 19355 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 ' + 19356 '-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 ' + 19357 '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 ' + 19358 '-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 ' + 19359 '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 ' + 19360 '-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' + 19361 '2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' + 19362 '-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' + 19363 '-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 ' + 19364 '-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 ' + 19365 '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 ' + 19366 '-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z' 19367 }, 19368 'TASK_TYPE_INSTANTIATING_SEND': { 19369 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' 19370 }, 19371 'TASK_TYPE_SERVICE': { 19372 d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' + 19373 '0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' + 19374 '-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' + 19375 'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' + 19376 '-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' + 19377 '-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' + 19378 '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 ' + 19379 '-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' + 19380 'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' + 19381 'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' + 19382 '0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' + 19383 'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' + 19384 'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' + 19385 '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' + 19386 '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z' 19387 }, 19388 'TASK_TYPE_SERVICE_FILL': { 19389 d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' + 19390 '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' + 19391 '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z' 19392 }, 19393 'TASK_TYPE_BUSINESS_RULE_HEADER': { 19394 d: 'm {mx},{my} 0,4 20,0 0,-4 z' 19395 }, 19396 'TASK_TYPE_BUSINESS_RULE_MAIN': { 19397 d: 'm {mx},{my} 0,12 20,0 0,-12 z' + 19398 'm 0,8 l 20,0 ' + 19399 'm -13,-4 l 0,8' 19400 }, 19401 'MESSAGE_FLOW_MARKER': { 19402 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' 19403 } 19404 }; 19405 19406 this.getRawPath = function getRawPath(pathId) { 19407 return this.pathMap[pathId].d; 19408 }; 19409 19410 /** 19411 * Scales the path to the given height and width. 19412 * <h1>Use case</h1> 19413 * <p>Use case is to scale the content of elements (event, gateways) based 19414 * on the element bounding box's size. 19415 * </p> 19416 * <h1>Why not transform</h1> 19417 * <p>Scaling a path with transform() will also scale the stroke and IE does not support 19418 * the option 'non-scaling-stroke' to prevent this. 19419 * Also there are use cases where only some parts of a path should be 19420 * scaled.</p> 19421 * 19422 * @param {string} pathId The ID of the path. 19423 * @param {Object} param <p> 19424 * Example param object scales the path to 60% size of the container (data.width, data.height). 19425 * <pre> 19426 * { 19427 * xScaleFactor: 0.6, 19428 * yScaleFactor:0.6, 19429 * containerWidth: data.width, 19430 * containerHeight: data.height, 19431 * position: { 19432 * mx: 0.46, 19433 * my: 0.2, 19434 * } 19435 * } 19436 * </pre> 19437 * <ul> 19438 * <li>targetpathwidth = xScaleFactor * containerWidth</li> 19439 * <li>targetpathheight = yScaleFactor * containerHeight</li> 19440 * <li>Position is used to set the starting coordinate of the path. M is computed: 19441 * <ul> 19442 * <li>position.x * containerWidth</li> 19443 * <li>position.y * containerHeight</li> 19444 * </ul> 19445 * Center of the container <pre> position: { 19446 * mx: 0.5, 19447 * my: 0.5, 19448 * }</pre> 19449 * Upper left corner of the container 19450 * <pre> position: { 19451 * mx: 0.0, 19452 * my: 0.0, 19453 * }</pre> 19454 * </li> 19455 * </ul> 19456 * </p> 19457 * 19458 */ 19459 this.getScaledPath = function getScaledPath(pathId, param) { 19460 var rawPath = this.pathMap[pathId]; 19461 19462 // positioning 19463 // compute the start point of the path 19464 var mx, my; 19465 19466 if (param.abspos) { 19467 mx = param.abspos.x; 19468 my = param.abspos.y; 19469 } else { 19470 mx = param.containerWidth * param.position.mx; 19471 my = param.containerHeight * param.position.my; 19472 } 19473 19474 var coordinates = {}; // map for the scaled coordinates 19475 if (param.position) { 19476 19477 // path 19478 var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor; 19479 var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor; 19480 19481 19482 // Apply height ratio 19483 for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) { 19484 coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio; 19485 } 19486 19487 // Apply width ratio 19488 for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) { 19489 coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio; 19490 } 19491 } 19492 19493 // Apply value to raw path 19494 var path = format( 19495 rawPath.d, { 19496 mx: mx, 19497 my: my, 19498 e: coordinates 19499 } 19500 ); 19501 return path; 19502 }; 19503 } 19504 19505 // helpers ////////////////////// 19506 19507 // copied and adjusted from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js 19508 var tokenRegex = /\{([^{}]+)\}/g, 19509 objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties 19510 19511 function replacer(all, key, obj) { 19512 var res = obj; 19513 key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) { 19514 name = name || quotedName; 19515 if (res) { 19516 if (name in res) { 19517 res = res[name]; 19518 } 19519 typeof res == 'function' && isFunc && (res = res()); 19520 } 19521 }); 19522 res = (res == null || res == obj ? all : res) + ''; 19523 19524 return res; 19525 } 19526 19527 function format(str, obj) { 19528 return String(str).replace(tokenRegex, function(all, key) { 19529 return replacer(all, key, obj); 19530 }); 19531 } 19532 19533 var DrawModule = { 19534 __init__: [ 'bpmnRenderer' ], 19535 bpmnRenderer: [ 'type', BpmnRenderer ], 19536 textRenderer: [ 'type', TextRenderer ], 19537 pathMap: [ 'type', PathMap ] 19538 }; 19539 19540 /** 19541 * A simple translation stub to be used for multi-language support 19542 * in diagrams. Can be easily replaced with a more sophisticated 19543 * solution. 19544 * 19545 * @example 19546 * 19547 * // use it inside any diagram component by injecting `translate`. 19548 * 19549 * function MyService(translate) { 19550 * alert(translate('HELLO {you}', { you: 'You!' })); 19551 * } 19552 * 19553 * @param {string} template to interpolate 19554 * @param {Object} [replacements] a map with substitutes 19555 * 19556 * @return {string} the translated string 19557 */ 19558 function translate$1(template, replacements) { 19559 19560 replacements = replacements || {}; 19561 19562 return template.replace(/{([^}]+)}/g, function(_, key) { 19563 return replacements[key] || '{' + key + '}'; 19564 }); 19565 } 19566 19567 var translate = { 19568 translate: [ 'value', translate$1 ] 19569 }; 19570 19571 var DEFAULT_LABEL_SIZE = { 19572 width: 90, 19573 height: 20 19574 }; 19575 19576 var FLOW_LABEL_INDENT = 15; 19577 19578 19579 /** 19580 * Returns true if the given semantic has an external label 19581 * 19582 * @param {BpmnElement} semantic 19583 * @return {boolean} true if has label 19584 */ 19585 function isLabelExternal(semantic) { 19586 return is$1(semantic, 'bpmn:Event') || 19587 is$1(semantic, 'bpmn:Gateway') || 19588 is$1(semantic, 'bpmn:DataStoreReference') || 19589 is$1(semantic, 'bpmn:DataObjectReference') || 19590 is$1(semantic, 'bpmn:DataInput') || 19591 is$1(semantic, 'bpmn:DataOutput') || 19592 is$1(semantic, 'bpmn:SequenceFlow') || 19593 is$1(semantic, 'bpmn:MessageFlow') || 19594 is$1(semantic, 'bpmn:Group'); 19595 } 19596 19597 /** 19598 * Returns true if the given element has an external label 19599 * 19600 * @param {djs.model.shape} element 19601 * @return {boolean} true if has label 19602 */ 19603 function hasExternalLabel(element) { 19604 return isLabel$6(element.label); 19605 } 19606 19607 /** 19608 * Get the position for sequence flow labels 19609 * 19610 * @param {Array<Point>} waypoints 19611 * @return {Point} the label position 19612 */ 19613 function getFlowLabelPosition(waypoints) { 19614 19615 // get the waypoints mid 19616 var mid = waypoints.length / 2 - 1; 19617 19618 var first = waypoints[Math.floor(mid)]; 19619 var second = waypoints[Math.ceil(mid + 0.01)]; 19620 19621 // get position 19622 var position = getWaypointsMid(waypoints); 19623 19624 // calculate angle 19625 var angle = Math.atan((second.y - first.y) / (second.x - first.x)); 19626 19627 var x = position.x, 19628 y = position.y; 19629 19630 if (Math.abs(angle) < Math.PI / 2) { 19631 y -= FLOW_LABEL_INDENT; 19632 } else { 19633 x += FLOW_LABEL_INDENT; 19634 } 19635 19636 return { x: x, y: y }; 19637 } 19638 19639 19640 /** 19641 * Get the middle of a number of waypoints 19642 * 19643 * @param {Array<Point>} waypoints 19644 * @return {Point} the mid point 19645 */ 19646 function getWaypointsMid(waypoints) { 19647 19648 var mid = waypoints.length / 2 - 1; 19649 19650 var first = waypoints[Math.floor(mid)]; 19651 var second = waypoints[Math.ceil(mid + 0.01)]; 19652 19653 return { 19654 x: first.x + (second.x - first.x) / 2, 19655 y: first.y + (second.y - first.y) / 2 19656 }; 19657 } 19658 19659 19660 function getExternalLabelMid(element) { 19661 19662 if (element.waypoints) { 19663 return getFlowLabelPosition(element.waypoints); 19664 } else if (is$1(element, 'bpmn:Group')) { 19665 return { 19666 x: element.x + element.width / 2, 19667 y: element.y + DEFAULT_LABEL_SIZE.height / 2 19668 }; 19669 } else { 19670 return { 19671 x: element.x + element.width / 2, 19672 y: element.y + element.height + DEFAULT_LABEL_SIZE.height / 2 19673 }; 19674 } 19675 } 19676 19677 19678 /** 19679 * Returns the bounds of an elements label, parsed from the elements DI or 19680 * generated from its bounds. 19681 * 19682 * @param {BpmnElement} semantic 19683 * @param {djs.model.Base} element 19684 */ 19685 function getExternalLabelBounds(semantic, element) { 19686 19687 var mid, 19688 size, 19689 bounds, 19690 di = semantic.di, 19691 label = di.label; 19692 19693 if (label && label.bounds) { 19694 bounds = label.bounds; 19695 19696 size = { 19697 width: Math.max(DEFAULT_LABEL_SIZE.width, bounds.width), 19698 height: bounds.height 19699 }; 19700 19701 mid = { 19702 x: bounds.x + bounds.width / 2, 19703 y: bounds.y + bounds.height / 2 19704 }; 19705 } else { 19706 19707 mid = getExternalLabelMid(element); 19708 19709 size = DEFAULT_LABEL_SIZE; 19710 } 19711 19712 return assign({ 19713 x: mid.x - size.width / 2, 19714 y: mid.y - size.height / 2 19715 }, size); 19716 } 19717 19718 function isLabel$6(element) { 19719 return element && !!element.labelTarget; 19720 } 19721 19722 function elementData(semantic, attrs) { 19723 return assign({ 19724 id: semantic.id, 19725 type: semantic.$type, 19726 businessObject: semantic 19727 }, attrs); 19728 } 19729 19730 function getWaypoints(bo, source, target) { 19731 19732 var waypoints = bo.di.waypoint; 19733 19734 if (!waypoints || waypoints.length < 2) { 19735 return [ getMid(source), getMid(target) ]; 19736 } 19737 19738 return waypoints.map(function(p) { 19739 return { x: p.x, y: p.y }; 19740 }); 19741 } 19742 19743 function notYetDrawn(translate, semantic, refSemantic, property) { 19744 return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', { 19745 element: elementToString(refSemantic), 19746 referenced: elementToString(semantic), 19747 property: property 19748 })); 19749 } 19750 19751 19752 /** 19753 * An importer that adds bpmn elements to the canvas 19754 * 19755 * @param {EventBus} eventBus 19756 * @param {Canvas} canvas 19757 * @param {ElementFactory} elementFactory 19758 * @param {ElementRegistry} elementRegistry 19759 * @param {Function} translate 19760 * @param {TextRenderer} textRenderer 19761 */ 19762 function BpmnImporter( 19763 eventBus, canvas, elementFactory, 19764 elementRegistry, translate, textRenderer) { 19765 19766 this._eventBus = eventBus; 19767 this._canvas = canvas; 19768 this._elementFactory = elementFactory; 19769 this._elementRegistry = elementRegistry; 19770 this._translate = translate; 19771 this._textRenderer = textRenderer; 19772 } 19773 19774 BpmnImporter.$inject = [ 19775 'eventBus', 19776 'canvas', 19777 'elementFactory', 19778 'elementRegistry', 19779 'translate', 19780 'textRenderer' 19781 ]; 19782 19783 19784 /** 19785 * Add bpmn element (semantic) to the canvas onto the 19786 * specified parent shape. 19787 */ 19788 BpmnImporter.prototype.add = function(semantic, parentElement) { 19789 19790 var di = semantic.di, 19791 element, 19792 translate = this._translate, 19793 hidden; 19794 19795 var parentIndex; 19796 19797 // ROOT ELEMENT 19798 // handle the special case that we deal with a 19799 // invisible root element (process or collaboration) 19800 if (is$1(di, 'bpmndi:BPMNPlane')) { 19801 19802 // add a virtual element (not being drawn) 19803 element = this._elementFactory.createRoot(elementData(semantic)); 19804 19805 this._canvas.setRootElement(element); 19806 } 19807 19808 // SHAPE 19809 else if (is$1(di, 'bpmndi:BPMNShape')) { 19810 19811 var collapsed = !isExpanded(semantic), 19812 isFrame = isFrameElement(semantic); 19813 hidden = parentElement && (parentElement.hidden || parentElement.collapsed); 19814 19815 var bounds = semantic.di.bounds; 19816 19817 element = this._elementFactory.createShape(elementData(semantic, { 19818 collapsed: collapsed, 19819 hidden: hidden, 19820 x: Math.round(bounds.x), 19821 y: Math.round(bounds.y), 19822 width: Math.round(bounds.width), 19823 height: Math.round(bounds.height), 19824 isFrame: isFrame 19825 })); 19826 19827 if (is$1(semantic, 'bpmn:BoundaryEvent')) { 19828 this._attachBoundary(semantic, element); 19829 } 19830 19831 // insert lanes behind other flow nodes (cf. #727) 19832 if (is$1(semantic, 'bpmn:Lane')) { 19833 parentIndex = 0; 19834 } 19835 19836 if (is$1(semantic, 'bpmn:DataStoreReference')) { 19837 19838 // check whether data store is inside our outside of its semantic parent 19839 if (!isPointInsideBBox$1(parentElement, getMid(bounds))) { 19840 parentElement = this._canvas.getRootElement(); 19841 } 19842 } 19843 19844 this._canvas.addShape(element, parentElement, parentIndex); 19845 } 19846 19847 // CONNECTION 19848 else if (is$1(di, 'bpmndi:BPMNEdge')) { 19849 19850 var source = this._getSource(semantic), 19851 target = this._getTarget(semantic); 19852 19853 hidden = parentElement && (parentElement.hidden || parentElement.collapsed); 19854 19855 element = this._elementFactory.createConnection(elementData(semantic, { 19856 hidden: hidden, 19857 source: source, 19858 target: target, 19859 waypoints: getWaypoints(semantic, source, target) 19860 })); 19861 19862 if (is$1(semantic, 'bpmn:DataAssociation')) { 19863 19864 // render always on top; this ensures DataAssociations 19865 // are rendered correctly across different "hacks" people 19866 // love to model such as cross participant / sub process 19867 // associations 19868 parentElement = null; 19869 } 19870 19871 // insert sequence flows behind other flow nodes (cf. #727) 19872 if (is$1(semantic, 'bpmn:SequenceFlow')) { 19873 parentIndex = 0; 19874 } 19875 19876 this._canvas.addConnection(element, parentElement, parentIndex); 19877 } else { 19878 throw new Error(translate('unknown di {di} for element {semantic}', { 19879 di: elementToString(di), 19880 semantic: elementToString(semantic) 19881 })); 19882 } 19883 19884 // (optional) LABEL 19885 if (isLabelExternal(semantic) && getLabel(element)) { 19886 this.addLabel(semantic, element); 19887 } 19888 19889 19890 this._eventBus.fire('bpmnElement.added', { element: element }); 19891 19892 return element; 19893 }; 19894 19895 19896 /** 19897 * Attach the boundary element to the given host 19898 * 19899 * @param {ModdleElement} boundarySemantic 19900 * @param {djs.model.Base} boundaryElement 19901 */ 19902 BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) { 19903 var translate = this._translate; 19904 var hostSemantic = boundarySemantic.attachedToRef; 19905 19906 if (!hostSemantic) { 19907 throw new Error(translate('missing {semantic}#attachedToRef', { 19908 semantic: elementToString(boundarySemantic) 19909 })); 19910 } 19911 19912 var host = this._elementRegistry.get(hostSemantic.id), 19913 attachers = host && host.attachers; 19914 19915 if (!host) { 19916 throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef'); 19917 } 19918 19919 // wire element.host <> host.attachers 19920 boundaryElement.host = host; 19921 19922 if (!attachers) { 19923 host.attachers = attachers = []; 19924 } 19925 19926 if (attachers.indexOf(boundaryElement) === -1) { 19927 attachers.push(boundaryElement); 19928 } 19929 }; 19930 19931 19932 /** 19933 * add label for an element 19934 */ 19935 BpmnImporter.prototype.addLabel = function(semantic, element) { 19936 var bounds, 19937 text, 19938 label; 19939 19940 bounds = getExternalLabelBounds(semantic, element); 19941 19942 text = getLabel(element); 19943 19944 if (text) { 19945 19946 // get corrected bounds from actual layouted text 19947 bounds = this._textRenderer.getExternalLabelBounds(bounds, text); 19948 } 19949 19950 label = this._elementFactory.createLabel(elementData(semantic, { 19951 id: semantic.id + '_label', 19952 labelTarget: element, 19953 type: 'label', 19954 hidden: element.hidden || !getLabel(element), 19955 x: Math.round(bounds.x), 19956 y: Math.round(bounds.y), 19957 width: Math.round(bounds.width), 19958 height: Math.round(bounds.height) 19959 })); 19960 19961 return this._canvas.addShape(label, element.parent); 19962 }; 19963 19964 /** 19965 * Return the drawn connection end based on the given side. 19966 * 19967 * @throws {Error} if the end is not yet drawn 19968 */ 19969 BpmnImporter.prototype._getEnd = function(semantic, side) { 19970 19971 var element, 19972 refSemantic, 19973 type = semantic.$type, 19974 translate = this._translate; 19975 19976 refSemantic = semantic[side + 'Ref']; 19977 19978 // handle mysterious isMany DataAssociation#sourceRef 19979 if (side === 'source' && type === 'bpmn:DataInputAssociation') { 19980 refSemantic = refSemantic && refSemantic[0]; 19981 } 19982 19983 // fix source / target for DataInputAssociation / DataOutputAssociation 19984 if (side === 'source' && type === 'bpmn:DataOutputAssociation' || 19985 side === 'target' && type === 'bpmn:DataInputAssociation') { 19986 19987 refSemantic = semantic.$parent; 19988 } 19989 19990 element = refSemantic && this._getElement(refSemantic); 19991 19992 if (element) { 19993 return element; 19994 } 19995 19996 if (refSemantic) { 19997 throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref'); 19998 } else { 19999 throw new Error(translate('{semantic}#{side} Ref not specified', { 20000 semantic: elementToString(semantic), 20001 side: side 20002 })); 20003 } 20004 }; 20005 20006 BpmnImporter.prototype._getSource = function(semantic) { 20007 return this._getEnd(semantic, 'source'); 20008 }; 20009 20010 BpmnImporter.prototype._getTarget = function(semantic) { 20011 return this._getEnd(semantic, 'target'); 20012 }; 20013 20014 20015 BpmnImporter.prototype._getElement = function(semantic) { 20016 return this._elementRegistry.get(semantic.id); 20017 }; 20018 20019 20020 // helpers //////////////////// 20021 20022 function isPointInsideBBox$1(bbox, point) { 20023 var x = point.x, 20024 y = point.y; 20025 20026 return x >= bbox.x && 20027 x <= bbox.x + bbox.width && 20028 y >= bbox.y && 20029 y <= bbox.y + bbox.height; 20030 } 20031 20032 function isFrameElement(semantic) { 20033 return is$1(semantic, 'bpmn:Group'); 20034 } 20035 20036 var ImportModule = { 20037 __depends__: [ 20038 translate 20039 ], 20040 bpmnImporter: [ 'type', BpmnImporter ] 20041 }; 20042 20043 var CoreModule = { 20044 __depends__: [ 20045 DrawModule, 20046 ImportModule 20047 ] 20048 }; 20049 20050 function __stopPropagation(event) { 20051 if (!event || typeof event.stopPropagation !== 'function') { 20052 return; 20053 } 20054 20055 event.stopPropagation(); 20056 } 20057 20058 20059 function getOriginal$1(event) { 20060 return event.originalEvent || event.srcEvent; 20061 } 20062 20063 20064 function stopPropagation$1(event, immediate) { 20065 __stopPropagation(event); 20066 __stopPropagation(getOriginal$1(event)); 20067 } 20068 20069 20070 function toPoint(event) { 20071 20072 if (event.pointers && event.pointers.length) { 20073 event = event.pointers[0]; 20074 } 20075 20076 if (event.touches && event.touches.length) { 20077 event = event.touches[0]; 20078 } 20079 20080 return event ? { 20081 x: event.clientX, 20082 y: event.clientY 20083 } : null; 20084 } 20085 20086 function isMac() { 20087 return (/mac/i).test(navigator.platform); 20088 } 20089 20090 function isButton(event, button) { 20091 return (getOriginal$1(event) || event).button === button; 20092 } 20093 20094 function isPrimaryButton(event) { 20095 20096 // button === 0 -> left áka primary mouse button 20097 return isButton(event, 0); 20098 } 20099 20100 function isAuxiliaryButton(event) { 20101 20102 // button === 1 -> auxiliary áka wheel button 20103 return isButton(event, 1); 20104 } 20105 20106 function hasPrimaryModifier(event) { 20107 var originalEvent = getOriginal$1(event) || event; 20108 20109 if (!isPrimaryButton(event)) { 20110 return false; 20111 } 20112 20113 // Use cmd as primary modifier key for mac OS 20114 if (isMac()) { 20115 return originalEvent.metaKey; 20116 } else { 20117 return originalEvent.ctrlKey; 20118 } 20119 } 20120 20121 20122 function hasSecondaryModifier(event) { 20123 var originalEvent = getOriginal$1(event) || event; 20124 20125 return isPrimaryButton(event) && originalEvent.shiftKey; 20126 } 20127 20128 function allowAll(event) { return true; } 20129 20130 function allowPrimaryAndAuxiliary(event) { 20131 return isPrimaryButton(event) || isAuxiliaryButton(event); 20132 } 20133 20134 var LOW_PRIORITY$m = 500; 20135 20136 20137 /** 20138 * A plugin that provides interaction events for diagram elements. 20139 * 20140 * It emits the following events: 20141 * 20142 * * element.click 20143 * * element.contextmenu 20144 * * element.dblclick 20145 * * element.hover 20146 * * element.mousedown 20147 * * element.mousemove 20148 * * element.mouseup 20149 * * element.out 20150 * 20151 * Each event is a tuple { element, gfx, originalEvent }. 20152 * 20153 * Canceling the event via Event#preventDefault() 20154 * prevents the original DOM operation. 20155 * 20156 * @param {EventBus} eventBus 20157 */ 20158 function InteractionEvents(eventBus, elementRegistry, styles) { 20159 20160 var self = this; 20161 20162 /** 20163 * Fire an interaction event. 20164 * 20165 * @param {string} type local event name, e.g. element.click. 20166 * @param {DOMEvent} event native event 20167 * @param {djs.model.Base} [element] the diagram element to emit the event on; 20168 * defaults to the event target 20169 */ 20170 function fire(type, event, element) { 20171 20172 if (isIgnored(type, event)) { 20173 return; 20174 } 20175 20176 var target, gfx, returnValue; 20177 20178 if (!element) { 20179 target = event.delegateTarget || event.target; 20180 20181 if (target) { 20182 gfx = target; 20183 element = elementRegistry.get(gfx); 20184 } 20185 } else { 20186 gfx = elementRegistry.getGraphics(element); 20187 } 20188 20189 if (!gfx || !element) { 20190 return; 20191 } 20192 20193 returnValue = eventBus.fire(type, { 20194 element: element, 20195 gfx: gfx, 20196 originalEvent: event 20197 }); 20198 20199 if (returnValue === false) { 20200 event.stopPropagation(); 20201 event.preventDefault(); 20202 } 20203 } 20204 20205 // TODO(nikku): document this 20206 var handlers = {}; 20207 20208 function mouseHandler(localEventName) { 20209 return handlers[localEventName]; 20210 } 20211 20212 function isIgnored(localEventName, event) { 20213 20214 var filter = ignoredFilters[localEventName] || isPrimaryButton; 20215 20216 // only react on left mouse button interactions 20217 // except for interaction events that are enabled 20218 // for secundary mouse button 20219 return !filter(event); 20220 } 20221 20222 var bindings = { 20223 click: 'element.click', 20224 contextmenu: 'element.contextmenu', 20225 dblclick: 'element.dblclick', 20226 mousedown: 'element.mousedown', 20227 mousemove: 'element.mousemove', 20228 mouseover: 'element.hover', 20229 mouseout: 'element.out', 20230 mouseup: 'element.mouseup', 20231 }; 20232 20233 var ignoredFilters = { 20234 'element.contextmenu': allowAll, 20235 'element.mousedown': allowPrimaryAndAuxiliary, 20236 'element.mouseup': allowPrimaryAndAuxiliary, 20237 'element.click': allowPrimaryAndAuxiliary, 20238 'element.dblclick': allowPrimaryAndAuxiliary 20239 }; 20240 20241 20242 // manual event trigger ////////// 20243 20244 /** 20245 * Trigger an interaction event (based on a native dom event) 20246 * on the target shape or connection. 20247 * 20248 * @param {string} eventName the name of the triggered DOM event 20249 * @param {MouseEvent} event 20250 * @param {djs.model.Base} targetElement 20251 */ 20252 function triggerMouseEvent(eventName, event, targetElement) { 20253 20254 // i.e. element.mousedown... 20255 var localEventName = bindings[eventName]; 20256 20257 if (!localEventName) { 20258 throw new Error('unmapped DOM event name <' + eventName + '>'); 20259 } 20260 20261 return fire(localEventName, event, targetElement); 20262 } 20263 20264 20265 var ELEMENT_SELECTOR = 'svg, .djs-element'; 20266 20267 // event handling /////// 20268 20269 function registerEvent(node, event, localEvent, ignoredFilter) { 20270 20271 var handler = handlers[localEvent] = function(event) { 20272 fire(localEvent, event); 20273 }; 20274 20275 if (ignoredFilter) { 20276 ignoredFilters[localEvent] = ignoredFilter; 20277 } 20278 20279 handler.$delegate = delegate.bind(node, ELEMENT_SELECTOR, event, handler); 20280 } 20281 20282 function unregisterEvent(node, event, localEvent) { 20283 20284 var handler = mouseHandler(localEvent); 20285 20286 if (!handler) { 20287 return; 20288 } 20289 20290 delegate.unbind(node, event, handler.$delegate); 20291 } 20292 20293 function registerEvents(svg) { 20294 forEach(bindings, function(val, key) { 20295 registerEvent(svg, key, val); 20296 }); 20297 } 20298 20299 function unregisterEvents(svg) { 20300 forEach(bindings, function(val, key) { 20301 unregisterEvent(svg, key, val); 20302 }); 20303 } 20304 20305 eventBus.on('canvas.destroy', function(event) { 20306 unregisterEvents(event.svg); 20307 }); 20308 20309 eventBus.on('canvas.init', function(event) { 20310 registerEvents(event.svg); 20311 }); 20312 20313 20314 // hit box updating //////////////// 20315 20316 eventBus.on([ 'shape.added', 'connection.added' ], function(event) { 20317 var element = event.element, 20318 gfx = event.gfx; 20319 20320 eventBus.fire('interactionEvents.createHit', { element: element, gfx: gfx }); 20321 }); 20322 20323 // Update djs-hit on change. 20324 // A low priortity is necessary, because djs-hit of labels has to be updated 20325 // after the label bounds have been updated in the renderer. 20326 eventBus.on([ 20327 'shape.changed', 20328 'connection.changed' 20329 ], LOW_PRIORITY$m, function(event) { 20330 20331 var element = event.element, 20332 gfx = event.gfx; 20333 20334 eventBus.fire('interactionEvents.updateHit', { element: element, gfx: gfx }); 20335 }); 20336 20337 eventBus.on('interactionEvents.createHit', LOW_PRIORITY$m, function(event) { 20338 var element = event.element, 20339 gfx = event.gfx; 20340 20341 self.createDefaultHit(element, gfx); 20342 }); 20343 20344 eventBus.on('interactionEvents.updateHit', function(event) { 20345 var element = event.element, 20346 gfx = event.gfx; 20347 20348 self.updateDefaultHit(element, gfx); 20349 }); 20350 20351 20352 // hit styles //////////// 20353 20354 var STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-stroke'); 20355 20356 var CLICK_STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-click-stroke'); 20357 20358 var ALL_HIT_STYLE = createHitStyle('djs-hit djs-hit-all'); 20359 20360 var HIT_TYPES = { 20361 'all': ALL_HIT_STYLE, 20362 'click-stroke': CLICK_STROKE_HIT_STYLE, 20363 'stroke': STROKE_HIT_STYLE 20364 }; 20365 20366 function createHitStyle(classNames, attrs) { 20367 20368 attrs = assign({ 20369 stroke: 'white', 20370 strokeWidth: 15 20371 }, attrs || {}); 20372 20373 return styles.cls(classNames, [ 'no-fill', 'no-border' ], attrs); 20374 } 20375 20376 20377 // style helpers /////////////// 20378 20379 function applyStyle(hit, type) { 20380 20381 var attrs = HIT_TYPES[type]; 20382 20383 if (!attrs) { 20384 throw new Error('invalid hit type <' + type + '>'); 20385 } 20386 20387 attr(hit, attrs); 20388 20389 return hit; 20390 } 20391 20392 function appendHit(gfx, hit) { 20393 append(gfx, hit); 20394 } 20395 20396 20397 // API 20398 20399 /** 20400 * Remove hints on the given graphics. 20401 * 20402 * @param {SVGElement} gfx 20403 */ 20404 this.removeHits = function(gfx) { 20405 var hits = all('.djs-hit', gfx); 20406 20407 forEach(hits, remove$1); 20408 }; 20409 20410 /** 20411 * Create default hit for the given element. 20412 * 20413 * @param {djs.model.Base} element 20414 * @param {SVGElement} gfx 20415 * 20416 * @return {SVGElement} created hit 20417 */ 20418 this.createDefaultHit = function(element, gfx) { 20419 var waypoints = element.waypoints, 20420 isFrame = element.isFrame, 20421 boxType; 20422 20423 if (waypoints) { 20424 return this.createWaypointsHit(gfx, waypoints); 20425 } else { 20426 20427 boxType = isFrame ? 'stroke' : 'all'; 20428 20429 return this.createBoxHit(gfx, boxType, { 20430 width: element.width, 20431 height: element.height 20432 }); 20433 } 20434 }; 20435 20436 /** 20437 * Create hits for the given waypoints. 20438 * 20439 * @param {SVGElement} gfx 20440 * @param {Array<Point>} waypoints 20441 * 20442 * @return {SVGElement} 20443 */ 20444 this.createWaypointsHit = function(gfx, waypoints) { 20445 20446 var hit = createLine(waypoints); 20447 20448 applyStyle(hit, 'stroke'); 20449 20450 appendHit(gfx, hit); 20451 20452 return hit; 20453 }; 20454 20455 /** 20456 * Create hits for a box. 20457 * 20458 * @param {SVGElement} gfx 20459 * @param {string} hitType 20460 * @param {Object} attrs 20461 * 20462 * @return {SVGElement} 20463 */ 20464 this.createBoxHit = function(gfx, type, attrs) { 20465 20466 attrs = assign({ 20467 x: 0, 20468 y: 0 20469 }, attrs); 20470 20471 var hit = create$1('rect'); 20472 20473 applyStyle(hit, type); 20474 20475 attr(hit, attrs); 20476 20477 appendHit(gfx, hit); 20478 20479 return hit; 20480 }; 20481 20482 /** 20483 * Update default hit of the element. 20484 * 20485 * @param {djs.model.Base} element 20486 * @param {SVGElement} gfx 20487 * 20488 * @return {SVGElement} updated hit 20489 */ 20490 this.updateDefaultHit = function(element, gfx) { 20491 20492 var hit = query('.djs-hit', gfx); 20493 20494 if (!hit) { 20495 return; 20496 } 20497 20498 if (element.waypoints) { 20499 updateLine(hit, element.waypoints); 20500 } else { 20501 attr(hit, { 20502 width: element.width, 20503 height: element.height 20504 }); 20505 } 20506 20507 return hit; 20508 }; 20509 20510 this.fire = fire; 20511 20512 this.triggerMouseEvent = triggerMouseEvent; 20513 20514 this.mouseHandler = mouseHandler; 20515 20516 this.registerEvent = registerEvent; 20517 this.unregisterEvent = unregisterEvent; 20518 } 20519 20520 20521 InteractionEvents.$inject = [ 20522 'eventBus', 20523 'elementRegistry', 20524 'styles' 20525 ]; 20526 20527 20528 /** 20529 * An event indicating that the mouse hovered over an element 20530 * 20531 * @event element.hover 20532 * 20533 * @type {Object} 20534 * @property {djs.model.Base} element 20535 * @property {SVGElement} gfx 20536 * @property {Event} originalEvent 20537 */ 20538 20539 /** 20540 * An event indicating that the mouse has left an element 20541 * 20542 * @event element.out 20543 * 20544 * @type {Object} 20545 * @property {djs.model.Base} element 20546 * @property {SVGElement} gfx 20547 * @property {Event} originalEvent 20548 */ 20549 20550 /** 20551 * An event indicating that the mouse has clicked an element 20552 * 20553 * @event element.click 20554 * 20555 * @type {Object} 20556 * @property {djs.model.Base} element 20557 * @property {SVGElement} gfx 20558 * @property {Event} originalEvent 20559 */ 20560 20561 /** 20562 * An event indicating that the mouse has double clicked an element 20563 * 20564 * @event element.dblclick 20565 * 20566 * @type {Object} 20567 * @property {djs.model.Base} element 20568 * @property {SVGElement} gfx 20569 * @property {Event} originalEvent 20570 */ 20571 20572 /** 20573 * An event indicating that the mouse has gone down on an element. 20574 * 20575 * @event element.mousedown 20576 * 20577 * @type {Object} 20578 * @property {djs.model.Base} element 20579 * @property {SVGElement} gfx 20580 * @property {Event} originalEvent 20581 */ 20582 20583 /** 20584 * An event indicating that the mouse has gone up on an element. 20585 * 20586 * @event element.mouseup 20587 * 20588 * @type {Object} 20589 * @property {djs.model.Base} element 20590 * @property {SVGElement} gfx 20591 * @property {Event} originalEvent 20592 */ 20593 20594 /** 20595 * An event indicating that the context menu action is triggered 20596 * via mouse or touch controls. 20597 * 20598 * @event element.contextmenu 20599 * 20600 * @type {Object} 20601 * @property {djs.model.Base} element 20602 * @property {SVGElement} gfx 20603 * @property {Event} originalEvent 20604 */ 20605 20606 var InteractionEventsModule$1 = { 20607 __init__: [ 'interactionEvents' ], 20608 interactionEvents: [ 'type', InteractionEvents ] 20609 }; 20610 20611 var LOW_PRIORITY$l = 500; 20612 20613 20614 /** 20615 * @class 20616 * 20617 * A plugin that adds an outline to shapes and connections that may be activated and styled 20618 * via CSS classes. 20619 * 20620 * @param {EventBus} eventBus 20621 * @param {Styles} styles 20622 * @param {ElementRegistry} elementRegistry 20623 */ 20624 function Outline(eventBus, styles, elementRegistry) { 20625 20626 this.offset = 6; 20627 20628 var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]); 20629 20630 var self = this; 20631 20632 function createOutline(gfx, bounds) { 20633 var outline = create$1('rect'); 20634 20635 attr(outline, assign({ 20636 x: 10, 20637 y: 10, 20638 width: 100, 20639 height: 100 20640 }, OUTLINE_STYLE)); 20641 20642 append(gfx, outline); 20643 20644 return outline; 20645 } 20646 20647 // A low priortity is necessary, because outlines of labels have to be updated 20648 // after the label bounds have been updated in the renderer. 20649 eventBus.on([ 'shape.added', 'shape.changed' ], LOW_PRIORITY$l, function(event) { 20650 var element = event.element, 20651 gfx = event.gfx; 20652 20653 var outline = query('.djs-outline', gfx); 20654 20655 if (!outline) { 20656 outline = createOutline(gfx); 20657 } 20658 20659 self.updateShapeOutline(outline, element); 20660 }); 20661 20662 eventBus.on([ 'connection.added', 'connection.changed' ], function(event) { 20663 var element = event.element, 20664 gfx = event.gfx; 20665 20666 var outline = query('.djs-outline', gfx); 20667 20668 if (!outline) { 20669 outline = createOutline(gfx); 20670 } 20671 20672 self.updateConnectionOutline(outline, element); 20673 }); 20674 } 20675 20676 20677 /** 20678 * Updates the outline of a shape respecting the dimension of the 20679 * element and an outline offset. 20680 * 20681 * @param {SVGElement} outline 20682 * @param {djs.model.Base} element 20683 */ 20684 Outline.prototype.updateShapeOutline = function(outline, element) { 20685 20686 attr(outline, { 20687 x: -this.offset, 20688 y: -this.offset, 20689 width: element.width + this.offset * 2, 20690 height: element.height + this.offset * 2 20691 }); 20692 20693 }; 20694 20695 20696 /** 20697 * Updates the outline of a connection respecting the bounding box of 20698 * the connection and an outline offset. 20699 * 20700 * @param {SVGElement} outline 20701 * @param {djs.model.Base} element 20702 */ 20703 Outline.prototype.updateConnectionOutline = function(outline, connection) { 20704 20705 var bbox = getBBox(connection); 20706 20707 attr(outline, { 20708 x: bbox.x - this.offset, 20709 y: bbox.y - this.offset, 20710 width: bbox.width + this.offset * 2, 20711 height: bbox.height + this.offset * 2 20712 }); 20713 20714 }; 20715 20716 20717 Outline.$inject = ['eventBus', 'styles', 'elementRegistry']; 20718 20719 var OutlineModule = { 20720 __init__: [ 'outline' ], 20721 outline: [ 'type', Outline ] 20722 }; 20723 20724 /** 20725 * A service that offers the current selection in a diagram. 20726 * Offers the api to control the selection, too. 20727 * 20728 * @class 20729 * 20730 * @param {EventBus} eventBus the event bus 20731 */ 20732 function Selection(eventBus, canvas) { 20733 20734 this._eventBus = eventBus; 20735 this._canvas = canvas; 20736 20737 this._selectedElements = []; 20738 20739 var self = this; 20740 20741 eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) { 20742 var element = e.element; 20743 self.deselect(element); 20744 }); 20745 20746 eventBus.on([ 'diagram.clear', 'plane.set' ], function(e) { 20747 self.select(null); 20748 }); 20749 } 20750 20751 Selection.$inject = [ 'eventBus', 'canvas' ]; 20752 20753 20754 Selection.prototype.deselect = function(element) { 20755 var selectedElements = this._selectedElements; 20756 20757 var idx = selectedElements.indexOf(element); 20758 20759 if (idx !== -1) { 20760 var oldSelection = selectedElements.slice(); 20761 20762 selectedElements.splice(idx, 1); 20763 20764 this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements }); 20765 } 20766 }; 20767 20768 20769 Selection.prototype.get = function() { 20770 return this._selectedElements; 20771 }; 20772 20773 Selection.prototype.isSelected = function(element) { 20774 return this._selectedElements.indexOf(element) !== -1; 20775 }; 20776 20777 20778 /** 20779 * This method selects one or more elements on the diagram. 20780 * 20781 * By passing an additional add parameter you can decide whether or not the element(s) 20782 * should be added to the already existing selection or not. 20783 * 20784 * @method Selection#select 20785 * 20786 * @param {Object|Object[]} elements element or array of elements to be selected 20787 * @param {boolean} [add] whether the element(s) should be appended to the current selection, defaults to false 20788 */ 20789 Selection.prototype.select = function(elements, add) { 20790 var selectedElements = this._selectedElements, 20791 oldSelection = selectedElements.slice(); 20792 20793 if (!isArray$2(elements)) { 20794 elements = elements ? [ elements ] : []; 20795 } 20796 20797 var canvas = this._canvas; 20798 20799 elements = elements.filter(function(element) { 20800 var plane = canvas.findPlane(element); 20801 20802 return plane === canvas.getActivePlane(); 20803 }); 20804 20805 // selection may be cleared by passing an empty array or null 20806 // to the method 20807 if (add) { 20808 forEach(elements, function(element) { 20809 if (selectedElements.indexOf(element) !== -1) { 20810 20811 // already selected 20812 return; 20813 } else { 20814 selectedElements.push(element); 20815 } 20816 }); 20817 } else { 20818 this._selectedElements = selectedElements = elements.slice(); 20819 } 20820 20821 this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements }); 20822 }; 20823 20824 var MARKER_HOVER = 'hover', 20825 MARKER_SELECTED = 'selected'; 20826 20827 20828 /** 20829 * A plugin that adds a visible selection UI to shapes and connections 20830 * by appending the <code>hover</code> and <code>selected</code> classes to them. 20831 * 20832 * @class 20833 * 20834 * Makes elements selectable, too. 20835 * 20836 * @param {EventBus} events 20837 * @param {SelectionService} selection 20838 * @param {Canvas} canvas 20839 */ 20840 function SelectionVisuals(events, canvas, selection, styles) { 20841 20842 this._multiSelectionBox = null; 20843 20844 function addMarker(e, cls) { 20845 canvas.addMarker(e, cls); 20846 } 20847 20848 function removeMarker(e, cls) { 20849 canvas.removeMarker(e, cls); 20850 } 20851 20852 events.on('element.hover', function(event) { 20853 addMarker(event.element, MARKER_HOVER); 20854 }); 20855 20856 events.on('element.out', function(event) { 20857 removeMarker(event.element, MARKER_HOVER); 20858 }); 20859 20860 events.on('selection.changed', function(event) { 20861 20862 function deselect(s) { 20863 removeMarker(s, MARKER_SELECTED); 20864 } 20865 20866 function select(s) { 20867 addMarker(s, MARKER_SELECTED); 20868 } 20869 20870 var oldSelection = event.oldSelection, 20871 newSelection = event.newSelection; 20872 20873 forEach(oldSelection, function(e) { 20874 if (newSelection.indexOf(e) === -1) { 20875 deselect(e); 20876 } 20877 }); 20878 20879 forEach(newSelection, function(e) { 20880 if (oldSelection.indexOf(e) === -1) { 20881 select(e); 20882 } 20883 }); 20884 }); 20885 } 20886 20887 SelectionVisuals.$inject = [ 20888 'eventBus', 20889 'canvas', 20890 'selection', 20891 'styles' 20892 ]; 20893 20894 function SelectionBehavior(eventBus, selection, canvas, elementRegistry) { 20895 20896 // Select elements on create 20897 eventBus.on('create.end', 500, function(event) { 20898 var context = event.context, 20899 canExecute = context.canExecute, 20900 elements = context.elements, 20901 hints = context.hints || {}, 20902 autoSelect = hints.autoSelect; 20903 20904 if (canExecute) { 20905 if (autoSelect === false) { 20906 20907 // Select no elements 20908 return; 20909 } 20910 20911 if (isArray$2(autoSelect)) { 20912 selection.select(autoSelect); 20913 } else { 20914 20915 // Select all elements by default 20916 selection.select(elements.filter(isShown)); 20917 } 20918 } 20919 }); 20920 20921 // Select connection targets on connect 20922 eventBus.on('connect.end', 500, function(event) { 20923 var context = event.context, 20924 canExecute = context.canExecute, 20925 hover = context.hover; 20926 20927 if (canExecute && hover) { 20928 selection.select(hover); 20929 } 20930 }); 20931 20932 // Select shapes on move 20933 eventBus.on('shape.move.end', 500, function(event) { 20934 var previousSelection = event.previousSelection || []; 20935 20936 var shape = elementRegistry.get(event.context.shape.id); 20937 20938 // Always select main shape on move 20939 var isSelected = find(previousSelection, function(selectedShape) { 20940 return shape.id === selectedShape.id; 20941 }); 20942 20943 if (!isSelected) { 20944 selection.select(shape); 20945 } 20946 }); 20947 20948 // Select elements on click 20949 eventBus.on('element.click', function(event) { 20950 20951 if (!isPrimaryButton(event)) { 20952 return; 20953 } 20954 20955 var element = event.element; 20956 20957 if (element === canvas.getRootElement()) { 20958 element = null; 20959 } 20960 20961 var isSelected = selection.isSelected(element), 20962 isMultiSelect = selection.get().length > 1; 20963 20964 // Add to selection if CTRL or SHIFT pressed 20965 var add = hasPrimaryModifier(event) || hasSecondaryModifier(event); 20966 20967 if (isSelected && isMultiSelect) { 20968 if (add) { 20969 20970 // Deselect element 20971 return selection.deselect(element); 20972 } else { 20973 20974 // Select element only 20975 return selection.select(element); 20976 } 20977 } else if (!isSelected) { 20978 20979 // Select element 20980 selection.select(element, add); 20981 } else { 20982 20983 // Deselect element 20984 selection.deselect(element); 20985 } 20986 }); 20987 } 20988 20989 SelectionBehavior.$inject = [ 20990 'eventBus', 20991 'selection', 20992 'canvas', 20993 'elementRegistry' 20994 ]; 20995 20996 20997 function isShown(element) { 20998 return !element.hidden; 20999 } 21000 21001 var SelectionModule = { 21002 __init__: [ 'selectionVisuals', 'selectionBehavior' ], 21003 __depends__: [ 21004 InteractionEventsModule$1, 21005 OutlineModule 21006 ], 21007 selection: [ 'type', Selection ], 21008 selectionVisuals: [ 'type', SelectionVisuals ], 21009 selectionBehavior: [ 'type', SelectionBehavior ] 21010 }; 21011 21012 /** 21013 * Util that provides unique IDs. 21014 * 21015 * @class djs.util.IdGenerator 21016 * @constructor 21017 * @memberOf djs.util 21018 * 21019 * The ids can be customized via a given prefix and contain a random value to avoid collisions. 21020 * 21021 * @param {string} prefix a prefix to prepend to generated ids (for better readability) 21022 */ 21023 function IdGenerator(prefix) { 21024 21025 this._counter = 0; 21026 this._prefix = (prefix ? prefix + '-' : '') + Math.floor(Math.random() * 1000000000) + '-'; 21027 } 21028 21029 /** 21030 * Returns a next unique ID. 21031 * 21032 * @method djs.util.IdGenerator#next 21033 * 21034 * @returns {string} the id 21035 */ 21036 IdGenerator.prototype.next = function() { 21037 return this._prefix + (++this._counter); 21038 }; 21039 21040 // document wide unique overlay ids 21041 var ids$1 = new IdGenerator('ov'); 21042 21043 var LOW_PRIORITY$k = 500; 21044 21045 21046 /** 21047 * A service that allows users to attach overlays to diagram elements. 21048 * 21049 * The overlay service will take care of overlay positioning during updates. 21050 * 21051 * @example 21052 * 21053 * // add a pink badge on the top left of the shape 21054 * overlays.add(someShape, { 21055 * position: { 21056 * top: -5, 21057 * left: -5 21058 * }, 21059 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>' 21060 * }); 21061 * 21062 * // or add via shape id 21063 * 21064 * overlays.add('some-element-id', { 21065 * position: { 21066 * top: -5, 21067 * left: -5 21068 * } 21069 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>' 21070 * }); 21071 * 21072 * // or add with optional type 21073 * 21074 * overlays.add(someShape, 'badge', { 21075 * position: { 21076 * top: -5, 21077 * left: -5 21078 * } 21079 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>' 21080 * }); 21081 * 21082 * 21083 * // remove an overlay 21084 * 21085 * var id = overlays.add(...); 21086 * overlays.remove(id); 21087 * 21088 * 21089 * You may configure overlay defaults during tool by providing a `config` module 21090 * with `overlays.defaults` as an entry: 21091 * 21092 * { 21093 * overlays: { 21094 * defaults: { 21095 * show: { 21096 * minZoom: 0.7, 21097 * maxZoom: 5.0 21098 * }, 21099 * scale: { 21100 * min: 1 21101 * } 21102 * } 21103 * } 21104 * 21105 * @param {Object} config 21106 * @param {EventBus} eventBus 21107 * @param {Canvas} canvas 21108 * @param {ElementRegistry} elementRegistry 21109 */ 21110 function Overlays(config, eventBus, canvas, elementRegistry) { 21111 21112 this._eventBus = eventBus; 21113 this._canvas = canvas; 21114 this._elementRegistry = elementRegistry; 21115 21116 this._ids = ids$1; 21117 21118 this._overlayDefaults = assign({ 21119 21120 // no show constraints 21121 show: null, 21122 21123 // always scale 21124 scale: true 21125 }, config && config.defaults); 21126 21127 /** 21128 * Mapping overlayId -> overlay 21129 */ 21130 this._overlays = {}; 21131 21132 /** 21133 * Mapping elementId -> overlay container 21134 */ 21135 this._overlayContainers = []; 21136 21137 // root html element for all overlays 21138 this._overlayRoot = createRoot$1(canvas.getContainer()); 21139 21140 this._init(); 21141 } 21142 21143 21144 Overlays.$inject = [ 21145 'config.overlays', 21146 'eventBus', 21147 'canvas', 21148 'elementRegistry' 21149 ]; 21150 21151 21152 /** 21153 * Returns the overlay with the specified id or a list of overlays 21154 * for an element with a given type. 21155 * 21156 * @example 21157 * 21158 * // return the single overlay with the given id 21159 * overlays.get('some-id'); 21160 * 21161 * // return all overlays for the shape 21162 * overlays.get({ element: someShape }); 21163 * 21164 * // return all overlays on shape with type 'badge' 21165 * overlays.get({ element: someShape, type: 'badge' }); 21166 * 21167 * // shape can also be specified as id 21168 * overlays.get({ element: 'element-id', type: 'badge' }); 21169 * 21170 * 21171 * @param {Object} search 21172 * @param {string} [search.id] 21173 * @param {string|djs.model.Base} [search.element] 21174 * @param {string} [search.type] 21175 * 21176 * @return {Object|Array<Object>} the overlay(s) 21177 */ 21178 Overlays.prototype.get = function(search) { 21179 21180 if (isString(search)) { 21181 search = { id: search }; 21182 } 21183 21184 if (isString(search.element)) { 21185 search.element = this._elementRegistry.get(search.element); 21186 } 21187 21188 if (search.element) { 21189 var container = this._getOverlayContainer(search.element, true); 21190 21191 // return a list of overlays when searching by element (+type) 21192 if (container) { 21193 return search.type ? filter(container.overlays, matchPattern({ type: search.type })) : container.overlays.slice(); 21194 } else { 21195 return []; 21196 } 21197 } else 21198 if (search.type) { 21199 return filter(this._overlays, matchPattern({ type: search.type })); 21200 } else { 21201 21202 // return single element when searching by id 21203 return search.id ? this._overlays[search.id] : null; 21204 } 21205 }; 21206 21207 /** 21208 * Adds a HTML overlay to an element. 21209 * 21210 * @param {string|djs.model.Base} element attach overlay to this shape 21211 * @param {string} [type] optional type to assign to the overlay 21212 * @param {Object} overlay the overlay configuration 21213 * 21214 * @param {string|DOMElement} overlay.html html element to use as an overlay 21215 * @param {Object} [overlay.show] show configuration 21216 * @param {number} [overlay.show.minZoom] minimal zoom level to show the overlay 21217 * @param {number} [overlay.show.maxZoom] maximum zoom level to show the overlay 21218 * @param {Object} overlay.position where to attach the overlay 21219 * @param {number} [overlay.position.left] relative to element bbox left attachment 21220 * @param {number} [overlay.position.top] relative to element bbox top attachment 21221 * @param {number} [overlay.position.bottom] relative to element bbox bottom attachment 21222 * @param {number} [overlay.position.right] relative to element bbox right attachment 21223 * @param {boolean|Object} [overlay.scale=true] false to preserve the same size regardless of 21224 * diagram zoom 21225 * @param {number} [overlay.scale.min] 21226 * @param {number} [overlay.scale.max] 21227 * 21228 * @return {string} id that may be used to reference the overlay for update or removal 21229 */ 21230 Overlays.prototype.add = function(element, type, overlay) { 21231 21232 if (isObject(type)) { 21233 overlay = type; 21234 type = null; 21235 } 21236 21237 if (!element.id) { 21238 element = this._elementRegistry.get(element); 21239 } 21240 21241 if (!overlay.position) { 21242 throw new Error('must specifiy overlay position'); 21243 } 21244 21245 if (!overlay.html) { 21246 throw new Error('must specifiy overlay html'); 21247 } 21248 21249 if (!element) { 21250 throw new Error('invalid element specified'); 21251 } 21252 21253 var id = this._ids.next(); 21254 21255 overlay = assign({}, this._overlayDefaults, overlay, { 21256 id: id, 21257 type: type, 21258 element: element, 21259 html: overlay.html 21260 }); 21261 21262 this._addOverlay(overlay); 21263 21264 return id; 21265 }; 21266 21267 21268 /** 21269 * Remove an overlay with the given id or all overlays matching the given filter. 21270 * 21271 * @see Overlays#get for filter options. 21272 * 21273 * @param {string} [id] 21274 * @param {Object} [filter] 21275 */ 21276 Overlays.prototype.remove = function(filter) { 21277 21278 var overlays = this.get(filter) || []; 21279 21280 if (!isArray$2(overlays)) { 21281 overlays = [ overlays ]; 21282 } 21283 21284 var self = this; 21285 21286 forEach(overlays, function(overlay) { 21287 21288 var container = self._getOverlayContainer(overlay.element, true); 21289 21290 if (overlay) { 21291 remove$2(overlay.html); 21292 remove$2(overlay.htmlContainer); 21293 21294 delete overlay.htmlContainer; 21295 delete overlay.element; 21296 21297 delete self._overlays[overlay.id]; 21298 } 21299 21300 if (container) { 21301 var idx = container.overlays.indexOf(overlay); 21302 if (idx !== -1) { 21303 container.overlays.splice(idx, 1); 21304 } 21305 } 21306 }); 21307 21308 }; 21309 21310 21311 Overlays.prototype.show = function() { 21312 setVisible$1(this._overlayRoot); 21313 }; 21314 21315 21316 Overlays.prototype.hide = function() { 21317 setVisible$1(this._overlayRoot, false); 21318 }; 21319 21320 Overlays.prototype.clear = function() { 21321 this._overlays = {}; 21322 21323 this._overlayContainers = []; 21324 21325 clear$1(this._overlayRoot); 21326 }; 21327 21328 Overlays.prototype._updateOverlayContainer = function(container) { 21329 var element = container.element, 21330 html = container.html; 21331 21332 // update container left,top according to the elements x,y coordinates 21333 // this ensures we can attach child elements relative to this container 21334 21335 var x = element.x, 21336 y = element.y; 21337 21338 if (element.waypoints) { 21339 var bbox = getBBox(element); 21340 x = bbox.x; 21341 y = bbox.y; 21342 } 21343 21344 setPosition$1(html, x, y); 21345 21346 attr$1(container.html, 'data-container-id', element.id); 21347 }; 21348 21349 21350 Overlays.prototype._updateOverlay = function(overlay) { 21351 21352 var position = overlay.position, 21353 htmlContainer = overlay.htmlContainer, 21354 element = overlay.element; 21355 21356 // update overlay html relative to shape because 21357 // it is already positioned on the element 21358 21359 // update relative 21360 var left = position.left, 21361 top = position.top; 21362 21363 if (position.right !== undefined) { 21364 21365 var width; 21366 21367 if (element.waypoints) { 21368 width = getBBox(element).width; 21369 } else { 21370 width = element.width; 21371 } 21372 21373 left = position.right * -1 + width; 21374 } 21375 21376 if (position.bottom !== undefined) { 21377 21378 var height; 21379 21380 if (element.waypoints) { 21381 height = getBBox(element).height; 21382 } else { 21383 height = element.height; 21384 } 21385 21386 top = position.bottom * -1 + height; 21387 } 21388 21389 setPosition$1(htmlContainer, left || 0, top || 0); 21390 }; 21391 21392 21393 Overlays.prototype._createOverlayContainer = function(element) { 21394 var html = domify('<div class="djs-overlays" style="position: absolute" />'); 21395 21396 this._overlayRoot.appendChild(html); 21397 21398 var container = { 21399 html: html, 21400 element: element, 21401 overlays: [] 21402 }; 21403 21404 this._updateOverlayContainer(container); 21405 21406 this._overlayContainers.push(container); 21407 21408 return container; 21409 }; 21410 21411 21412 Overlays.prototype._updateRoot = function(viewbox) { 21413 var scale = viewbox.scale || 1; 21414 21415 var matrix = 'matrix(' + 21416 [ 21417 scale, 21418 0, 21419 0, 21420 scale, 21421 -1 * viewbox.x * scale, 21422 -1 * viewbox.y * scale 21423 ].join(',') + 21424 ')'; 21425 21426 setTransform$1(this._overlayRoot, matrix); 21427 }; 21428 21429 21430 Overlays.prototype._getOverlayContainer = function(element, raw) { 21431 var container = find(this._overlayContainers, function(c) { 21432 return c.element === element; 21433 }); 21434 21435 21436 if (!container && !raw) { 21437 return this._createOverlayContainer(element); 21438 } 21439 21440 return container; 21441 }; 21442 21443 21444 Overlays.prototype._addOverlay = function(overlay) { 21445 21446 var id = overlay.id, 21447 element = overlay.element, 21448 html = overlay.html, 21449 htmlContainer, 21450 overlayContainer; 21451 21452 // unwrap jquery (for those who need it) 21453 if (html.get && html.constructor.prototype.jquery) { 21454 html = html.get(0); 21455 } 21456 21457 // create proper html elements from 21458 // overlay HTML strings 21459 if (isString(html)) { 21460 html = domify(html); 21461 } 21462 21463 overlayContainer = this._getOverlayContainer(element); 21464 21465 htmlContainer = domify('<div class="djs-overlay" data-overlay-id="' + id + '" style="position: absolute">'); 21466 21467 htmlContainer.appendChild(html); 21468 21469 if (overlay.type) { 21470 classes$1(htmlContainer).add('djs-overlay-' + overlay.type); 21471 } 21472 21473 var plane = this._canvas.findPlane(element); 21474 var activePlane = this._canvas.getActivePlane(); 21475 overlay.plane = plane; 21476 if (plane !== activePlane) { 21477 setVisible$1(htmlContainer, false); 21478 } 21479 21480 overlay.htmlContainer = htmlContainer; 21481 21482 overlayContainer.overlays.push(overlay); 21483 overlayContainer.html.appendChild(htmlContainer); 21484 21485 this._overlays[id] = overlay; 21486 21487 this._updateOverlay(overlay); 21488 this._updateOverlayVisibilty(overlay, this._canvas.viewbox()); 21489 }; 21490 21491 21492 Overlays.prototype._updateOverlayVisibilty = function(overlay, viewbox) { 21493 var show = overlay.show, 21494 minZoom = show && show.minZoom, 21495 maxZoom = show && show.maxZoom, 21496 htmlContainer = overlay.htmlContainer, 21497 visible = true; 21498 21499 if (show) { 21500 if ( 21501 (isDefined(minZoom) && minZoom > viewbox.scale) || 21502 (isDefined(maxZoom) && maxZoom < viewbox.scale) 21503 ) { 21504 visible = false; 21505 } 21506 21507 setVisible$1(htmlContainer, visible); 21508 } 21509 21510 this._updateOverlayScale(overlay, viewbox); 21511 }; 21512 21513 21514 Overlays.prototype._updateOverlayScale = function(overlay, viewbox) { 21515 var shouldScale = overlay.scale, 21516 minScale, 21517 maxScale, 21518 htmlContainer = overlay.htmlContainer; 21519 21520 var scale, transform = ''; 21521 21522 if (shouldScale !== true) { 21523 21524 if (shouldScale === false) { 21525 minScale = 1; 21526 maxScale = 1; 21527 } else { 21528 minScale = shouldScale.min; 21529 maxScale = shouldScale.max; 21530 } 21531 21532 if (isDefined(minScale) && viewbox.scale < minScale) { 21533 scale = (1 / viewbox.scale || 1) * minScale; 21534 } 21535 21536 if (isDefined(maxScale) && viewbox.scale > maxScale) { 21537 scale = (1 / viewbox.scale || 1) * maxScale; 21538 } 21539 } 21540 21541 if (isDefined(scale)) { 21542 transform = 'scale(' + scale + ',' + scale + ')'; 21543 } 21544 21545 setTransform$1(htmlContainer, transform); 21546 }; 21547 21548 21549 Overlays.prototype._updateOverlaysVisibilty = function(viewbox) { 21550 21551 var self = this; 21552 21553 forEach(this._overlays, function(overlay) { 21554 self._updateOverlayVisibilty(overlay, viewbox); 21555 }); 21556 }; 21557 21558 21559 Overlays.prototype._init = function() { 21560 21561 var eventBus = this._eventBus; 21562 21563 var self = this; 21564 21565 21566 // scroll/zoom integration 21567 21568 function updateViewbox(viewbox) { 21569 self._updateRoot(viewbox); 21570 self._updateOverlaysVisibilty(viewbox); 21571 21572 self.show(); 21573 } 21574 21575 eventBus.on('canvas.viewbox.changing', function(event) { 21576 self.hide(); 21577 }); 21578 21579 eventBus.on('canvas.viewbox.changed', function(event) { 21580 updateViewbox(event.viewbox); 21581 }); 21582 21583 21584 // remove integration 21585 21586 eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) { 21587 var element = e.element; 21588 var overlays = self.get({ element: element }); 21589 21590 forEach(overlays, function(o) { 21591 self.remove(o.id); 21592 }); 21593 21594 var container = self._getOverlayContainer(element); 21595 21596 if (container) { 21597 remove$2(container.html); 21598 var i = self._overlayContainers.indexOf(container); 21599 if (i !== -1) { 21600 self._overlayContainers.splice(i, 1); 21601 } 21602 } 21603 }); 21604 21605 21606 // move integration 21607 21608 eventBus.on('element.changed', LOW_PRIORITY$k, function(e) { 21609 var element = e.element; 21610 21611 var container = self._getOverlayContainer(element, true); 21612 21613 if (container) { 21614 forEach(container.overlays, function(overlay) { 21615 self._updateOverlay(overlay); 21616 }); 21617 21618 self._updateOverlayContainer(container); 21619 } 21620 }); 21621 21622 21623 // marker integration, simply add them on the overlays as classes, too. 21624 21625 eventBus.on('element.marker.update', function(e) { 21626 var container = self._getOverlayContainer(e.element, true); 21627 if (container) { 21628 classes$1(container.html)[e.add ? 'add' : 'remove'](e.marker); 21629 } 21630 }); 21631 21632 21633 eventBus.on('plane.set', function(e) { 21634 forEach(self._overlays, function(el) { 21635 setVisible$1(el.htmlContainer, el.plane === e.plane); 21636 }); 21637 }); 21638 21639 // clear overlays with diagram 21640 21641 eventBus.on('diagram.clear', this.clear, this); 21642 }; 21643 21644 21645 21646 // helpers ///////////////////////////// 21647 21648 function createRoot$1(parentNode) { 21649 var root = domify( 21650 '<div class="djs-overlay-container" style="position: absolute; width: 0; height: 0;" />' 21651 ); 21652 21653 parentNode.insertBefore(root, parentNode.firstChild); 21654 21655 return root; 21656 } 21657 21658 function setPosition$1(el, x, y) { 21659 assign(el.style, { left: x + 'px', top: y + 'px' }); 21660 } 21661 21662 function setVisible$1(el, visible) { 21663 el.style.display = visible === false ? 'none' : ''; 21664 } 21665 21666 function setTransform$1(el, transform) { 21667 21668 el.style['transform-origin'] = 'top left'; 21669 21670 [ '', '-ms-', '-webkit-' ].forEach(function(prefix) { 21671 el.style[prefix + 'transform'] = transform; 21672 }); 21673 } 21674 21675 var OverlaysModule = { 21676 __init__: [ 'overlays' ], 21677 overlays: [ 'type', Overlays ] 21678 }; 21679 21680 /** 21681 * A viewer for BPMN 2.0 diagrams. 21682 * 21683 * Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include 21684 * additional features. 21685 * 21686 * 21687 * ## Extending the Viewer 21688 * 21689 * In order to extend the viewer pass extension modules to bootstrap via the 21690 * `additionalModules` option. An extension module is an object that exposes 21691 * named services. 21692 * 21693 * The following example depicts the integration of a simple 21694 * logging component that integrates with interaction events: 21695 * 21696 * 21697 * ```javascript 21698 * 21699 * // logging component 21700 * function InteractionLogger(eventBus) { 21701 * eventBus.on('element.hover', function(event) { 21702 * console.log() 21703 * }) 21704 * } 21705 * 21706 * InteractionLogger.$inject = [ 'eventBus' ]; // minification save 21707 * 21708 * // extension module 21709 * var extensionModule = { 21710 * __init__: [ 'interactionLogger' ], 21711 * interactionLogger: [ 'type', InteractionLogger ] 21712 * }; 21713 * 21714 * // extend the viewer 21715 * var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] }); 21716 * bpmnViewer.importXML(...); 21717 * ``` 21718 * 21719 * @param {Object} [options] configuration options to pass to the viewer 21720 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body. 21721 * @param {string|number} [options.width] the width of the viewer 21722 * @param {string|number} [options.height] the height of the viewer 21723 * @param {Object} [options.moddleExtensions] extension packages to provide 21724 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules 21725 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules 21726 */ 21727 function Viewer(options) { 21728 BaseViewer.call(this, options); 21729 } 21730 21731 inherits$1(Viewer, BaseViewer); 21732 21733 // modules the viewer is composed of 21734 Viewer.prototype._modules = [ 21735 CoreModule, 21736 translate, 21737 SelectionModule, 21738 OverlaysModule 21739 ]; 21740 21741 // default moddle extensions the viewer is composed of 21742 Viewer.prototype._moddleExtensions = {}; 21743 21744 /** 21745 * Returns true if event was triggered with any modifier 21746 * @param {KeyboardEvent} event 21747 */ 21748 function hasModifier(event) { 21749 return (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey); 21750 } 21751 21752 /** 21753 * @param {KeyboardEvent} event 21754 */ 21755 function isCmd(event) { 21756 21757 // ensure we don't react to AltGr 21758 // (mapped to CTRL + ALT) 21759 if (event.altKey) { 21760 return false; 21761 } 21762 21763 return event.ctrlKey || event.metaKey; 21764 } 21765 21766 /** 21767 * Checks if key pressed is one of provided keys. 21768 * 21769 * @param {string|Array<string>} keys 21770 * @param {KeyboardEvent} event 21771 */ 21772 function isKey(keys, event) { 21773 keys = isArray$2(keys) ? keys : [ keys ]; 21774 21775 return keys.indexOf(event.key) !== -1 || keys.indexOf(event.keyCode) !== -1; 21776 } 21777 21778 /** 21779 * @param {KeyboardEvent} event 21780 */ 21781 function isShift(event) { 21782 return event.shiftKey; 21783 } 21784 21785 var KEYDOWN_EVENT = 'keyboard.keydown', 21786 KEYUP_EVENT = 'keyboard.keyup'; 21787 21788 var HANDLE_MODIFIER_ATTRIBUTE = 'input-handle-modified-keys'; 21789 21790 var DEFAULT_PRIORITY$4 = 1000; 21791 21792 /** 21793 * A keyboard abstraction that may be activated and 21794 * deactivated by users at will, consuming key events 21795 * and triggering diagram actions. 21796 * 21797 * For keys pressed down, keyboard fires `keyboard.keydown` event. 21798 * The event context contains one field which is `KeyboardEvent` event. 21799 * 21800 * The implementation fires the following key events that allow 21801 * other components to hook into key handling: 21802 * 21803 * - keyboard.bind 21804 * - keyboard.unbind 21805 * - keyboard.init 21806 * - keyboard.destroy 21807 * 21808 * All events contain one field which is node. 21809 * 21810 * A default binding for the keyboard may be specified via the 21811 * `keyboard.bindTo` configuration option. 21812 * 21813 * @param {Config} config 21814 * @param {EventBus} eventBus 21815 */ 21816 function Keyboard(config, eventBus) { 21817 var self = this; 21818 21819 this._config = config || {}; 21820 this._eventBus = eventBus; 21821 21822 this._keydownHandler = this._keydownHandler.bind(this); 21823 this._keyupHandler = this._keyupHandler.bind(this); 21824 21825 // properly clean dom registrations 21826 eventBus.on('diagram.destroy', function() { 21827 self._fire('destroy'); 21828 21829 self.unbind(); 21830 }); 21831 21832 eventBus.on('diagram.init', function() { 21833 self._fire('init'); 21834 }); 21835 21836 eventBus.on('attach', function() { 21837 if (config && config.bindTo) { 21838 self.bind(config.bindTo); 21839 } 21840 }); 21841 21842 eventBus.on('detach', function() { 21843 self.unbind(); 21844 }); 21845 } 21846 21847 Keyboard.$inject = [ 21848 'config.keyboard', 21849 'eventBus' 21850 ]; 21851 21852 Keyboard.prototype._keydownHandler = function(event) { 21853 this._keyHandler(event, KEYDOWN_EVENT); 21854 }; 21855 21856 Keyboard.prototype._keyupHandler = function(event) { 21857 this._keyHandler(event, KEYUP_EVENT); 21858 }; 21859 21860 Keyboard.prototype._keyHandler = function(event, type) { 21861 var eventBusResult; 21862 21863 if (this._isEventIgnored(event)) { 21864 return; 21865 } 21866 21867 var context = { 21868 keyEvent: event 21869 }; 21870 21871 eventBusResult = this._eventBus.fire(type || KEYDOWN_EVENT, context); 21872 21873 if (eventBusResult) { 21874 event.preventDefault(); 21875 } 21876 }; 21877 21878 Keyboard.prototype._isEventIgnored = function(event) { 21879 return isInput(event.target) && this._isModifiedKeyIgnored(event); 21880 }; 21881 21882 Keyboard.prototype._isModifiedKeyIgnored = function(event) { 21883 if (!isCmd(event)) { 21884 return true; 21885 } 21886 21887 var allowedModifiers = this._getAllowedModifiers(event.target); 21888 return !allowedModifiers.includes(event.key); 21889 }; 21890 21891 Keyboard.prototype._getAllowedModifiers = function(element) { 21892 var modifierContainer = closest(element, '[' + HANDLE_MODIFIER_ATTRIBUTE + ']', true); 21893 21894 if (!modifierContainer || (this._node && !this._node.contains(modifierContainer))) { 21895 return []; 21896 } 21897 21898 return modifierContainer.getAttribute(HANDLE_MODIFIER_ATTRIBUTE).split(','); 21899 }; 21900 21901 Keyboard.prototype.bind = function(node) { 21902 21903 // make sure that the keyboard is only bound once to the DOM 21904 this.unbind(); 21905 21906 this._node = node; 21907 21908 // bind key events 21909 componentEvent.bind(node, 'keydown', this._keydownHandler, true); 21910 componentEvent.bind(node, 'keyup', this._keyupHandler, true); 21911 21912 this._fire('bind'); 21913 }; 21914 21915 Keyboard.prototype.getBinding = function() { 21916 return this._node; 21917 }; 21918 21919 Keyboard.prototype.unbind = function() { 21920 var node = this._node; 21921 21922 if (node) { 21923 this._fire('unbind'); 21924 21925 // unbind key events 21926 componentEvent.unbind(node, 'keydown', this._keydownHandler, true); 21927 componentEvent.unbind(node, 'keyup', this._keyupHandler, true); 21928 } 21929 21930 this._node = null; 21931 }; 21932 21933 Keyboard.prototype._fire = function(event) { 21934 this._eventBus.fire('keyboard.' + event, { node: this._node }); 21935 }; 21936 21937 /** 21938 * Add a listener function that is notified with `KeyboardEvent` whenever 21939 * the keyboard is bound and the user presses a key. If no priority is 21940 * provided, the default value of 1000 is used. 21941 * 21942 * @param {number} [priority] 21943 * @param {Function} listener 21944 * @param {string} type 21945 */ 21946 Keyboard.prototype.addListener = function(priority, listener, type) { 21947 if (isFunction(priority)) { 21948 type = listener; 21949 listener = priority; 21950 priority = DEFAULT_PRIORITY$4; 21951 } 21952 21953 this._eventBus.on(type || KEYDOWN_EVENT, priority, listener); 21954 }; 21955 21956 Keyboard.prototype.removeListener = function(listener, type) { 21957 this._eventBus.off(type || KEYDOWN_EVENT, listener); 21958 }; 21959 21960 Keyboard.prototype.hasModifier = hasModifier; 21961 Keyboard.prototype.isCmd = isCmd; 21962 Keyboard.prototype.isShift = isShift; 21963 Keyboard.prototype.isKey = isKey; 21964 21965 21966 21967 // helpers /////// 21968 21969 function isInput(target) { 21970 return target && (matchesSelector(target, 'input, textarea') || target.contentEditable === 'true'); 21971 } 21972 21973 var LOW_PRIORITY$j = 500; 21974 21975 var KEYCODE_C = 67; 21976 var KEYCODE_V = 86; 21977 var KEYCODE_Y = 89; 21978 var KEYCODE_Z = 90; 21979 21980 var KEYS_COPY = ['c', 'C', KEYCODE_C ]; 21981 var KEYS_PASTE = [ 'v', 'V', KEYCODE_V ]; 21982 var KEYS_REDO = [ 'y', 'Y', KEYCODE_Y ]; 21983 var KEYS_UNDO = [ 'z', 'Z', KEYCODE_Z ]; 21984 21985 21986 /** 21987 * Adds default keyboard bindings. 21988 * 21989 * This does not pull in any features will bind only actions that 21990 * have previously been registered against the editorActions component. 21991 * 21992 * @param {EventBus} eventBus 21993 * @param {Keyboard} keyboard 21994 */ 21995 function KeyboardBindings(eventBus, keyboard) { 21996 21997 var self = this; 21998 21999 eventBus.on('editorActions.init', LOW_PRIORITY$j, function(event) { 22000 22001 var editorActions = event.editorActions; 22002 22003 self.registerBindings(keyboard, editorActions); 22004 }); 22005 } 22006 22007 KeyboardBindings.$inject = [ 22008 'eventBus', 22009 'keyboard' 22010 ]; 22011 22012 22013 /** 22014 * Register available keyboard bindings. 22015 * 22016 * @param {Keyboard} keyboard 22017 * @param {EditorActions} editorActions 22018 */ 22019 KeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) { 22020 22021 /** 22022 * Add keyboard binding if respective editor action 22023 * is registered. 22024 * 22025 * @param {string} action name 22026 * @param {Function} fn that implements the key binding 22027 */ 22028 function addListener(action, fn) { 22029 22030 if (editorActions.isRegistered(action)) { 22031 keyboard.addListener(fn); 22032 } 22033 } 22034 22035 22036 // undo 22037 // (CTRL|CMD) + Z 22038 addListener('undo', function(context) { 22039 22040 var event = context.keyEvent; 22041 22042 if (isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event)) { 22043 editorActions.trigger('undo'); 22044 22045 return true; 22046 } 22047 }); 22048 22049 // redo 22050 // CTRL + Y 22051 // CMD + SHIFT + Z 22052 addListener('redo', function(context) { 22053 22054 var event = context.keyEvent; 22055 22056 if (isCmd(event) && (isKey(KEYS_REDO, event) || (isKey(KEYS_UNDO, event) && isShift(event)))) { 22057 editorActions.trigger('redo'); 22058 22059 return true; 22060 } 22061 }); 22062 22063 // copy 22064 // CTRL/CMD + C 22065 addListener('copy', function(context) { 22066 22067 var event = context.keyEvent; 22068 22069 if (isCmd(event) && isKey(KEYS_COPY, event)) { 22070 editorActions.trigger('copy'); 22071 22072 return true; 22073 } 22074 }); 22075 22076 // paste 22077 // CTRL/CMD + V 22078 addListener('paste', function(context) { 22079 22080 var event = context.keyEvent; 22081 22082 if (isCmd(event) && isKey(KEYS_PASTE, event)) { 22083 editorActions.trigger('paste'); 22084 22085 return true; 22086 } 22087 }); 22088 22089 // zoom in one step 22090 // CTRL/CMD + + 22091 addListener('stepZoom', function(context) { 22092 22093 var event = context.keyEvent; 22094 22095 // quirk: it has to be triggered by `=` as well to work on international keyboard layout 22096 // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754 22097 if (isKey([ '+', 'Add', '=' ], event) && isCmd(event)) { 22098 editorActions.trigger('stepZoom', { value: 1 }); 22099 22100 return true; 22101 } 22102 }); 22103 22104 // zoom out one step 22105 // CTRL + - 22106 addListener('stepZoom', function(context) { 22107 22108 var event = context.keyEvent; 22109 22110 if (isKey([ '-', 'Subtract' ], event) && isCmd(event)) { 22111 editorActions.trigger('stepZoom', { value: -1 }); 22112 22113 return true; 22114 } 22115 }); 22116 22117 // zoom to the default level 22118 // CTRL + 0 22119 addListener('zoom', function(context) { 22120 22121 var event = context.keyEvent; 22122 22123 if (isKey('0', event) && isCmd(event)) { 22124 editorActions.trigger('zoom', { value: 1 }); 22125 22126 return true; 22127 } 22128 }); 22129 22130 // delete selected element 22131 // DEL 22132 addListener('removeSelection', function(context) { 22133 22134 var event = context.keyEvent; 22135 22136 if (isKey(['Backspace', 'Delete', 'Del' ], event)) { 22137 editorActions.trigger('removeSelection'); 22138 22139 return true; 22140 } 22141 }); 22142 }; 22143 22144 var KeyboardModule$1 = { 22145 __init__: [ 'keyboard', 'keyboardBindings' ], 22146 keyboard: [ 'type', Keyboard ], 22147 keyboardBindings: [ 'type', KeyboardBindings ] 22148 }; 22149 22150 var DEFAULT_CONFIG$1 = { 22151 moveSpeed: 50, 22152 moveSpeedAccelerated: 200 22153 }; 22154 22155 22156 /** 22157 * A feature that allows users to move the canvas using the keyboard. 22158 * 22159 * @param {Object} config 22160 * @param {number} [config.moveSpeed=50] 22161 * @param {number} [config.moveSpeedAccelerated=200] 22162 * @param {Keyboard} keyboard 22163 * @param {Canvas} canvas 22164 */ 22165 function KeyboardMove( 22166 config, 22167 keyboard, 22168 canvas 22169 ) { 22170 22171 var self = this; 22172 22173 this._config = assign({}, DEFAULT_CONFIG$1, config || {}); 22174 22175 keyboard.addListener(arrowsListener); 22176 22177 22178 function arrowsListener(context) { 22179 22180 var event = context.keyEvent, 22181 config = self._config; 22182 22183 if (!keyboard.isCmd(event)) { 22184 return; 22185 } 22186 22187 if (keyboard.isKey([ 22188 'ArrowLeft', 'Left', 22189 'ArrowUp', 'Up', 22190 'ArrowDown', 'Down', 22191 'ArrowRight', 'Right' 22192 ], event)) { 22193 22194 var speed = ( 22195 keyboard.isShift(event) ? 22196 config.moveSpeedAccelerated : 22197 config.moveSpeed 22198 ); 22199 22200 var direction; 22201 22202 switch (event.key) { 22203 case 'ArrowLeft': 22204 case 'Left': 22205 direction = 'left'; 22206 break; 22207 case 'ArrowUp': 22208 case 'Up': 22209 direction = 'up'; 22210 break; 22211 case 'ArrowRight': 22212 case 'Right': 22213 direction = 'right'; 22214 break; 22215 case 'ArrowDown': 22216 case 'Down': 22217 direction = 'down'; 22218 break; 22219 } 22220 22221 self.moveCanvas({ 22222 speed: speed, 22223 direction: direction 22224 }); 22225 22226 return true; 22227 } 22228 } 22229 22230 this.moveCanvas = function(opts) { 22231 22232 var dx = 0, 22233 dy = 0, 22234 speed = opts.speed; 22235 22236 var actualSpeed = speed / Math.min(Math.sqrt(canvas.viewbox().scale), 1); 22237 22238 switch (opts.direction) { 22239 case 'left': // Left 22240 dx = actualSpeed; 22241 break; 22242 case 'up': // Up 22243 dy = actualSpeed; 22244 break; 22245 case 'right': // Right 22246 dx = -actualSpeed; 22247 break; 22248 case 'down': // Down 22249 dy = -actualSpeed; 22250 break; 22251 } 22252 22253 canvas.scroll({ 22254 dx: dx, 22255 dy: dy 22256 }); 22257 }; 22258 22259 } 22260 22261 22262 KeyboardMove.$inject = [ 22263 'config.keyboardMove', 22264 'keyboard', 22265 'canvas' 22266 ]; 22267 22268 var KeyboardMoveModule = { 22269 __depends__: [ 22270 KeyboardModule$1 22271 ], 22272 __init__: [ 'keyboardMove' ], 22273 keyboardMove: [ 'type', KeyboardMove ] 22274 }; 22275 22276 var CURSOR_CLS_PATTERN = /^djs-cursor-.*$/; 22277 22278 22279 function set(mode) { 22280 var classes = classes$1(document.body); 22281 22282 classes.removeMatching(CURSOR_CLS_PATTERN); 22283 22284 if (mode) { 22285 classes.add('djs-cursor-' + mode); 22286 } 22287 } 22288 22289 function unset() { 22290 set(null); 22291 } 22292 22293 var TRAP_PRIORITY = 5000; 22294 22295 /** 22296 * Installs a click trap that prevents a ghost click following a dragging operation. 22297 * 22298 * @return {Function} a function to immediately remove the installed trap. 22299 */ 22300 function install(eventBus, eventName) { 22301 22302 eventName = eventName || 'element.click'; 22303 22304 function trap() { 22305 return false; 22306 } 22307 22308 eventBus.once(eventName, TRAP_PRIORITY, trap); 22309 22310 return function() { 22311 eventBus.off(eventName, trap); 22312 }; 22313 } 22314 22315 function center(bounds) { 22316 return { 22317 x: bounds.x + (bounds.width / 2), 22318 y: bounds.y + (bounds.height / 2) 22319 }; 22320 } 22321 22322 22323 function delta(a, b) { 22324 return { 22325 x: a.x - b.x, 22326 y: a.y - b.y 22327 }; 22328 } 22329 22330 var THRESHOLD$1 = 15; 22331 22332 22333 /** 22334 * Move the canvas via mouse. 22335 * 22336 * @param {EventBus} eventBus 22337 * @param {Canvas} canvas 22338 */ 22339 function MoveCanvas(eventBus, canvas) { 22340 22341 var context; 22342 22343 22344 // listen for move on element mouse down; 22345 // allow others to hook into the event before us though 22346 // (dragging / element moving will do this) 22347 eventBus.on('element.mousedown', 500, function(e) { 22348 return handleStart(e.originalEvent); 22349 }); 22350 22351 22352 function handleMove(event) { 22353 22354 var start = context.start, 22355 button = context.button, 22356 position = toPoint(event), 22357 delta$1 = delta(position, start); 22358 22359 if (!context.dragging && length(delta$1) > THRESHOLD$1) { 22360 context.dragging = true; 22361 22362 if (button === 0) { 22363 install(eventBus); 22364 } 22365 22366 set('grab'); 22367 } 22368 22369 if (context.dragging) { 22370 22371 var lastPosition = context.last || context.start; 22372 22373 delta$1 = delta(position, lastPosition); 22374 22375 canvas.scroll({ 22376 dx: delta$1.x, 22377 dy: delta$1.y 22378 }); 22379 22380 context.last = position; 22381 } 22382 22383 // prevent select 22384 event.preventDefault(); 22385 } 22386 22387 22388 function handleEnd(event) { 22389 componentEvent.unbind(document, 'mousemove', handleMove); 22390 componentEvent.unbind(document, 'mouseup', handleEnd); 22391 22392 context = null; 22393 22394 unset(); 22395 } 22396 22397 function handleStart(event) { 22398 22399 // event is already handled by '.djs-draggable' 22400 if (closest(event.target, '.djs-draggable')) { 22401 return; 22402 } 22403 22404 var button = event.button; 22405 22406 // reject right mouse button or modifier key 22407 if (button >= 2 || event.ctrlKey || event.shiftKey || event.altKey) { 22408 return; 22409 } 22410 22411 context = { 22412 button: button, 22413 start: toPoint(event) 22414 }; 22415 22416 componentEvent.bind(document, 'mousemove', handleMove); 22417 componentEvent.bind(document, 'mouseup', handleEnd); 22418 22419 // we've handled the event 22420 return true; 22421 } 22422 22423 this.isActive = function() { 22424 return !!context; 22425 }; 22426 22427 } 22428 22429 22430 MoveCanvas.$inject = [ 22431 'eventBus', 22432 'canvas' 22433 ]; 22434 22435 22436 22437 // helpers /////// 22438 22439 function length(point) { 22440 return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2)); 22441 } 22442 22443 var MoveCanvasModule = { 22444 __init__: [ 'moveCanvas' ], 22445 moveCanvas: [ 'type', MoveCanvas ] 22446 }; 22447 22448 /** 22449 * Get the logarithm of x with base 10 22450 * @param {Integer} value 22451 */ 22452 function log10(x) { 22453 return Math.log(x) / Math.log(10); 22454 } 22455 22456 /** 22457 * Get step size for given range and number of steps. 22458 * 22459 * @param {Object} range 22460 * @param {number} range.min 22461 * @param {number} range.max 22462 */ 22463 function getStepSize(range, steps) { 22464 22465 var minLinearRange = log10(range.min), 22466 maxLinearRange = log10(range.max); 22467 22468 var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange); 22469 22470 return absoluteLinearRange / steps; 22471 } 22472 22473 function cap(range, scale) { 22474 return Math.max(range.min, Math.min(range.max, scale)); 22475 } 22476 22477 var sign = Math.sign || function(n) { 22478 return n >= 0 ? 1 : -1; 22479 }; 22480 22481 var RANGE = { min: 0.2, max: 4 }, 22482 NUM_STEPS = 10; 22483 22484 var DELTA_THRESHOLD = 0.1; 22485 22486 var DEFAULT_SCALE = 0.75; 22487 22488 /** 22489 * An implementation of zooming and scrolling within the 22490 * {@link Canvas} via the mouse wheel. 22491 * 22492 * Mouse wheel zooming / scrolling may be disabled using 22493 * the {@link toggle(enabled)} method. 22494 * 22495 * @param {Object} [config] 22496 * @param {boolean} [config.enabled=true] default enabled state 22497 * @param {number} [config.scale=.75] scroll sensivity 22498 * @param {EventBus} eventBus 22499 * @param {Canvas} canvas 22500 */ 22501 function ZoomScroll(config, eventBus, canvas) { 22502 22503 config = config || {}; 22504 22505 this._enabled = false; 22506 22507 this._canvas = canvas; 22508 this._container = canvas._container; 22509 22510 this._handleWheel = bind$2(this._handleWheel, this); 22511 22512 this._totalDelta = 0; 22513 this._scale = config.scale || DEFAULT_SCALE; 22514 22515 var self = this; 22516 22517 eventBus.on('canvas.init', function(e) { 22518 self._init(config.enabled !== false); 22519 }); 22520 } 22521 22522 ZoomScroll.$inject = [ 22523 'config.zoomScroll', 22524 'eventBus', 22525 'canvas' 22526 ]; 22527 22528 ZoomScroll.prototype.scroll = function scroll(delta) { 22529 this._canvas.scroll(delta); 22530 }; 22531 22532 22533 ZoomScroll.prototype.reset = function reset() { 22534 this._canvas.zoom('fit-viewport'); 22535 }; 22536 22537 /** 22538 * Zoom depending on delta. 22539 * 22540 * @param {number} delta 22541 * @param {Object} position 22542 */ 22543 ZoomScroll.prototype.zoom = function zoom(delta, position) { 22544 22545 // zoom with half the step size of stepZoom 22546 var stepSize = getStepSize(RANGE, NUM_STEPS * 2); 22547 22548 // add until threshold reached 22549 this._totalDelta += delta; 22550 22551 if (Math.abs(this._totalDelta) > DELTA_THRESHOLD) { 22552 this._zoom(delta, position, stepSize); 22553 22554 // reset 22555 this._totalDelta = 0; 22556 } 22557 }; 22558 22559 22560 ZoomScroll.prototype._handleWheel = function handleWheel(event) { 22561 22562 // event is already handled by '.djs-scrollable' 22563 if (closest(event.target, '.djs-scrollable', true)) { 22564 return; 22565 } 22566 22567 var element = this._container; 22568 22569 event.preventDefault(); 22570 22571 // pinch to zoom is mapped to wheel + ctrlKey = true 22572 // in modern browsers (!) 22573 22574 var isZoom = event.ctrlKey; 22575 22576 var isHorizontalScroll = event.shiftKey; 22577 22578 var factor = -1 * this._scale, 22579 delta; 22580 22581 if (isZoom) { 22582 factor *= event.deltaMode === 0 ? 0.020 : 0.32; 22583 } else { 22584 factor *= event.deltaMode === 0 ? 1.0 : 16.0; 22585 } 22586 22587 if (isZoom) { 22588 var elementRect = element.getBoundingClientRect(); 22589 22590 var offset = { 22591 x: event.clientX - elementRect.left, 22592 y: event.clientY - elementRect.top 22593 }; 22594 22595 delta = ( 22596 Math.sqrt( 22597 Math.pow(event.deltaY, 2) + 22598 Math.pow(event.deltaX, 2) 22599 ) * sign(event.deltaY) * factor 22600 ); 22601 22602 // zoom in relative to diagram {x,y} coordinates 22603 this.zoom(delta, offset); 22604 } else { 22605 22606 if (isHorizontalScroll) { 22607 delta = { 22608 dx: factor * event.deltaY, 22609 dy: 0 22610 }; 22611 } else { 22612 delta = { 22613 dx: factor * event.deltaX, 22614 dy: factor * event.deltaY 22615 }; 22616 } 22617 22618 this.scroll(delta); 22619 } 22620 }; 22621 22622 /** 22623 * Zoom with fixed step size. 22624 * 22625 * @param {number} delta - Zoom delta (1 for zooming in, -1 for out). 22626 * @param {Object} position 22627 */ 22628 ZoomScroll.prototype.stepZoom = function stepZoom(delta, position) { 22629 22630 var stepSize = getStepSize(RANGE, NUM_STEPS); 22631 22632 this._zoom(delta, position, stepSize); 22633 }; 22634 22635 22636 /** 22637 * Zoom in/out given a step size. 22638 * 22639 * @param {number} delta 22640 * @param {Object} position 22641 * @param {number} stepSize 22642 */ 22643 ZoomScroll.prototype._zoom = function(delta, position, stepSize) { 22644 var canvas = this._canvas; 22645 22646 var direction = delta > 0 ? 1 : -1; 22647 22648 var currentLinearZoomLevel = log10(canvas.zoom()); 22649 22650 // snap to a proximate zoom step 22651 var newLinearZoomLevel = Math.round(currentLinearZoomLevel / stepSize) * stepSize; 22652 22653 // increase or decrease one zoom step in the given direction 22654 newLinearZoomLevel += stepSize * direction; 22655 22656 // calculate the absolute logarithmic zoom level based on the linear zoom level 22657 // (e.g. 2 for an absolute x2 zoom) 22658 var newLogZoomLevel = Math.pow(10, newLinearZoomLevel); 22659 22660 canvas.zoom(cap(RANGE, newLogZoomLevel), position); 22661 }; 22662 22663 22664 /** 22665 * Toggle the zoom scroll ability via mouse wheel. 22666 * 22667 * @param {boolean} [newEnabled] new enabled state 22668 */ 22669 ZoomScroll.prototype.toggle = function toggle(newEnabled) { 22670 22671 var element = this._container; 22672 var handleWheel = this._handleWheel; 22673 22674 var oldEnabled = this._enabled; 22675 22676 if (typeof newEnabled === 'undefined') { 22677 newEnabled = !oldEnabled; 22678 } 22679 22680 // only react on actual changes 22681 if (oldEnabled !== newEnabled) { 22682 22683 // add or remove wheel listener based on 22684 // changed enabled state 22685 componentEvent[newEnabled ? 'bind' : 'unbind'](element, 'wheel', handleWheel, false); 22686 } 22687 22688 this._enabled = newEnabled; 22689 22690 return newEnabled; 22691 }; 22692 22693 22694 ZoomScroll.prototype._init = function(newEnabled) { 22695 this.toggle(newEnabled); 22696 }; 22697 22698 var ZoomScrollModule = { 22699 __init__: [ 'zoomScroll' ], 22700 zoomScroll: [ 'type', ZoomScroll ] 22701 }; 22702 22703 /** 22704 * A viewer that includes mouse navigation facilities 22705 * 22706 * @param {Object} options 22707 */ 22708 function NavigatedViewer(options) { 22709 Viewer.call(this, options); 22710 } 22711 22712 inherits$1(NavigatedViewer, Viewer); 22713 22714 22715 NavigatedViewer.prototype._navigationModules = [ 22716 KeyboardMoveModule, 22717 MoveCanvasModule, 22718 ZoomScrollModule 22719 ]; 22720 22721 NavigatedViewer.prototype._modules = [].concat( 22722 Viewer.prototype._modules, 22723 NavigatedViewer.prototype._navigationModules 22724 ); 22725 22726 var hammer = {exports: {}}; 22727 22728 /*! Hammer.JS - v2.0.7 - 2016-04-22 22729 * http://hammerjs.github.io/ 22730 * 22731 * Copyright (c) 2016 Jorik Tangelder; 22732 * Licensed under the MIT license */ 22733 22734 (function (module) { 22735 (function(window, document, exportName, undefined$1) { 22736 22737 var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o']; 22738 var TEST_ELEMENT = document.createElement('div'); 22739 22740 var TYPE_FUNCTION = 'function'; 22741 22742 var round = Math.round; 22743 var abs = Math.abs; 22744 var now = Date.now; 22745 22746 /** 22747 * set a timeout with a given scope 22748 * @param {Function} fn 22749 * @param {Number} timeout 22750 * @param {Object} context 22751 * @returns {number} 22752 */ 22753 function setTimeoutContext(fn, timeout, context) { 22754 return setTimeout(bindFn(fn, context), timeout); 22755 } 22756 22757 /** 22758 * if the argument is an array, we want to execute the fn on each entry 22759 * if it aint an array we don't want to do a thing. 22760 * this is used by all the methods that accept a single and array argument. 22761 * @param {*|Array} arg 22762 * @param {String} fn 22763 * @param {Object} [context] 22764 * @returns {Boolean} 22765 */ 22766 function invokeArrayArg(arg, fn, context) { 22767 if (Array.isArray(arg)) { 22768 each(arg, context[fn], context); 22769 return true; 22770 } 22771 return false; 22772 } 22773 22774 /** 22775 * walk objects and arrays 22776 * @param {Object} obj 22777 * @param {Function} iterator 22778 * @param {Object} context 22779 */ 22780 function each(obj, iterator, context) { 22781 var i; 22782 22783 if (!obj) { 22784 return; 22785 } 22786 22787 if (obj.forEach) { 22788 obj.forEach(iterator, context); 22789 } else if (obj.length !== undefined$1) { 22790 i = 0; 22791 while (i < obj.length) { 22792 iterator.call(context, obj[i], i, obj); 22793 i++; 22794 } 22795 } else { 22796 for (i in obj) { 22797 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); 22798 } 22799 } 22800 } 22801 22802 /** 22803 * wrap a method with a deprecation warning and stack trace 22804 * @param {Function} method 22805 * @param {String} name 22806 * @param {String} message 22807 * @returns {Function} A new function wrapping the supplied method. 22808 */ 22809 function deprecate(method, name, message) { 22810 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n'; 22811 return function() { 22812 var e = new Error('get-stack-trace'); 22813 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '') 22814 .replace(/^\s+at\s+/gm, '') 22815 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace'; 22816 22817 var log = window.console && (window.console.warn || window.console.log); 22818 if (log) { 22819 log.call(window.console, deprecationMessage, stack); 22820 } 22821 return method.apply(this, arguments); 22822 }; 22823 } 22824 22825 /** 22826 * extend object. 22827 * means that properties in dest will be overwritten by the ones in src. 22828 * @param {Object} target 22829 * @param {...Object} objects_to_assign 22830 * @returns {Object} target 22831 */ 22832 var assign; 22833 if (typeof Object.assign !== 'function') { 22834 assign = function assign(target) { 22835 if (target === undefined$1 || target === null) { 22836 throw new TypeError('Cannot convert undefined or null to object'); 22837 } 22838 22839 var output = Object(target); 22840 for (var index = 1; index < arguments.length; index++) { 22841 var source = arguments[index]; 22842 if (source !== undefined$1 && source !== null) { 22843 for (var nextKey in source) { 22844 if (source.hasOwnProperty(nextKey)) { 22845 output[nextKey] = source[nextKey]; 22846 } 22847 } 22848 } 22849 } 22850 return output; 22851 }; 22852 } else { 22853 assign = Object.assign; 22854 } 22855 22856 /** 22857 * extend object. 22858 * means that properties in dest will be overwritten by the ones in src. 22859 * @param {Object} dest 22860 * @param {Object} src 22861 * @param {Boolean} [merge=false] 22862 * @returns {Object} dest 22863 */ 22864 var extend = deprecate(function extend(dest, src, merge) { 22865 var keys = Object.keys(src); 22866 var i = 0; 22867 while (i < keys.length) { 22868 if (!merge || (merge && dest[keys[i]] === undefined$1)) { 22869 dest[keys[i]] = src[keys[i]]; 22870 } 22871 i++; 22872 } 22873 return dest; 22874 }, 'extend', 'Use `assign`.'); 22875 22876 /** 22877 * merge the values from src in the dest. 22878 * means that properties that exist in dest will not be overwritten by src 22879 * @param {Object} dest 22880 * @param {Object} src 22881 * @returns {Object} dest 22882 */ 22883 var merge = deprecate(function merge(dest, src) { 22884 return extend(dest, src, true); 22885 }, 'merge', 'Use `assign`.'); 22886 22887 /** 22888 * simple class inheritance 22889 * @param {Function} child 22890 * @param {Function} base 22891 * @param {Object} [properties] 22892 */ 22893 function inherit(child, base, properties) { 22894 var baseP = base.prototype, 22895 childP; 22896 22897 childP = child.prototype = Object.create(baseP); 22898 childP.constructor = child; 22899 childP._super = baseP; 22900 22901 if (properties) { 22902 assign(childP, properties); 22903 } 22904 } 22905 22906 /** 22907 * simple function bind 22908 * @param {Function} fn 22909 * @param {Object} context 22910 * @returns {Function} 22911 */ 22912 function bindFn(fn, context) { 22913 return function boundFn() { 22914 return fn.apply(context, arguments); 22915 }; 22916 } 22917 22918 /** 22919 * let a boolean value also be a function that must return a boolean 22920 * this first item in args will be used as the context 22921 * @param {Boolean|Function} val 22922 * @param {Array} [args] 22923 * @returns {Boolean} 22924 */ 22925 function boolOrFn(val, args) { 22926 if (typeof val == TYPE_FUNCTION) { 22927 return val.apply(args ? args[0] || undefined$1 : undefined$1, args); 22928 } 22929 return val; 22930 } 22931 22932 /** 22933 * use the val2 when val1 is undefined 22934 * @param {*} val1 22935 * @param {*} val2 22936 * @returns {*} 22937 */ 22938 function ifUndefined(val1, val2) { 22939 return (val1 === undefined$1) ? val2 : val1; 22940 } 22941 22942 /** 22943 * addEventListener with multiple events at once 22944 * @param {EventTarget} target 22945 * @param {String} types 22946 * @param {Function} handler 22947 */ 22948 function addEventListeners(target, types, handler) { 22949 each(splitStr(types), function(type) { 22950 target.addEventListener(type, handler, false); 22951 }); 22952 } 22953 22954 /** 22955 * removeEventListener with multiple events at once 22956 * @param {EventTarget} target 22957 * @param {String} types 22958 * @param {Function} handler 22959 */ 22960 function removeEventListeners(target, types, handler) { 22961 each(splitStr(types), function(type) { 22962 target.removeEventListener(type, handler, false); 22963 }); 22964 } 22965 22966 /** 22967 * find if a node is in the given parent 22968 * @method hasParent 22969 * @param {HTMLElement} node 22970 * @param {HTMLElement} parent 22971 * @return {Boolean} found 22972 */ 22973 function hasParent(node, parent) { 22974 while (node) { 22975 if (node == parent) { 22976 return true; 22977 } 22978 node = node.parentNode; 22979 } 22980 return false; 22981 } 22982 22983 /** 22984 * small indexOf wrapper 22985 * @param {String} str 22986 * @param {String} find 22987 * @returns {Boolean} found 22988 */ 22989 function inStr(str, find) { 22990 return str.indexOf(find) > -1; 22991 } 22992 22993 /** 22994 * split string on whitespace 22995 * @param {String} str 22996 * @returns {Array} words 22997 */ 22998 function splitStr(str) { 22999 return str.trim().split(/\s+/g); 23000 } 23001 23002 /** 23003 * find if a array contains the object using indexOf or a simple polyFill 23004 * @param {Array} src 23005 * @param {String} find 23006 * @param {String} [findByKey] 23007 * @return {Boolean|Number} false when not found, or the index 23008 */ 23009 function inArray(src, find, findByKey) { 23010 if (src.indexOf && !findByKey) { 23011 return src.indexOf(find); 23012 } else { 23013 var i = 0; 23014 while (i < src.length) { 23015 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) { 23016 return i; 23017 } 23018 i++; 23019 } 23020 return -1; 23021 } 23022 } 23023 23024 /** 23025 * convert array-like objects to real arrays 23026 * @param {Object} obj 23027 * @returns {Array} 23028 */ 23029 function toArray(obj) { 23030 return Array.prototype.slice.call(obj, 0); 23031 } 23032 23033 /** 23034 * unique array with objects based on a key (like 'id') or just by the array's value 23035 * @param {Array} src [{id:1},{id:2},{id:1}] 23036 * @param {String} [key] 23037 * @param {Boolean} [sort=False] 23038 * @returns {Array} [{id:1},{id:2}] 23039 */ 23040 function uniqueArray(src, key, sort) { 23041 var results = []; 23042 var values = []; 23043 var i = 0; 23044 23045 while (i < src.length) { 23046 var val = key ? src[i][key] : src[i]; 23047 if (inArray(values, val) < 0) { 23048 results.push(src[i]); 23049 } 23050 values[i] = val; 23051 i++; 23052 } 23053 23054 if (sort) { 23055 if (!key) { 23056 results = results.sort(); 23057 } else { 23058 results = results.sort(function sortUniqueArray(a, b) { 23059 return a[key] > b[key]; 23060 }); 23061 } 23062 } 23063 23064 return results; 23065 } 23066 23067 /** 23068 * get the prefixed property 23069 * @param {Object} obj 23070 * @param {String} property 23071 * @returns {String|Undefined} prefixed 23072 */ 23073 function prefixed(obj, property) { 23074 var prefix, prop; 23075 var camelProp = property[0].toUpperCase() + property.slice(1); 23076 23077 var i = 0; 23078 while (i < VENDOR_PREFIXES.length) { 23079 prefix = VENDOR_PREFIXES[i]; 23080 prop = (prefix) ? prefix + camelProp : property; 23081 23082 if (prop in obj) { 23083 return prop; 23084 } 23085 i++; 23086 } 23087 return undefined$1; 23088 } 23089 23090 /** 23091 * get a unique id 23092 * @returns {number} uniqueId 23093 */ 23094 var _uniqueId = 1; 23095 function uniqueId() { 23096 return _uniqueId++; 23097 } 23098 23099 /** 23100 * get the window object of an element 23101 * @param {HTMLElement} element 23102 * @returns {DocumentView|Window} 23103 */ 23104 function getWindowForElement(element) { 23105 var doc = element.ownerDocument || element; 23106 return (doc.defaultView || doc.parentWindow || window); 23107 } 23108 23109 var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; 23110 23111 var SUPPORT_TOUCH = ('ontouchstart' in window); 23112 var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined$1; 23113 var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent); 23114 23115 var INPUT_TYPE_TOUCH = 'touch'; 23116 var INPUT_TYPE_PEN = 'pen'; 23117 var INPUT_TYPE_MOUSE = 'mouse'; 23118 var INPUT_TYPE_KINECT = 'kinect'; 23119 23120 var COMPUTE_INTERVAL = 25; 23121 23122 var INPUT_START = 1; 23123 var INPUT_MOVE = 2; 23124 var INPUT_END = 4; 23125 var INPUT_CANCEL = 8; 23126 23127 var DIRECTION_NONE = 1; 23128 var DIRECTION_LEFT = 2; 23129 var DIRECTION_RIGHT = 4; 23130 var DIRECTION_UP = 8; 23131 var DIRECTION_DOWN = 16; 23132 23133 var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; 23134 var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; 23135 var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; 23136 23137 var PROPS_XY = ['x', 'y']; 23138 var PROPS_CLIENT_XY = ['clientX', 'clientY']; 23139 23140 /** 23141 * create new input type manager 23142 * @param {Manager} manager 23143 * @param {Function} callback 23144 * @returns {Input} 23145 * @constructor 23146 */ 23147 function Input(manager, callback) { 23148 var self = this; 23149 this.manager = manager; 23150 this.callback = callback; 23151 this.element = manager.element; 23152 this.target = manager.options.inputTarget; 23153 23154 // smaller wrapper around the handler, for the scope and the enabled state of the manager, 23155 // so when disabled the input events are completely bypassed. 23156 this.domHandler = function(ev) { 23157 if (boolOrFn(manager.options.enable, [manager])) { 23158 self.handler(ev); 23159 } 23160 }; 23161 23162 this.init(); 23163 23164 } 23165 23166 Input.prototype = { 23167 /** 23168 * should handle the inputEvent data and trigger the callback 23169 * @virtual 23170 */ 23171 handler: function() { }, 23172 23173 /** 23174 * bind the events 23175 */ 23176 init: function() { 23177 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); 23178 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); 23179 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); 23180 }, 23181 23182 /** 23183 * unbind the events 23184 */ 23185 destroy: function() { 23186 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler); 23187 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler); 23188 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); 23189 } 23190 }; 23191 23192 /** 23193 * create new input type manager 23194 * called by the Manager constructor 23195 * @param {Hammer} manager 23196 * @returns {Input} 23197 */ 23198 function createInputInstance(manager) { 23199 var Type; 23200 var inputClass = manager.options.inputClass; 23201 23202 if (inputClass) { 23203 Type = inputClass; 23204 } else if (SUPPORT_POINTER_EVENTS) { 23205 Type = PointerEventInput; 23206 } else if (SUPPORT_ONLY_TOUCH) { 23207 Type = TouchInput; 23208 } else if (!SUPPORT_TOUCH) { 23209 Type = MouseInput; 23210 } else { 23211 Type = TouchMouseInput; 23212 } 23213 return new (Type)(manager, inputHandler); 23214 } 23215 23216 /** 23217 * handle input events 23218 * @param {Manager} manager 23219 * @param {String} eventType 23220 * @param {Object} input 23221 */ 23222 function inputHandler(manager, eventType, input) { 23223 var pointersLen = input.pointers.length; 23224 var changedPointersLen = input.changedPointers.length; 23225 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0)); 23226 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0)); 23227 23228 input.isFirst = !!isFirst; 23229 input.isFinal = !!isFinal; 23230 23231 if (isFirst) { 23232 manager.session = {}; 23233 } 23234 23235 // source event is the normalized value of the domEvents 23236 // like 'touchstart, mouseup, pointerdown' 23237 input.eventType = eventType; 23238 23239 // compute scale, rotation etc 23240 computeInputData(manager, input); 23241 23242 // emit secret event 23243 manager.emit('hammer.input', input); 23244 23245 manager.recognize(input); 23246 manager.session.prevInput = input; 23247 } 23248 23249 /** 23250 * extend the data with some usable properties like scale, rotate, velocity etc 23251 * @param {Object} manager 23252 * @param {Object} input 23253 */ 23254 function computeInputData(manager, input) { 23255 var session = manager.session; 23256 var pointers = input.pointers; 23257 var pointersLength = pointers.length; 23258 23259 // store the first input to calculate the distance and direction 23260 if (!session.firstInput) { 23261 session.firstInput = simpleCloneInputData(input); 23262 } 23263 23264 // to compute scale and rotation we need to store the multiple touches 23265 if (pointersLength > 1 && !session.firstMultiple) { 23266 session.firstMultiple = simpleCloneInputData(input); 23267 } else if (pointersLength === 1) { 23268 session.firstMultiple = false; 23269 } 23270 23271 var firstInput = session.firstInput; 23272 var firstMultiple = session.firstMultiple; 23273 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center; 23274 23275 var center = input.center = getCenter(pointers); 23276 input.timeStamp = now(); 23277 input.deltaTime = input.timeStamp - firstInput.timeStamp; 23278 23279 input.angle = getAngle(offsetCenter, center); 23280 input.distance = getDistance(offsetCenter, center); 23281 23282 computeDeltaXY(session, input); 23283 input.offsetDirection = getDirection(input.deltaX, input.deltaY); 23284 23285 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY); 23286 input.overallVelocityX = overallVelocity.x; 23287 input.overallVelocityY = overallVelocity.y; 23288 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y; 23289 23290 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; 23291 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; 23292 23293 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length > 23294 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers); 23295 23296 computeIntervalInputData(session, input); 23297 23298 // find the correct target 23299 var target = manager.element; 23300 if (hasParent(input.srcEvent.target, target)) { 23301 target = input.srcEvent.target; 23302 } 23303 input.target = target; 23304 } 23305 23306 function computeDeltaXY(session, input) { 23307 var center = input.center; 23308 var offset = session.offsetDelta || {}; 23309 var prevDelta = session.prevDelta || {}; 23310 var prevInput = session.prevInput || {}; 23311 23312 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) { 23313 prevDelta = session.prevDelta = { 23314 x: prevInput.deltaX || 0, 23315 y: prevInput.deltaY || 0 23316 }; 23317 23318 offset = session.offsetDelta = { 23319 x: center.x, 23320 y: center.y 23321 }; 23322 } 23323 23324 input.deltaX = prevDelta.x + (center.x - offset.x); 23325 input.deltaY = prevDelta.y + (center.y - offset.y); 23326 } 23327 23328 /** 23329 * velocity is calculated every x ms 23330 * @param {Object} session 23331 * @param {Object} input 23332 */ 23333 function computeIntervalInputData(session, input) { 23334 var last = session.lastInterval || input, 23335 deltaTime = input.timeStamp - last.timeStamp, 23336 velocity, velocityX, velocityY, direction; 23337 23338 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined$1)) { 23339 var deltaX = input.deltaX - last.deltaX; 23340 var deltaY = input.deltaY - last.deltaY; 23341 23342 var v = getVelocity(deltaTime, deltaX, deltaY); 23343 velocityX = v.x; 23344 velocityY = v.y; 23345 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y; 23346 direction = getDirection(deltaX, deltaY); 23347 23348 session.lastInterval = input; 23349 } else { 23350 // use latest velocity info if it doesn't overtake a minimum period 23351 velocity = last.velocity; 23352 velocityX = last.velocityX; 23353 velocityY = last.velocityY; 23354 direction = last.direction; 23355 } 23356 23357 input.velocity = velocity; 23358 input.velocityX = velocityX; 23359 input.velocityY = velocityY; 23360 input.direction = direction; 23361 } 23362 23363 /** 23364 * create a simple clone from the input used for storage of firstInput and firstMultiple 23365 * @param {Object} input 23366 * @returns {Object} clonedInputData 23367 */ 23368 function simpleCloneInputData(input) { 23369 // make a simple copy of the pointers because we will get a reference if we don't 23370 // we only need clientXY for the calculations 23371 var pointers = []; 23372 var i = 0; 23373 while (i < input.pointers.length) { 23374 pointers[i] = { 23375 clientX: round(input.pointers[i].clientX), 23376 clientY: round(input.pointers[i].clientY) 23377 }; 23378 i++; 23379 } 23380 23381 return { 23382 timeStamp: now(), 23383 pointers: pointers, 23384 center: getCenter(pointers), 23385 deltaX: input.deltaX, 23386 deltaY: input.deltaY 23387 }; 23388 } 23389 23390 /** 23391 * get the center of all the pointers 23392 * @param {Array} pointers 23393 * @return {Object} center contains `x` and `y` properties 23394 */ 23395 function getCenter(pointers) { 23396 var pointersLength = pointers.length; 23397 23398 // no need to loop when only one touch 23399 if (pointersLength === 1) { 23400 return { 23401 x: round(pointers[0].clientX), 23402 y: round(pointers[0].clientY) 23403 }; 23404 } 23405 23406 var x = 0, y = 0, i = 0; 23407 while (i < pointersLength) { 23408 x += pointers[i].clientX; 23409 y += pointers[i].clientY; 23410 i++; 23411 } 23412 23413 return { 23414 x: round(x / pointersLength), 23415 y: round(y / pointersLength) 23416 }; 23417 } 23418 23419 /** 23420 * calculate the velocity between two points. unit is in px per ms. 23421 * @param {Number} deltaTime 23422 * @param {Number} x 23423 * @param {Number} y 23424 * @return {Object} velocity `x` and `y` 23425 */ 23426 function getVelocity(deltaTime, x, y) { 23427 return { 23428 x: x / deltaTime || 0, 23429 y: y / deltaTime || 0 23430 }; 23431 } 23432 23433 /** 23434 * get the direction between two points 23435 * @param {Number} x 23436 * @param {Number} y 23437 * @return {Number} direction 23438 */ 23439 function getDirection(x, y) { 23440 if (x === y) { 23441 return DIRECTION_NONE; 23442 } 23443 23444 if (abs(x) >= abs(y)) { 23445 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; 23446 } 23447 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN; 23448 } 23449 23450 /** 23451 * calculate the absolute distance between two points 23452 * @param {Object} p1 {x, y} 23453 * @param {Object} p2 {x, y} 23454 * @param {Array} [props] containing x and y keys 23455 * @return {Number} distance 23456 */ 23457 function getDistance(p1, p2, props) { 23458 if (!props) { 23459 props = PROPS_XY; 23460 } 23461 var x = p2[props[0]] - p1[props[0]], 23462 y = p2[props[1]] - p1[props[1]]; 23463 23464 return Math.sqrt((x * x) + (y * y)); 23465 } 23466 23467 /** 23468 * calculate the angle between two coordinates 23469 * @param {Object} p1 23470 * @param {Object} p2 23471 * @param {Array} [props] containing x and y keys 23472 * @return {Number} angle 23473 */ 23474 function getAngle(p1, p2, props) { 23475 if (!props) { 23476 props = PROPS_XY; 23477 } 23478 var x = p2[props[0]] - p1[props[0]], 23479 y = p2[props[1]] - p1[props[1]]; 23480 return Math.atan2(y, x) * 180 / Math.PI; 23481 } 23482 23483 /** 23484 * calculate the rotation degrees between two pointersets 23485 * @param {Array} start array of pointers 23486 * @param {Array} end array of pointers 23487 * @return {Number} rotation 23488 */ 23489 function getRotation(start, end) { 23490 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY); 23491 } 23492 23493 /** 23494 * calculate the scale factor between two pointersets 23495 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out 23496 * @param {Array} start array of pointers 23497 * @param {Array} end array of pointers 23498 * @return {Number} scale 23499 */ 23500 function getScale(start, end) { 23501 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY); 23502 } 23503 23504 var MOUSE_INPUT_MAP = { 23505 mousedown: INPUT_START, 23506 mousemove: INPUT_MOVE, 23507 mouseup: INPUT_END 23508 }; 23509 23510 var MOUSE_ELEMENT_EVENTS = 'mousedown'; 23511 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup'; 23512 23513 /** 23514 * Mouse events input 23515 * @constructor 23516 * @extends Input 23517 */ 23518 function MouseInput() { 23519 this.evEl = MOUSE_ELEMENT_EVENTS; 23520 this.evWin = MOUSE_WINDOW_EVENTS; 23521 23522 this.pressed = false; // mousedown state 23523 23524 Input.apply(this, arguments); 23525 } 23526 23527 inherit(MouseInput, Input, { 23528 /** 23529 * handle mouse events 23530 * @param {Object} ev 23531 */ 23532 handler: function MEhandler(ev) { 23533 var eventType = MOUSE_INPUT_MAP[ev.type]; 23534 23535 // on start we want to have the left mouse button down 23536 if (eventType & INPUT_START && ev.button === 0) { 23537 this.pressed = true; 23538 } 23539 23540 if (eventType & INPUT_MOVE && ev.which !== 1) { 23541 eventType = INPUT_END; 23542 } 23543 23544 // mouse must be down 23545 if (!this.pressed) { 23546 return; 23547 } 23548 23549 if (eventType & INPUT_END) { 23550 this.pressed = false; 23551 } 23552 23553 this.callback(this.manager, eventType, { 23554 pointers: [ev], 23555 changedPointers: [ev], 23556 pointerType: INPUT_TYPE_MOUSE, 23557 srcEvent: ev 23558 }); 23559 } 23560 }); 23561 23562 var POINTER_INPUT_MAP = { 23563 pointerdown: INPUT_START, 23564 pointermove: INPUT_MOVE, 23565 pointerup: INPUT_END, 23566 pointercancel: INPUT_CANCEL, 23567 pointerout: INPUT_CANCEL 23568 }; 23569 23570 // in IE10 the pointer types is defined as an enum 23571 var IE10_POINTER_TYPE_ENUM = { 23572 2: INPUT_TYPE_TOUCH, 23573 3: INPUT_TYPE_PEN, 23574 4: INPUT_TYPE_MOUSE, 23575 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816 23576 }; 23577 23578 var POINTER_ELEMENT_EVENTS = 'pointerdown'; 23579 var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; 23580 23581 // IE10 has prefixed support, and case-sensitive 23582 if (window.MSPointerEvent && !window.PointerEvent) { 23583 POINTER_ELEMENT_EVENTS = 'MSPointerDown'; 23584 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; 23585 } 23586 23587 /** 23588 * Pointer events input 23589 * @constructor 23590 * @extends Input 23591 */ 23592 function PointerEventInput() { 23593 this.evEl = POINTER_ELEMENT_EVENTS; 23594 this.evWin = POINTER_WINDOW_EVENTS; 23595 23596 Input.apply(this, arguments); 23597 23598 this.store = (this.manager.session.pointerEvents = []); 23599 } 23600 23601 inherit(PointerEventInput, Input, { 23602 /** 23603 * handle mouse events 23604 * @param {Object} ev 23605 */ 23606 handler: function PEhandler(ev) { 23607 var store = this.store; 23608 var removePointer = false; 23609 23610 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', ''); 23611 var eventType = POINTER_INPUT_MAP[eventTypeNormalized]; 23612 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType; 23613 23614 var isTouch = (pointerType == INPUT_TYPE_TOUCH); 23615 23616 // get index of the event in the store 23617 var storeIndex = inArray(store, ev.pointerId, 'pointerId'); 23618 23619 // start and mouse must be down 23620 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) { 23621 if (storeIndex < 0) { 23622 store.push(ev); 23623 storeIndex = store.length - 1; 23624 } 23625 } else if (eventType & (INPUT_END | INPUT_CANCEL)) { 23626 removePointer = true; 23627 } 23628 23629 // it not found, so the pointer hasn't been down (so it's probably a hover) 23630 if (storeIndex < 0) { 23631 return; 23632 } 23633 23634 // update the event in the store 23635 store[storeIndex] = ev; 23636 23637 this.callback(this.manager, eventType, { 23638 pointers: store, 23639 changedPointers: [ev], 23640 pointerType: pointerType, 23641 srcEvent: ev 23642 }); 23643 23644 if (removePointer) { 23645 // remove from the store 23646 store.splice(storeIndex, 1); 23647 } 23648 } 23649 }); 23650 23651 var SINGLE_TOUCH_INPUT_MAP = { 23652 touchstart: INPUT_START, 23653 touchmove: INPUT_MOVE, 23654 touchend: INPUT_END, 23655 touchcancel: INPUT_CANCEL 23656 }; 23657 23658 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart'; 23659 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel'; 23660 23661 /** 23662 * Touch events input 23663 * @constructor 23664 * @extends Input 23665 */ 23666 function SingleTouchInput() { 23667 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS; 23668 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS; 23669 this.started = false; 23670 23671 Input.apply(this, arguments); 23672 } 23673 23674 inherit(SingleTouchInput, Input, { 23675 handler: function TEhandler(ev) { 23676 var type = SINGLE_TOUCH_INPUT_MAP[ev.type]; 23677 23678 // should we handle the touch events? 23679 if (type === INPUT_START) { 23680 this.started = true; 23681 } 23682 23683 if (!this.started) { 23684 return; 23685 } 23686 23687 var touches = normalizeSingleTouches.call(this, ev, type); 23688 23689 // when done, reset the started state 23690 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) { 23691 this.started = false; 23692 } 23693 23694 this.callback(this.manager, type, { 23695 pointers: touches[0], 23696 changedPointers: touches[1], 23697 pointerType: INPUT_TYPE_TOUCH, 23698 srcEvent: ev 23699 }); 23700 } 23701 }); 23702 23703 /** 23704 * @this {TouchInput} 23705 * @param {Object} ev 23706 * @param {Number} type flag 23707 * @returns {undefined|Array} [all, changed] 23708 */ 23709 function normalizeSingleTouches(ev, type) { 23710 var all = toArray(ev.touches); 23711 var changed = toArray(ev.changedTouches); 23712 23713 if (type & (INPUT_END | INPUT_CANCEL)) { 23714 all = uniqueArray(all.concat(changed), 'identifier', true); 23715 } 23716 23717 return [all, changed]; 23718 } 23719 23720 var TOUCH_INPUT_MAP = { 23721 touchstart: INPUT_START, 23722 touchmove: INPUT_MOVE, 23723 touchend: INPUT_END, 23724 touchcancel: INPUT_CANCEL 23725 }; 23726 23727 var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel'; 23728 23729 /** 23730 * Multi-user touch events input 23731 * @constructor 23732 * @extends Input 23733 */ 23734 function TouchInput() { 23735 this.evTarget = TOUCH_TARGET_EVENTS; 23736 this.targetIds = {}; 23737 23738 Input.apply(this, arguments); 23739 } 23740 23741 inherit(TouchInput, Input, { 23742 handler: function MTEhandler(ev) { 23743 var type = TOUCH_INPUT_MAP[ev.type]; 23744 var touches = getTouches.call(this, ev, type); 23745 if (!touches) { 23746 return; 23747 } 23748 23749 this.callback(this.manager, type, { 23750 pointers: touches[0], 23751 changedPointers: touches[1], 23752 pointerType: INPUT_TYPE_TOUCH, 23753 srcEvent: ev 23754 }); 23755 } 23756 }); 23757 23758 /** 23759 * @this {TouchInput} 23760 * @param {Object} ev 23761 * @param {Number} type flag 23762 * @returns {undefined|Array} [all, changed] 23763 */ 23764 function getTouches(ev, type) { 23765 var allTouches = toArray(ev.touches); 23766 var targetIds = this.targetIds; 23767 23768 // when there is only one touch, the process can be simplified 23769 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) { 23770 targetIds[allTouches[0].identifier] = true; 23771 return [allTouches, allTouches]; 23772 } 23773 23774 var i, 23775 targetTouches, 23776 changedTouches = toArray(ev.changedTouches), 23777 changedTargetTouches = [], 23778 target = this.target; 23779 23780 // get target touches from touches 23781 targetTouches = allTouches.filter(function(touch) { 23782 return hasParent(touch.target, target); 23783 }); 23784 23785 // collect touches 23786 if (type === INPUT_START) { 23787 i = 0; 23788 while (i < targetTouches.length) { 23789 targetIds[targetTouches[i].identifier] = true; 23790 i++; 23791 } 23792 } 23793 23794 // filter changed touches to only contain touches that exist in the collected target ids 23795 i = 0; 23796 while (i < changedTouches.length) { 23797 if (targetIds[changedTouches[i].identifier]) { 23798 changedTargetTouches.push(changedTouches[i]); 23799 } 23800 23801 // cleanup removed touches 23802 if (type & (INPUT_END | INPUT_CANCEL)) { 23803 delete targetIds[changedTouches[i].identifier]; 23804 } 23805 i++; 23806 } 23807 23808 if (!changedTargetTouches.length) { 23809 return; 23810 } 23811 23812 return [ 23813 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel' 23814 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true), 23815 changedTargetTouches 23816 ]; 23817 } 23818 23819 /** 23820 * Combined touch and mouse input 23821 * 23822 * Touch has a higher priority then mouse, and while touching no mouse events are allowed. 23823 * This because touch devices also emit mouse events while doing a touch. 23824 * 23825 * @constructor 23826 * @extends Input 23827 */ 23828 23829 var DEDUP_TIMEOUT = 2500; 23830 var DEDUP_DISTANCE = 25; 23831 23832 function TouchMouseInput() { 23833 Input.apply(this, arguments); 23834 23835 var handler = bindFn(this.handler, this); 23836 this.touch = new TouchInput(this.manager, handler); 23837 this.mouse = new MouseInput(this.manager, handler); 23838 23839 this.primaryTouch = null; 23840 this.lastTouches = []; 23841 } 23842 23843 inherit(TouchMouseInput, Input, { 23844 /** 23845 * handle mouse and touch events 23846 * @param {Hammer} manager 23847 * @param {String} inputEvent 23848 * @param {Object} inputData 23849 */ 23850 handler: function TMEhandler(manager, inputEvent, inputData) { 23851 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH), 23852 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE); 23853 23854 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) { 23855 return; 23856 } 23857 23858 // when we're in a touch event, record touches to de-dupe synthetic mouse event 23859 if (isTouch) { 23860 recordTouches.call(this, inputEvent, inputData); 23861 } else if (isMouse && isSyntheticEvent.call(this, inputData)) { 23862 return; 23863 } 23864 23865 this.callback(manager, inputEvent, inputData); 23866 }, 23867 23868 /** 23869 * remove the event listeners 23870 */ 23871 destroy: function destroy() { 23872 this.touch.destroy(); 23873 this.mouse.destroy(); 23874 } 23875 }); 23876 23877 function recordTouches(eventType, eventData) { 23878 if (eventType & INPUT_START) { 23879 this.primaryTouch = eventData.changedPointers[0].identifier; 23880 setLastTouch.call(this, eventData); 23881 } else if (eventType & (INPUT_END | INPUT_CANCEL)) { 23882 setLastTouch.call(this, eventData); 23883 } 23884 } 23885 23886 function setLastTouch(eventData) { 23887 var touch = eventData.changedPointers[0]; 23888 23889 if (touch.identifier === this.primaryTouch) { 23890 var lastTouch = {x: touch.clientX, y: touch.clientY}; 23891 this.lastTouches.push(lastTouch); 23892 var lts = this.lastTouches; 23893 var removeLastTouch = function() { 23894 var i = lts.indexOf(lastTouch); 23895 if (i > -1) { 23896 lts.splice(i, 1); 23897 } 23898 }; 23899 setTimeout(removeLastTouch, DEDUP_TIMEOUT); 23900 } 23901 } 23902 23903 function isSyntheticEvent(eventData) { 23904 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY; 23905 for (var i = 0; i < this.lastTouches.length; i++) { 23906 var t = this.lastTouches[i]; 23907 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); 23908 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) { 23909 return true; 23910 } 23911 } 23912 return false; 23913 } 23914 23915 var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); 23916 var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined$1; 23917 23918 // magical touchAction value 23919 var TOUCH_ACTION_COMPUTE = 'compute'; 23920 var TOUCH_ACTION_AUTO = 'auto'; 23921 var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented 23922 var TOUCH_ACTION_NONE = 'none'; 23923 var TOUCH_ACTION_PAN_X = 'pan-x'; 23924 var TOUCH_ACTION_PAN_Y = 'pan-y'; 23925 var TOUCH_ACTION_MAP = getTouchActionProps(); 23926 23927 /** 23928 * Touch Action 23929 * sets the touchAction property or uses the js alternative 23930 * @param {Manager} manager 23931 * @param {String} value 23932 * @constructor 23933 */ 23934 function TouchAction(manager, value) { 23935 this.manager = manager; 23936 this.set(value); 23937 } 23938 23939 TouchAction.prototype = { 23940 /** 23941 * set the touchAction value on the element or enable the polyfill 23942 * @param {String} value 23943 */ 23944 set: function(value) { 23945 // find out the touch-action by the event handlers 23946 if (value == TOUCH_ACTION_COMPUTE) { 23947 value = this.compute(); 23948 } 23949 23950 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) { 23951 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; 23952 } 23953 this.actions = value.toLowerCase().trim(); 23954 }, 23955 23956 /** 23957 * just re-set the touchAction value 23958 */ 23959 update: function() { 23960 this.set(this.manager.options.touchAction); 23961 }, 23962 23963 /** 23964 * compute the value for the touchAction property based on the recognizer's settings 23965 * @returns {String} value 23966 */ 23967 compute: function() { 23968 var actions = []; 23969 each(this.manager.recognizers, function(recognizer) { 23970 if (boolOrFn(recognizer.options.enable, [recognizer])) { 23971 actions = actions.concat(recognizer.getTouchAction()); 23972 } 23973 }); 23974 return cleanTouchActions(actions.join(' ')); 23975 }, 23976 23977 /** 23978 * this method is called on each input cycle and provides the preventing of the browser behavior 23979 * @param {Object} input 23980 */ 23981 preventDefaults: function(input) { 23982 var srcEvent = input.srcEvent; 23983 var direction = input.offsetDirection; 23984 23985 // if the touch action did prevented once this session 23986 if (this.manager.session.prevented) { 23987 srcEvent.preventDefault(); 23988 return; 23989 } 23990 23991 var actions = this.actions; 23992 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE]; 23993 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y]; 23994 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X]; 23995 23996 if (hasNone) { 23997 //do not prevent defaults if this is a tap gesture 23998 23999 var isTapPointer = input.pointers.length === 1; 24000 var isTapMovement = input.distance < 2; 24001 var isTapTouchTime = input.deltaTime < 250; 24002 24003 if (isTapPointer && isTapMovement && isTapTouchTime) { 24004 return; 24005 } 24006 } 24007 24008 if (hasPanX && hasPanY) { 24009 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent 24010 return; 24011 } 24012 24013 if (hasNone || 24014 (hasPanY && direction & DIRECTION_HORIZONTAL) || 24015 (hasPanX && direction & DIRECTION_VERTICAL)) { 24016 return this.preventSrc(srcEvent); 24017 } 24018 }, 24019 24020 /** 24021 * call preventDefault to prevent the browser's default behavior (scrolling in most cases) 24022 * @param {Object} srcEvent 24023 */ 24024 preventSrc: function(srcEvent) { 24025 this.manager.session.prevented = true; 24026 srcEvent.preventDefault(); 24027 } 24028 }; 24029 24030 /** 24031 * when the touchActions are collected they are not a valid value, so we need to clean things up. * 24032 * @param {String} actions 24033 * @returns {*} 24034 */ 24035 function cleanTouchActions(actions) { 24036 // none 24037 if (inStr(actions, TOUCH_ACTION_NONE)) { 24038 return TOUCH_ACTION_NONE; 24039 } 24040 24041 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); 24042 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); 24043 24044 // if both pan-x and pan-y are set (different recognizers 24045 // for different directions, e.g. horizontal pan but vertical swipe?) 24046 // we need none (as otherwise with pan-x pan-y combined none of these 24047 // recognizers will work, since the browser would handle all panning 24048 if (hasPanX && hasPanY) { 24049 return TOUCH_ACTION_NONE; 24050 } 24051 24052 // pan-x OR pan-y 24053 if (hasPanX || hasPanY) { 24054 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; 24055 } 24056 24057 // manipulation 24058 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { 24059 return TOUCH_ACTION_MANIPULATION; 24060 } 24061 24062 return TOUCH_ACTION_AUTO; 24063 } 24064 24065 function getTouchActionProps() { 24066 if (!NATIVE_TOUCH_ACTION) { 24067 return false; 24068 } 24069 var touchMap = {}; 24070 var cssSupports = window.CSS && window.CSS.supports; 24071 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) { 24072 24073 // If css.supports is not supported but there is native touch-action assume it supports 24074 // all values. This is the case for IE 10 and 11. 24075 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true; 24076 }); 24077 return touchMap; 24078 } 24079 24080 /** 24081 * Recognizer flow explained; * 24082 * All recognizers have the initial state of POSSIBLE when a input session starts. 24083 * The definition of a input session is from the first input until the last input, with all it's movement in it. * 24084 * Example session for mouse-input: mousedown -> mousemove -> mouseup 24085 * 24086 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed 24087 * which determines with state it should be. 24088 * 24089 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to 24090 * POSSIBLE to give it another change on the next cycle. 24091 * 24092 * Possible 24093 * | 24094 * +-----+---------------+ 24095 * | | 24096 * +-----+-----+ | 24097 * | | | 24098 * Failed Cancelled | 24099 * +-------+------+ 24100 * | | 24101 * Recognized Began 24102 * | 24103 * Changed 24104 * | 24105 * Ended/Recognized 24106 */ 24107 var STATE_POSSIBLE = 1; 24108 var STATE_BEGAN = 2; 24109 var STATE_CHANGED = 4; 24110 var STATE_ENDED = 8; 24111 var STATE_RECOGNIZED = STATE_ENDED; 24112 var STATE_CANCELLED = 16; 24113 var STATE_FAILED = 32; 24114 24115 /** 24116 * Recognizer 24117 * Every recognizer needs to extend from this class. 24118 * @constructor 24119 * @param {Object} options 24120 */ 24121 function Recognizer(options) { 24122 this.options = assign({}, this.defaults, options || {}); 24123 24124 this.id = uniqueId(); 24125 24126 this.manager = null; 24127 24128 // default is enable true 24129 this.options.enable = ifUndefined(this.options.enable, true); 24130 24131 this.state = STATE_POSSIBLE; 24132 24133 this.simultaneous = {}; 24134 this.requireFail = []; 24135 } 24136 24137 Recognizer.prototype = { 24138 /** 24139 * @virtual 24140 * @type {Object} 24141 */ 24142 defaults: {}, 24143 24144 /** 24145 * set options 24146 * @param {Object} options 24147 * @return {Recognizer} 24148 */ 24149 set: function(options) { 24150 assign(this.options, options); 24151 24152 // also update the touchAction, in case something changed about the directions/enabled state 24153 this.manager && this.manager.touchAction.update(); 24154 return this; 24155 }, 24156 24157 /** 24158 * recognize simultaneous with an other recognizer. 24159 * @param {Recognizer} otherRecognizer 24160 * @returns {Recognizer} this 24161 */ 24162 recognizeWith: function(otherRecognizer) { 24163 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) { 24164 return this; 24165 } 24166 24167 var simultaneous = this.simultaneous; 24168 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 24169 if (!simultaneous[otherRecognizer.id]) { 24170 simultaneous[otherRecognizer.id] = otherRecognizer; 24171 otherRecognizer.recognizeWith(this); 24172 } 24173 return this; 24174 }, 24175 24176 /** 24177 * drop the simultaneous link. it doesnt remove the link on the other recognizer. 24178 * @param {Recognizer} otherRecognizer 24179 * @returns {Recognizer} this 24180 */ 24181 dropRecognizeWith: function(otherRecognizer) { 24182 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) { 24183 return this; 24184 } 24185 24186 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 24187 delete this.simultaneous[otherRecognizer.id]; 24188 return this; 24189 }, 24190 24191 /** 24192 * recognizer can only run when an other is failing 24193 * @param {Recognizer} otherRecognizer 24194 * @returns {Recognizer} this 24195 */ 24196 requireFailure: function(otherRecognizer) { 24197 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) { 24198 return this; 24199 } 24200 24201 var requireFail = this.requireFail; 24202 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 24203 if (inArray(requireFail, otherRecognizer) === -1) { 24204 requireFail.push(otherRecognizer); 24205 otherRecognizer.requireFailure(this); 24206 } 24207 return this; 24208 }, 24209 24210 /** 24211 * drop the requireFailure link. it does not remove the link on the other recognizer. 24212 * @param {Recognizer} otherRecognizer 24213 * @returns {Recognizer} this 24214 */ 24215 dropRequireFailure: function(otherRecognizer) { 24216 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) { 24217 return this; 24218 } 24219 24220 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this); 24221 var index = inArray(this.requireFail, otherRecognizer); 24222 if (index > -1) { 24223 this.requireFail.splice(index, 1); 24224 } 24225 return this; 24226 }, 24227 24228 /** 24229 * has require failures boolean 24230 * @returns {boolean} 24231 */ 24232 hasRequireFailures: function() { 24233 return this.requireFail.length > 0; 24234 }, 24235 24236 /** 24237 * if the recognizer can recognize simultaneous with an other recognizer 24238 * @param {Recognizer} otherRecognizer 24239 * @returns {Boolean} 24240 */ 24241 canRecognizeWith: function(otherRecognizer) { 24242 return !!this.simultaneous[otherRecognizer.id]; 24243 }, 24244 24245 /** 24246 * You should use `tryEmit` instead of `emit` directly to check 24247 * that all the needed recognizers has failed before emitting. 24248 * @param {Object} input 24249 */ 24250 emit: function(input) { 24251 var self = this; 24252 var state = this.state; 24253 24254 function emit(event) { 24255 self.manager.emit(event, input); 24256 } 24257 24258 // 'panstart' and 'panmove' 24259 if (state < STATE_ENDED) { 24260 emit(self.options.event + stateStr(state)); 24261 } 24262 24263 emit(self.options.event); // simple 'eventName' events 24264 24265 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...) 24266 emit(input.additionalEvent); 24267 } 24268 24269 // panend and pancancel 24270 if (state >= STATE_ENDED) { 24271 emit(self.options.event + stateStr(state)); 24272 } 24273 }, 24274 24275 /** 24276 * Check that all the require failure recognizers has failed, 24277 * if true, it emits a gesture event, 24278 * otherwise, setup the state to FAILED. 24279 * @param {Object} input 24280 */ 24281 tryEmit: function(input) { 24282 if (this.canEmit()) { 24283 return this.emit(input); 24284 } 24285 // it's failing anyway 24286 this.state = STATE_FAILED; 24287 }, 24288 24289 /** 24290 * can we emit? 24291 * @returns {boolean} 24292 */ 24293 canEmit: function() { 24294 var i = 0; 24295 while (i < this.requireFail.length) { 24296 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) { 24297 return false; 24298 } 24299 i++; 24300 } 24301 return true; 24302 }, 24303 24304 /** 24305 * update the recognizer 24306 * @param {Object} inputData 24307 */ 24308 recognize: function(inputData) { 24309 // make a new copy of the inputData 24310 // so we can change the inputData without messing up the other recognizers 24311 var inputDataClone = assign({}, inputData); 24312 24313 // is is enabled and allow recognizing? 24314 if (!boolOrFn(this.options.enable, [this, inputDataClone])) { 24315 this.reset(); 24316 this.state = STATE_FAILED; 24317 return; 24318 } 24319 24320 // reset when we've reached the end 24321 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) { 24322 this.state = STATE_POSSIBLE; 24323 } 24324 24325 this.state = this.process(inputDataClone); 24326 24327 // the recognizer has recognized a gesture 24328 // so trigger an event 24329 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) { 24330 this.tryEmit(inputDataClone); 24331 } 24332 }, 24333 24334 /** 24335 * return the state of the recognizer 24336 * the actual recognizing happens in this method 24337 * @virtual 24338 * @param {Object} inputData 24339 * @returns {Const} STATE 24340 */ 24341 process: function(inputData) { }, // jshint ignore:line 24342 24343 /** 24344 * return the preferred touch-action 24345 * @virtual 24346 * @returns {Array} 24347 */ 24348 getTouchAction: function() { }, 24349 24350 /** 24351 * called when the gesture isn't allowed to recognize 24352 * like when another is being recognized or it is disabled 24353 * @virtual 24354 */ 24355 reset: function() { } 24356 }; 24357 24358 /** 24359 * get a usable string, used as event postfix 24360 * @param {Const} state 24361 * @returns {String} state 24362 */ 24363 function stateStr(state) { 24364 if (state & STATE_CANCELLED) { 24365 return 'cancel'; 24366 } else if (state & STATE_ENDED) { 24367 return 'end'; 24368 } else if (state & STATE_CHANGED) { 24369 return 'move'; 24370 } else if (state & STATE_BEGAN) { 24371 return 'start'; 24372 } 24373 return ''; 24374 } 24375 24376 /** 24377 * direction cons to string 24378 * @param {Const} direction 24379 * @returns {String} 24380 */ 24381 function directionStr(direction) { 24382 if (direction == DIRECTION_DOWN) { 24383 return 'down'; 24384 } else if (direction == DIRECTION_UP) { 24385 return 'up'; 24386 } else if (direction == DIRECTION_LEFT) { 24387 return 'left'; 24388 } else if (direction == DIRECTION_RIGHT) { 24389 return 'right'; 24390 } 24391 return ''; 24392 } 24393 24394 /** 24395 * get a recognizer by name if it is bound to a manager 24396 * @param {Recognizer|String} otherRecognizer 24397 * @param {Recognizer} recognizer 24398 * @returns {Recognizer} 24399 */ 24400 function getRecognizerByNameIfManager(otherRecognizer, recognizer) { 24401 var manager = recognizer.manager; 24402 if (manager) { 24403 return manager.get(otherRecognizer); 24404 } 24405 return otherRecognizer; 24406 } 24407 24408 /** 24409 * This recognizer is just used as a base for the simple attribute recognizers. 24410 * @constructor 24411 * @extends Recognizer 24412 */ 24413 function AttrRecognizer() { 24414 Recognizer.apply(this, arguments); 24415 } 24416 24417 inherit(AttrRecognizer, Recognizer, { 24418 /** 24419 * @namespace 24420 * @memberof AttrRecognizer 24421 */ 24422 defaults: { 24423 /** 24424 * @type {Number} 24425 * @default 1 24426 */ 24427 pointers: 1 24428 }, 24429 24430 /** 24431 * Used to check if it the recognizer receives valid input, like input.distance > 10. 24432 * @memberof AttrRecognizer 24433 * @param {Object} input 24434 * @returns {Boolean} recognized 24435 */ 24436 attrTest: function(input) { 24437 var optionPointers = this.options.pointers; 24438 return optionPointers === 0 || input.pointers.length === optionPointers; 24439 }, 24440 24441 /** 24442 * Process the input and return the state for the recognizer 24443 * @memberof AttrRecognizer 24444 * @param {Object} input 24445 * @returns {*} State 24446 */ 24447 process: function(input) { 24448 var state = this.state; 24449 var eventType = input.eventType; 24450 24451 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED); 24452 var isValid = this.attrTest(input); 24453 24454 // on cancel input and we've recognized before, return STATE_CANCELLED 24455 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) { 24456 return state | STATE_CANCELLED; 24457 } else if (isRecognized || isValid) { 24458 if (eventType & INPUT_END) { 24459 return state | STATE_ENDED; 24460 } else if (!(state & STATE_BEGAN)) { 24461 return STATE_BEGAN; 24462 } 24463 return state | STATE_CHANGED; 24464 } 24465 return STATE_FAILED; 24466 } 24467 }); 24468 24469 /** 24470 * Pan 24471 * Recognized when the pointer is down and moved in the allowed direction. 24472 * @constructor 24473 * @extends AttrRecognizer 24474 */ 24475 function PanRecognizer() { 24476 AttrRecognizer.apply(this, arguments); 24477 24478 this.pX = null; 24479 this.pY = null; 24480 } 24481 24482 inherit(PanRecognizer, AttrRecognizer, { 24483 /** 24484 * @namespace 24485 * @memberof PanRecognizer 24486 */ 24487 defaults: { 24488 event: 'pan', 24489 threshold: 10, 24490 pointers: 1, 24491 direction: DIRECTION_ALL 24492 }, 24493 24494 getTouchAction: function() { 24495 var direction = this.options.direction; 24496 var actions = []; 24497 if (direction & DIRECTION_HORIZONTAL) { 24498 actions.push(TOUCH_ACTION_PAN_Y); 24499 } 24500 if (direction & DIRECTION_VERTICAL) { 24501 actions.push(TOUCH_ACTION_PAN_X); 24502 } 24503 return actions; 24504 }, 24505 24506 directionTest: function(input) { 24507 var options = this.options; 24508 var hasMoved = true; 24509 var distance = input.distance; 24510 var direction = input.direction; 24511 var x = input.deltaX; 24512 var y = input.deltaY; 24513 24514 // lock to axis? 24515 if (!(direction & options.direction)) { 24516 if (options.direction & DIRECTION_HORIZONTAL) { 24517 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; 24518 hasMoved = x != this.pX; 24519 distance = Math.abs(input.deltaX); 24520 } else { 24521 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN; 24522 hasMoved = y != this.pY; 24523 distance = Math.abs(input.deltaY); 24524 } 24525 } 24526 input.direction = direction; 24527 return hasMoved && distance > options.threshold && direction & options.direction; 24528 }, 24529 24530 attrTest: function(input) { 24531 return AttrRecognizer.prototype.attrTest.call(this, input) && 24532 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input))); 24533 }, 24534 24535 emit: function(input) { 24536 24537 this.pX = input.deltaX; 24538 this.pY = input.deltaY; 24539 24540 var direction = directionStr(input.direction); 24541 24542 if (direction) { 24543 input.additionalEvent = this.options.event + direction; 24544 } 24545 this._super.emit.call(this, input); 24546 } 24547 }); 24548 24549 /** 24550 * Pinch 24551 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out). 24552 * @constructor 24553 * @extends AttrRecognizer 24554 */ 24555 function PinchRecognizer() { 24556 AttrRecognizer.apply(this, arguments); 24557 } 24558 24559 inherit(PinchRecognizer, AttrRecognizer, { 24560 /** 24561 * @namespace 24562 * @memberof PinchRecognizer 24563 */ 24564 defaults: { 24565 event: 'pinch', 24566 threshold: 0, 24567 pointers: 2 24568 }, 24569 24570 getTouchAction: function() { 24571 return [TOUCH_ACTION_NONE]; 24572 }, 24573 24574 attrTest: function(input) { 24575 return this._super.attrTest.call(this, input) && 24576 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN); 24577 }, 24578 24579 emit: function(input) { 24580 if (input.scale !== 1) { 24581 var inOut = input.scale < 1 ? 'in' : 'out'; 24582 input.additionalEvent = this.options.event + inOut; 24583 } 24584 this._super.emit.call(this, input); 24585 } 24586 }); 24587 24588 /** 24589 * Press 24590 * Recognized when the pointer is down for x ms without any movement. 24591 * @constructor 24592 * @extends Recognizer 24593 */ 24594 function PressRecognizer() { 24595 Recognizer.apply(this, arguments); 24596 24597 this._timer = null; 24598 this._input = null; 24599 } 24600 24601 inherit(PressRecognizer, Recognizer, { 24602 /** 24603 * @namespace 24604 * @memberof PressRecognizer 24605 */ 24606 defaults: { 24607 event: 'press', 24608 pointers: 1, 24609 time: 251, // minimal time of the pointer to be pressed 24610 threshold: 9 // a minimal movement is ok, but keep it low 24611 }, 24612 24613 getTouchAction: function() { 24614 return [TOUCH_ACTION_AUTO]; 24615 }, 24616 24617 process: function(input) { 24618 var options = this.options; 24619 var validPointers = input.pointers.length === options.pointers; 24620 var validMovement = input.distance < options.threshold; 24621 var validTime = input.deltaTime > options.time; 24622 24623 this._input = input; 24624 24625 // we only allow little movement 24626 // and we've reached an end event, so a tap is possible 24627 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) { 24628 this.reset(); 24629 } else if (input.eventType & INPUT_START) { 24630 this.reset(); 24631 this._timer = setTimeoutContext(function() { 24632 this.state = STATE_RECOGNIZED; 24633 this.tryEmit(); 24634 }, options.time, this); 24635 } else if (input.eventType & INPUT_END) { 24636 return STATE_RECOGNIZED; 24637 } 24638 return STATE_FAILED; 24639 }, 24640 24641 reset: function() { 24642 clearTimeout(this._timer); 24643 }, 24644 24645 emit: function(input) { 24646 if (this.state !== STATE_RECOGNIZED) { 24647 return; 24648 } 24649 24650 if (input && (input.eventType & INPUT_END)) { 24651 this.manager.emit(this.options.event + 'up', input); 24652 } else { 24653 this._input.timeStamp = now(); 24654 this.manager.emit(this.options.event, this._input); 24655 } 24656 } 24657 }); 24658 24659 /** 24660 * Rotate 24661 * Recognized when two or more pointer are moving in a circular motion. 24662 * @constructor 24663 * @extends AttrRecognizer 24664 */ 24665 function RotateRecognizer() { 24666 AttrRecognizer.apply(this, arguments); 24667 } 24668 24669 inherit(RotateRecognizer, AttrRecognizer, { 24670 /** 24671 * @namespace 24672 * @memberof RotateRecognizer 24673 */ 24674 defaults: { 24675 event: 'rotate', 24676 threshold: 0, 24677 pointers: 2 24678 }, 24679 24680 getTouchAction: function() { 24681 return [TOUCH_ACTION_NONE]; 24682 }, 24683 24684 attrTest: function(input) { 24685 return this._super.attrTest.call(this, input) && 24686 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN); 24687 } 24688 }); 24689 24690 /** 24691 * Swipe 24692 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction. 24693 * @constructor 24694 * @extends AttrRecognizer 24695 */ 24696 function SwipeRecognizer() { 24697 AttrRecognizer.apply(this, arguments); 24698 } 24699 24700 inherit(SwipeRecognizer, AttrRecognizer, { 24701 /** 24702 * @namespace 24703 * @memberof SwipeRecognizer 24704 */ 24705 defaults: { 24706 event: 'swipe', 24707 threshold: 10, 24708 velocity: 0.3, 24709 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL, 24710 pointers: 1 24711 }, 24712 24713 getTouchAction: function() { 24714 return PanRecognizer.prototype.getTouchAction.call(this); 24715 }, 24716 24717 attrTest: function(input) { 24718 var direction = this.options.direction; 24719 var velocity; 24720 24721 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) { 24722 velocity = input.overallVelocity; 24723 } else if (direction & DIRECTION_HORIZONTAL) { 24724 velocity = input.overallVelocityX; 24725 } else if (direction & DIRECTION_VERTICAL) { 24726 velocity = input.overallVelocityY; 24727 } 24728 24729 return this._super.attrTest.call(this, input) && 24730 direction & input.offsetDirection && 24731 input.distance > this.options.threshold && 24732 input.maxPointers == this.options.pointers && 24733 abs(velocity) > this.options.velocity && input.eventType & INPUT_END; 24734 }, 24735 24736 emit: function(input) { 24737 var direction = directionStr(input.offsetDirection); 24738 if (direction) { 24739 this.manager.emit(this.options.event + direction, input); 24740 } 24741 24742 this.manager.emit(this.options.event, input); 24743 } 24744 }); 24745 24746 /** 24747 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur 24748 * between the given interval and position. The delay option can be used to recognize multi-taps without firing 24749 * a single tap. 24750 * 24751 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of 24752 * multi-taps being recognized. 24753 * @constructor 24754 * @extends Recognizer 24755 */ 24756 function TapRecognizer() { 24757 Recognizer.apply(this, arguments); 24758 24759 // previous time and center, 24760 // used for tap counting 24761 this.pTime = false; 24762 this.pCenter = false; 24763 24764 this._timer = null; 24765 this._input = null; 24766 this.count = 0; 24767 } 24768 24769 inherit(TapRecognizer, Recognizer, { 24770 /** 24771 * @namespace 24772 * @memberof PinchRecognizer 24773 */ 24774 defaults: { 24775 event: 'tap', 24776 pointers: 1, 24777 taps: 1, 24778 interval: 300, // max time between the multi-tap taps 24779 time: 250, // max time of the pointer to be down (like finger on the screen) 24780 threshold: 9, // a minimal movement is ok, but keep it low 24781 posThreshold: 10 // a multi-tap can be a bit off the initial position 24782 }, 24783 24784 getTouchAction: function() { 24785 return [TOUCH_ACTION_MANIPULATION]; 24786 }, 24787 24788 process: function(input) { 24789 var options = this.options; 24790 24791 var validPointers = input.pointers.length === options.pointers; 24792 var validMovement = input.distance < options.threshold; 24793 var validTouchTime = input.deltaTime < options.time; 24794 24795 this.reset(); 24796 24797 if ((input.eventType & INPUT_START) && (this.count === 0)) { 24798 return this.failTimeout(); 24799 } 24800 24801 // we only allow little movement 24802 // and we've reached an end event, so a tap is possible 24803 if (validMovement && validTouchTime && validPointers) { 24804 if (input.eventType != INPUT_END) { 24805 return this.failTimeout(); 24806 } 24807 24808 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true; 24809 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold; 24810 24811 this.pTime = input.timeStamp; 24812 this.pCenter = input.center; 24813 24814 if (!validMultiTap || !validInterval) { 24815 this.count = 1; 24816 } else { 24817 this.count += 1; 24818 } 24819 24820 this._input = input; 24821 24822 // if tap count matches we have recognized it, 24823 // else it has began recognizing... 24824 var tapCount = this.count % options.taps; 24825 if (tapCount === 0) { 24826 // no failing requirements, immediately trigger the tap event 24827 // or wait as long as the multitap interval to trigger 24828 if (!this.hasRequireFailures()) { 24829 return STATE_RECOGNIZED; 24830 } else { 24831 this._timer = setTimeoutContext(function() { 24832 this.state = STATE_RECOGNIZED; 24833 this.tryEmit(); 24834 }, options.interval, this); 24835 return STATE_BEGAN; 24836 } 24837 } 24838 } 24839 return STATE_FAILED; 24840 }, 24841 24842 failTimeout: function() { 24843 this._timer = setTimeoutContext(function() { 24844 this.state = STATE_FAILED; 24845 }, this.options.interval, this); 24846 return STATE_FAILED; 24847 }, 24848 24849 reset: function() { 24850 clearTimeout(this._timer); 24851 }, 24852 24853 emit: function() { 24854 if (this.state == STATE_RECOGNIZED) { 24855 this._input.tapCount = this.count; 24856 this.manager.emit(this.options.event, this._input); 24857 } 24858 } 24859 }); 24860 24861 /** 24862 * Simple way to create a manager with a default set of recognizers. 24863 * @param {HTMLElement} element 24864 * @param {Object} [options] 24865 * @constructor 24866 */ 24867 function Hammer(element, options) { 24868 options = options || {}; 24869 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset); 24870 return new Manager(element, options); 24871 } 24872 24873 /** 24874 * @const {string} 24875 */ 24876 Hammer.VERSION = '2.0.7'; 24877 24878 /** 24879 * default settings 24880 * @namespace 24881 */ 24882 Hammer.defaults = { 24883 /** 24884 * set if DOM events are being triggered. 24885 * But this is slower and unused by simple implementations, so disabled by default. 24886 * @type {Boolean} 24887 * @default false 24888 */ 24889 domEvents: false, 24890 24891 /** 24892 * The value for the touchAction property/fallback. 24893 * When set to `compute` it will magically set the correct value based on the added recognizers. 24894 * @type {String} 24895 * @default compute 24896 */ 24897 touchAction: TOUCH_ACTION_COMPUTE, 24898 24899 /** 24900 * @type {Boolean} 24901 * @default true 24902 */ 24903 enable: true, 24904 24905 /** 24906 * EXPERIMENTAL FEATURE -- can be removed/changed 24907 * Change the parent input target element. 24908 * If Null, then it is being set the to main element. 24909 * @type {Null|EventTarget} 24910 * @default null 24911 */ 24912 inputTarget: null, 24913 24914 /** 24915 * force an input class 24916 * @type {Null|Function} 24917 * @default null 24918 */ 24919 inputClass: null, 24920 24921 /** 24922 * Default recognizer setup when calling `Hammer()` 24923 * When creating a new Manager these will be skipped. 24924 * @type {Array} 24925 */ 24926 preset: [ 24927 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...] 24928 [RotateRecognizer, {enable: false}], 24929 [PinchRecognizer, {enable: false}, ['rotate']], 24930 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}], 24931 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']], 24932 [TapRecognizer], 24933 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']], 24934 [PressRecognizer] 24935 ], 24936 24937 /** 24938 * Some CSS properties can be used to improve the working of Hammer. 24939 * Add them to this method and they will be set when creating a new Manager. 24940 * @namespace 24941 */ 24942 cssProps: { 24943 /** 24944 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers. 24945 * @type {String} 24946 * @default 'none' 24947 */ 24948 userSelect: 'none', 24949 24950 /** 24951 * Disable the Windows Phone grippers when pressing an element. 24952 * @type {String} 24953 * @default 'none' 24954 */ 24955 touchSelect: 'none', 24956 24957 /** 24958 * Disables the default callout shown when you touch and hold a touch target. 24959 * On iOS, when you touch and hold a touch target such as a link, Safari displays 24960 * a callout containing information about the link. This property allows you to disable that callout. 24961 * @type {String} 24962 * @default 'none' 24963 */ 24964 touchCallout: 'none', 24965 24966 /** 24967 * Specifies whether zooming is enabled. Used by IE10> 24968 * @type {String} 24969 * @default 'none' 24970 */ 24971 contentZooming: 'none', 24972 24973 /** 24974 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers. 24975 * @type {String} 24976 * @default 'none' 24977 */ 24978 userDrag: 'none', 24979 24980 /** 24981 * Overrides the highlight color shown when the user taps a link or a JavaScript 24982 * clickable element in iOS. This property obeys the alpha value, if specified. 24983 * @type {String} 24984 * @default 'rgba(0,0,0,0)' 24985 */ 24986 tapHighlightColor: 'rgba(0,0,0,0)' 24987 } 24988 }; 24989 24990 var STOP = 1; 24991 var FORCED_STOP = 2; 24992 24993 /** 24994 * Manager 24995 * @param {HTMLElement} element 24996 * @param {Object} [options] 24997 * @constructor 24998 */ 24999 function Manager(element, options) { 25000 this.options = assign({}, Hammer.defaults, options || {}); 25001 25002 this.options.inputTarget = this.options.inputTarget || element; 25003 25004 this.handlers = {}; 25005 this.session = {}; 25006 this.recognizers = []; 25007 this.oldCssProps = {}; 25008 25009 this.element = element; 25010 this.input = createInputInstance(this); 25011 this.touchAction = new TouchAction(this, this.options.touchAction); 25012 25013 toggleCssProps(this, true); 25014 25015 each(this.options.recognizers, function(item) { 25016 var recognizer = this.add(new (item[0])(item[1])); 25017 item[2] && recognizer.recognizeWith(item[2]); 25018 item[3] && recognizer.requireFailure(item[3]); 25019 }, this); 25020 } 25021 25022 Manager.prototype = { 25023 /** 25024 * set options 25025 * @param {Object} options 25026 * @returns {Manager} 25027 */ 25028 set: function(options) { 25029 assign(this.options, options); 25030 25031 // Options that need a little more setup 25032 if (options.touchAction) { 25033 this.touchAction.update(); 25034 } 25035 if (options.inputTarget) { 25036 // Clean up existing event listeners and reinitialize 25037 this.input.destroy(); 25038 this.input.target = options.inputTarget; 25039 this.input.init(); 25040 } 25041 return this; 25042 }, 25043 25044 /** 25045 * stop recognizing for this session. 25046 * This session will be discarded, when a new [input]start event is fired. 25047 * When forced, the recognizer cycle is stopped immediately. 25048 * @param {Boolean} [force] 25049 */ 25050 stop: function(force) { 25051 this.session.stopped = force ? FORCED_STOP : STOP; 25052 }, 25053 25054 /** 25055 * run the recognizers! 25056 * called by the inputHandler function on every movement of the pointers (touches) 25057 * it walks through all the recognizers and tries to detect the gesture that is being made 25058 * @param {Object} inputData 25059 */ 25060 recognize: function(inputData) { 25061 var session = this.session; 25062 if (session.stopped) { 25063 return; 25064 } 25065 25066 // run the touch-action polyfill 25067 this.touchAction.preventDefaults(inputData); 25068 25069 var recognizer; 25070 var recognizers = this.recognizers; 25071 25072 // this holds the recognizer that is being recognized. 25073 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED 25074 // if no recognizer is detecting a thing, it is set to `null` 25075 var curRecognizer = session.curRecognizer; 25076 25077 // reset when the last recognizer is recognized 25078 // or when we're in a new session 25079 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) { 25080 curRecognizer = session.curRecognizer = null; 25081 } 25082 25083 var i = 0; 25084 while (i < recognizers.length) { 25085 recognizer = recognizers[i]; 25086 25087 // find out if we are allowed try to recognize the input for this one. 25088 // 1. allow if the session is NOT forced stopped (see the .stop() method) 25089 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one 25090 // that is being recognized. 25091 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer. 25092 // this can be setup with the `recognizeWith()` method on the recognizer. 25093 if (session.stopped !== FORCED_STOP && ( // 1 25094 !curRecognizer || recognizer == curRecognizer || // 2 25095 recognizer.canRecognizeWith(curRecognizer))) { // 3 25096 recognizer.recognize(inputData); 25097 } else { 25098 recognizer.reset(); 25099 } 25100 25101 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the 25102 // current active recognizer. but only if we don't already have an active recognizer 25103 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) { 25104 curRecognizer = session.curRecognizer = recognizer; 25105 } 25106 i++; 25107 } 25108 }, 25109 25110 /** 25111 * get a recognizer by its event name. 25112 * @param {Recognizer|String} recognizer 25113 * @returns {Recognizer|Null} 25114 */ 25115 get: function(recognizer) { 25116 if (recognizer instanceof Recognizer) { 25117 return recognizer; 25118 } 25119 25120 var recognizers = this.recognizers; 25121 for (var i = 0; i < recognizers.length; i++) { 25122 if (recognizers[i].options.event == recognizer) { 25123 return recognizers[i]; 25124 } 25125 } 25126 return null; 25127 }, 25128 25129 /** 25130 * add a recognizer to the manager 25131 * existing recognizers with the same event name will be removed 25132 * @param {Recognizer} recognizer 25133 * @returns {Recognizer|Manager} 25134 */ 25135 add: function(recognizer) { 25136 if (invokeArrayArg(recognizer, 'add', this)) { 25137 return this; 25138 } 25139 25140 // remove existing 25141 var existing = this.get(recognizer.options.event); 25142 if (existing) { 25143 this.remove(existing); 25144 } 25145 25146 this.recognizers.push(recognizer); 25147 recognizer.manager = this; 25148 25149 this.touchAction.update(); 25150 return recognizer; 25151 }, 25152 25153 /** 25154 * remove a recognizer by name or instance 25155 * @param {Recognizer|String} recognizer 25156 * @returns {Manager} 25157 */ 25158 remove: function(recognizer) { 25159 if (invokeArrayArg(recognizer, 'remove', this)) { 25160 return this; 25161 } 25162 25163 recognizer = this.get(recognizer); 25164 25165 // let's make sure this recognizer exists 25166 if (recognizer) { 25167 var recognizers = this.recognizers; 25168 var index = inArray(recognizers, recognizer); 25169 25170 if (index !== -1) { 25171 recognizers.splice(index, 1); 25172 this.touchAction.update(); 25173 } 25174 } 25175 25176 return this; 25177 }, 25178 25179 /** 25180 * bind event 25181 * @param {String} events 25182 * @param {Function} handler 25183 * @returns {EventEmitter} this 25184 */ 25185 on: function(events, handler) { 25186 if (events === undefined$1) { 25187 return; 25188 } 25189 if (handler === undefined$1) { 25190 return; 25191 } 25192 25193 var handlers = this.handlers; 25194 each(splitStr(events), function(event) { 25195 handlers[event] = handlers[event] || []; 25196 handlers[event].push(handler); 25197 }); 25198 return this; 25199 }, 25200 25201 /** 25202 * unbind event, leave emit blank to remove all handlers 25203 * @param {String} events 25204 * @param {Function} [handler] 25205 * @returns {EventEmitter} this 25206 */ 25207 off: function(events, handler) { 25208 if (events === undefined$1) { 25209 return; 25210 } 25211 25212 var handlers = this.handlers; 25213 each(splitStr(events), function(event) { 25214 if (!handler) { 25215 delete handlers[event]; 25216 } else { 25217 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1); 25218 } 25219 }); 25220 return this; 25221 }, 25222 25223 /** 25224 * emit event to the listeners 25225 * @param {String} event 25226 * @param {Object} data 25227 */ 25228 emit: function(event, data) { 25229 // we also want to trigger dom events 25230 if (this.options.domEvents) { 25231 triggerDomEvent(event, data); 25232 } 25233 25234 // no handlers, so skip it all 25235 var handlers = this.handlers[event] && this.handlers[event].slice(); 25236 if (!handlers || !handlers.length) { 25237 return; 25238 } 25239 25240 data.type = event; 25241 data.preventDefault = function() { 25242 data.srcEvent.preventDefault(); 25243 }; 25244 25245 var i = 0; 25246 while (i < handlers.length) { 25247 handlers[i](data); 25248 i++; 25249 } 25250 }, 25251 25252 /** 25253 * destroy the manager and unbinds all events 25254 * it doesn't unbind dom events, that is the user own responsibility 25255 */ 25256 destroy: function() { 25257 this.element && toggleCssProps(this, false); 25258 25259 this.handlers = {}; 25260 this.session = {}; 25261 this.input.destroy(); 25262 this.element = null; 25263 } 25264 }; 25265 25266 /** 25267 * add/remove the css properties as defined in manager.options.cssProps 25268 * @param {Manager} manager 25269 * @param {Boolean} add 25270 */ 25271 function toggleCssProps(manager, add) { 25272 var element = manager.element; 25273 if (!element.style) { 25274 return; 25275 } 25276 var prop; 25277 each(manager.options.cssProps, function(value, name) { 25278 prop = prefixed(element.style, name); 25279 if (add) { 25280 manager.oldCssProps[prop] = element.style[prop]; 25281 element.style[prop] = value; 25282 } else { 25283 element.style[prop] = manager.oldCssProps[prop] || ''; 25284 } 25285 }); 25286 if (!add) { 25287 manager.oldCssProps = {}; 25288 } 25289 } 25290 25291 /** 25292 * trigger dom event 25293 * @param {String} event 25294 * @param {Object} data 25295 */ 25296 function triggerDomEvent(event, data) { 25297 var gestureEvent = document.createEvent('Event'); 25298 gestureEvent.initEvent(event, true, true); 25299 gestureEvent.gesture = data; 25300 data.target.dispatchEvent(gestureEvent); 25301 } 25302 25303 assign(Hammer, { 25304 INPUT_START: INPUT_START, 25305 INPUT_MOVE: INPUT_MOVE, 25306 INPUT_END: INPUT_END, 25307 INPUT_CANCEL: INPUT_CANCEL, 25308 25309 STATE_POSSIBLE: STATE_POSSIBLE, 25310 STATE_BEGAN: STATE_BEGAN, 25311 STATE_CHANGED: STATE_CHANGED, 25312 STATE_ENDED: STATE_ENDED, 25313 STATE_RECOGNIZED: STATE_RECOGNIZED, 25314 STATE_CANCELLED: STATE_CANCELLED, 25315 STATE_FAILED: STATE_FAILED, 25316 25317 DIRECTION_NONE: DIRECTION_NONE, 25318 DIRECTION_LEFT: DIRECTION_LEFT, 25319 DIRECTION_RIGHT: DIRECTION_RIGHT, 25320 DIRECTION_UP: DIRECTION_UP, 25321 DIRECTION_DOWN: DIRECTION_DOWN, 25322 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL, 25323 DIRECTION_VERTICAL: DIRECTION_VERTICAL, 25324 DIRECTION_ALL: DIRECTION_ALL, 25325 25326 Manager: Manager, 25327 Input: Input, 25328 TouchAction: TouchAction, 25329 25330 TouchInput: TouchInput, 25331 MouseInput: MouseInput, 25332 PointerEventInput: PointerEventInput, 25333 TouchMouseInput: TouchMouseInput, 25334 SingleTouchInput: SingleTouchInput, 25335 25336 Recognizer: Recognizer, 25337 AttrRecognizer: AttrRecognizer, 25338 Tap: TapRecognizer, 25339 Pan: PanRecognizer, 25340 Swipe: SwipeRecognizer, 25341 Pinch: PinchRecognizer, 25342 Rotate: RotateRecognizer, 25343 Press: PressRecognizer, 25344 25345 on: addEventListeners, 25346 off: removeEventListeners, 25347 each: each, 25348 merge: merge, 25349 extend: extend, 25350 assign: assign, 25351 inherit: inherit, 25352 bindFn: bindFn, 25353 prefixed: prefixed 25354 }); 25355 25356 // this prevents errors when Hammer is loaded in the presence of an AMD 25357 // style loader but by script tag, not by the loader. 25358 var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line 25359 freeGlobal.Hammer = Hammer; 25360 25361 if (typeof undefined$1 === 'function' && undefined$1.amd) { 25362 undefined$1(function() { 25363 return Hammer; 25364 }); 25365 } else if (module.exports) { 25366 module.exports = Hammer; 25367 } else { 25368 window[exportName] = Hammer; 25369 } 25370 25371 })(window, document, 'Hammer'); 25372 }(hammer)); 25373 25374 var Hammer = hammer.exports; 25375 25376 var MIN_ZOOM = 0.2, 25377 MAX_ZOOM = 4; 25378 25379 var mouseEvents = [ 25380 'mousedown', 25381 'mouseup', 25382 'mouseover', 25383 'mouseout', 25384 'click', 25385 'dblclick' 25386 ]; 25387 25388 function get(service, injector) { 25389 return injector.get(service, false); 25390 } 25391 25392 function stopEvent(event) { 25393 25394 event.preventDefault(); 25395 25396 if (typeof event.stopPropagation === 'function') { 25397 event.stopPropagation(); 25398 } else if (event.srcEvent && typeof event.srcEvent.stopPropagation === 'function') { 25399 25400 // iPhone & iPad 25401 event.srcEvent.stopPropagation(); 25402 } 25403 25404 if (typeof event.stopImmediatePropagation === 'function') { 25405 event.stopImmediatePropagation(); 25406 } 25407 } 25408 25409 25410 function createTouchRecognizer(node) { 25411 25412 function stopMouse(event) { 25413 25414 forEach(mouseEvents, function(e) { 25415 componentEvent.bind(node, e, stopEvent, true); 25416 }); 25417 } 25418 25419 function allowMouse(event) { 25420 setTimeout(function() { 25421 forEach(mouseEvents, function(e) { 25422 componentEvent.unbind(node, e, stopEvent, true); 25423 }); 25424 }, 500); 25425 } 25426 25427 componentEvent.bind(node, 'touchstart', stopMouse, true); 25428 componentEvent.bind(node, 'touchend', allowMouse, true); 25429 componentEvent.bind(node, 'touchcancel', allowMouse, true); 25430 25431 // A touch event recognizer that handles 25432 // touch events only (we know, we can already handle 25433 // mouse events out of the box) 25434 25435 var recognizer = new Hammer.Manager(node, { 25436 inputClass: Hammer.TouchInput, 25437 recognizers: [], 25438 domEvents: true 25439 }); 25440 25441 25442 var tap = new Hammer.Tap(); 25443 var pan = new Hammer.Pan({ threshold: 10 }); 25444 var press = new Hammer.Press(); 25445 var pinch = new Hammer.Pinch(); 25446 25447 var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 }); 25448 25449 pinch.requireFailure(pan); 25450 pinch.requireFailure(press); 25451 25452 recognizer.add([ pan, press, pinch, doubleTap, tap ]); 25453 25454 recognizer.reset = function(force) { 25455 var recognizers = this.recognizers, 25456 session = this.session; 25457 25458 if (session.stopped) { 25459 return; 25460 } 25461 25462 recognizer.stop(force); 25463 25464 setTimeout(function() { 25465 var i, r; 25466 for (i = 0; (r = recognizers[i]); i++) { 25467 r.reset(); 25468 r.state = 8; // FAILED STATE 25469 } 25470 25471 session.curRecognizer = null; 25472 }, 0); 25473 }; 25474 25475 recognizer.on('hammer.input', function(event) { 25476 if (event.srcEvent.defaultPrevented) { 25477 recognizer.reset(true); 25478 } 25479 }); 25480 25481 return recognizer; 25482 } 25483 25484 /** 25485 * A plugin that provides touch events for elements. 25486 * 25487 * @param {EventBus} eventBus 25488 * @param {InteractionEvents} interactionEvents 25489 */ 25490 function TouchInteractionEvents( 25491 injector, canvas, eventBus, 25492 elementRegistry, interactionEvents) { 25493 25494 // optional integrations 25495 var dragging = get('dragging', injector), 25496 move = get('move', injector), 25497 contextPad = get('contextPad', injector), 25498 palette = get('palette', injector); 25499 25500 // the touch recognizer 25501 var recognizer; 25502 25503 function handler(type) { 25504 25505 return function(event) { 25506 25507 interactionEvents.fire(type, event); 25508 }; 25509 } 25510 25511 function getGfx(target) { 25512 var node = closest(target, 'svg, .djs-element', true); 25513 return node; 25514 } 25515 25516 function initEvents(svg) { 25517 25518 // touch recognizer 25519 recognizer = createTouchRecognizer(svg); 25520 25521 recognizer.on('doubletap', handler('element.dblclick')); 25522 25523 recognizer.on('tap', handler('element.click')); 25524 25525 function startGrabCanvas(event) { 25526 25527 var lx = 0, ly = 0; 25528 25529 function update(e) { 25530 25531 var dx = e.deltaX - lx, 25532 dy = e.deltaY - ly; 25533 25534 canvas.scroll({ dx: dx, dy: dy }); 25535 25536 lx = e.deltaX; 25537 ly = e.deltaY; 25538 } 25539 25540 function end(e) { 25541 recognizer.off('panmove', update); 25542 recognizer.off('panend', end); 25543 recognizer.off('pancancel', end); 25544 } 25545 25546 recognizer.on('panmove', update); 25547 recognizer.on('panend', end); 25548 recognizer.on('pancancel', end); 25549 } 25550 25551 function startGrab(event) { 25552 25553 var gfx = getGfx(event.target), 25554 element = gfx && elementRegistry.get(gfx); 25555 25556 // recognizer 25557 if (move && canvas.getRootElement() !== element) { 25558 return move.start(event, element, true); 25559 } else { 25560 startGrabCanvas(); 25561 } 25562 } 25563 25564 function startZoom(e) { 25565 25566 var zoom = canvas.zoom(), 25567 mid = e.center; 25568 25569 function update(e) { 25570 25571 var ratio = 1 - (1 - e.scale) / 1.50, 25572 newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, ratio * zoom)); 25573 25574 canvas.zoom(newZoom, mid); 25575 25576 stopEvent(e); 25577 } 25578 25579 function end(e) { 25580 recognizer.off('pinchmove', update); 25581 recognizer.off('pinchend', end); 25582 recognizer.off('pinchcancel', end); 25583 25584 recognizer.reset(true); 25585 } 25586 25587 recognizer.on('pinchmove', update); 25588 recognizer.on('pinchend', end); 25589 recognizer.on('pinchcancel', end); 25590 } 25591 25592 recognizer.on('panstart', startGrab); 25593 recognizer.on('press', startGrab); 25594 25595 recognizer.on('pinchstart', startZoom); 25596 } 25597 25598 if (dragging) { 25599 25600 // simulate hover during dragging 25601 eventBus.on('drag.move', function(event) { 25602 25603 var originalEvent = event.originalEvent; 25604 25605 if (!originalEvent || originalEvent instanceof MouseEvent) { 25606 return; 25607 } 25608 25609 var position = toPoint(originalEvent); 25610 25611 // this gets really expensive ... 25612 var node = document.elementFromPoint(position.x, position.y), 25613 gfx = getGfx(node), 25614 element = gfx && elementRegistry.get(gfx); 25615 25616 if (element !== event.hover) { 25617 if (event.hover) { 25618 dragging.out(event); 25619 } 25620 25621 if (element) { 25622 dragging.hover({ element: element, gfx: gfx }); 25623 25624 event.hover = element; 25625 event.hoverGfx = gfx; 25626 } 25627 } 25628 }); 25629 } 25630 25631 if (contextPad) { 25632 25633 eventBus.on('contextPad.create', function(event) { 25634 var node = event.pad.html; 25635 25636 // touch recognizer 25637 var padRecognizer = createTouchRecognizer(node); 25638 25639 padRecognizer.on('panstart', function(event) { 25640 contextPad.trigger('dragstart', event, true); 25641 }); 25642 25643 padRecognizer.on('press', function(event) { 25644 contextPad.trigger('dragstart', event, true); 25645 }); 25646 25647 padRecognizer.on('tap', function(event) { 25648 contextPad.trigger('click', event); 25649 }); 25650 }); 25651 } 25652 25653 if (palette) { 25654 eventBus.on('palette.create', function(event) { 25655 var node = event.container; 25656 25657 // touch recognizer 25658 var padRecognizer = createTouchRecognizer(node); 25659 25660 padRecognizer.on('panstart', function(event) { 25661 palette.trigger('dragstart', event, true); 25662 }); 25663 25664 padRecognizer.on('press', function(event) { 25665 palette.trigger('dragstart', event, true); 25666 }); 25667 25668 padRecognizer.on('tap', function(event) { 25669 palette.trigger('click', event); 25670 }); 25671 }); 25672 } 25673 25674 eventBus.on('canvas.init', function(event) { 25675 initEvents(event.svg); 25676 }); 25677 } 25678 25679 25680 TouchInteractionEvents.$inject = [ 25681 'injector', 25682 'canvas', 25683 'eventBus', 25684 'elementRegistry', 25685 'interactionEvents', 25686 'touchFix' 25687 ]; 25688 25689 function TouchFix(canvas, eventBus) { 25690 25691 var self = this; 25692 25693 eventBus.on('canvas.init', function(e) { 25694 self.addBBoxMarker(e.svg); 25695 }); 25696 } 25697 25698 TouchFix.$inject = [ 'canvas', 'eventBus' ]; 25699 25700 25701 /** 25702 * Safari mobile (iOS 7) does not fire touchstart event in <SVG> element 25703 * if there is no shape between 0,0 and viewport elements origin. 25704 * 25705 * So touchstart event is only fired when the <g class="viewport"> element was hit. 25706 * Putting an element over and below the 'viewport' fixes that behavior. 25707 */ 25708 TouchFix.prototype.addBBoxMarker = function(svg) { 25709 25710 var markerStyle = { 25711 fill: 'none', 25712 class: 'outer-bound-marker' 25713 }; 25714 25715 var rect1 = create$1('rect'); 25716 attr(rect1, { 25717 x: -10000, 25718 y: 10000, 25719 width: 10, 25720 height: 10 25721 }); 25722 attr(rect1, markerStyle); 25723 25724 append(svg, rect1); 25725 25726 var rect2 = create$1('rect'); 25727 attr(rect2, { 25728 x: 10000, 25729 y: 10000, 25730 width: 10, 25731 height: 10 25732 }); 25733 attr(rect2, markerStyle); 25734 25735 append(svg, rect2); 25736 }; 25737 25738 var TouchModule$1 = { 25739 __depends__: [ InteractionEventsModule$1 ], 25740 __init__: [ 'touchInteractionEvents' ], 25741 touchInteractionEvents: [ 'type', TouchInteractionEvents ], 25742 touchFix: [ 'type', TouchFix ] 25743 }; 25744 25745 var TouchModule = { 25746 __depends__: [ 25747 TouchModule$1 25748 ] 25749 }; 25750 25751 function last(arr) { 25752 return arr && arr[arr.length - 1]; 25753 } 25754 25755 function sortTopOrMiddle(element) { 25756 return element.y; 25757 } 25758 25759 function sortLeftOrCenter(element) { 25760 return element.x; 25761 } 25762 25763 /** 25764 * Sorting functions for different types of alignment 25765 * 25766 * @type {Object} 25767 * 25768 * @return {Function} 25769 */ 25770 var ALIGNMENT_SORTING = { 25771 left: sortLeftOrCenter, 25772 center: sortLeftOrCenter, 25773 right: function(element) { 25774 return element.x + element.width; 25775 }, 25776 top: sortTopOrMiddle, 25777 middle: sortTopOrMiddle, 25778 bottom: function(element) { 25779 return element.y + element.height; 25780 } 25781 }; 25782 25783 25784 function AlignElements$1(modeling) { 25785 this._modeling = modeling; 25786 } 25787 25788 AlignElements$1.$inject = [ 'modeling' ]; 25789 25790 25791 /** 25792 * Get the relevant "axis" and "dimension" related to the current type of alignment 25793 * 25794 * @param {string} type left|right|center|top|bottom|middle 25795 * 25796 * @return {Object} { axis, dimension } 25797 */ 25798 AlignElements$1.prototype._getOrientationDetails = function(type) { 25799 var vertical = [ 'top', 'bottom', 'middle' ], 25800 axis = 'x', 25801 dimension = 'width'; 25802 25803 if (vertical.indexOf(type) !== -1) { 25804 axis = 'y'; 25805 dimension = 'height'; 25806 } 25807 25808 return { 25809 axis: axis, 25810 dimension: dimension 25811 }; 25812 }; 25813 25814 AlignElements$1.prototype._isType = function(type, types) { 25815 return types.indexOf(type) !== -1; 25816 }; 25817 25818 /** 25819 * Get a point on the relevant axis where elements should align to 25820 * 25821 * @param {string} type left|right|center|top|bottom|middle 25822 * @param {Array} sortedElements 25823 * 25824 * @return {Object} 25825 */ 25826 AlignElements$1.prototype._alignmentPosition = function(type, sortedElements) { 25827 var orientation = this._getOrientationDetails(type), 25828 axis = orientation.axis, 25829 dimension = orientation.dimension, 25830 alignment = {}, 25831 centers = {}, 25832 hasSharedCenters = false, 25833 centeredElements, 25834 firstElement, 25835 lastElement; 25836 25837 function getMiddleOrTop(first, last) { 25838 return Math.round((first[axis] + last[axis] + last[dimension]) / 2); 25839 } 25840 25841 if (this._isType(type, [ 'left', 'top' ])) { 25842 alignment[type] = sortedElements[0][axis]; 25843 25844 } else if (this._isType(type, [ 'right', 'bottom' ])) { 25845 lastElement = last(sortedElements); 25846 25847 alignment[type] = lastElement[axis] + lastElement[dimension]; 25848 25849 } else if (this._isType(type, [ 'center', 'middle' ])) { 25850 25851 // check if there is a center shared by more than one shape 25852 // if not, just take the middle of the range 25853 forEach(sortedElements, function(element) { 25854 var center = element[axis] + Math.round(element[dimension] / 2); 25855 25856 if (centers[center]) { 25857 centers[center].elements.push(element); 25858 } else { 25859 centers[center] = { 25860 elements: [ element ], 25861 center: center 25862 }; 25863 } 25864 }); 25865 25866 centeredElements = sortBy(centers, function(center) { 25867 if (center.elements.length > 1) { 25868 hasSharedCenters = true; 25869 } 25870 25871 return center.elements.length; 25872 }); 25873 25874 if (hasSharedCenters) { 25875 alignment[type] = last(centeredElements).center; 25876 25877 return alignment; 25878 } 25879 25880 firstElement = sortedElements[0]; 25881 25882 sortedElements = sortBy(sortedElements, function(element) { 25883 return element[axis] + element[dimension]; 25884 }); 25885 25886 lastElement = last(sortedElements); 25887 25888 alignment[type] = getMiddleOrTop(firstElement, lastElement); 25889 } 25890 25891 return alignment; 25892 }; 25893 25894 /** 25895 * Executes the alignment of a selection of elements 25896 * 25897 * @param {Array} elements 25898 * @param {string} type left|right|center|top|bottom|middle 25899 */ 25900 AlignElements$1.prototype.trigger = function(elements, type) { 25901 var modeling = this._modeling; 25902 25903 var filteredElements = filter(elements, function(element) { 25904 return !(element.waypoints || element.host || element.labelTarget); 25905 }); 25906 25907 if (filteredElements.length < 2) { 25908 return; 25909 } 25910 25911 var sortFn = ALIGNMENT_SORTING[type]; 25912 25913 var sortedElements = sortBy(filteredElements, sortFn); 25914 25915 var alignment = this._alignmentPosition(type, sortedElements); 25916 25917 modeling.alignElements(sortedElements, alignment); 25918 }; 25919 25920 var AlignElementsModule = { 25921 __init__: [ 'alignElements' ], 25922 alignElements: [ 'type', AlignElements$1 ] 25923 }; 25924 25925 // padding to detect element placement 25926 var PLACEMENT_DETECTION_PAD = 10; 25927 25928 var DEFAULT_DISTANCE = 50; 25929 25930 var DEFAULT_MAX_DISTANCE = 250; 25931 25932 25933 /** 25934 * Get free position starting from given position. 25935 * 25936 * @param {djs.model.Shape} source 25937 * @param {djs.model.Shape} element 25938 * @param {Point} position 25939 * @param {Function} getNextPosition 25940 * 25941 * @return {Point} 25942 */ 25943 function findFreePosition(source, element, position, getNextPosition) { 25944 var connectedAtPosition; 25945 25946 while ((connectedAtPosition = getConnectedAtPosition(source, position, element))) { 25947 position = getNextPosition(element, position, connectedAtPosition); 25948 } 25949 25950 return position; 25951 } 25952 25953 /** 25954 * Returns function that returns next position. 25955 * 25956 * @param {Object} nextPositionDirection 25957 * @param {Object} [nextPositionDirection.x] 25958 * @param {Object} [nextPositionDirection.y] 25959 * 25960 * @returns {Function} 25961 */ 25962 function generateGetNextPosition(nextPositionDirection) { 25963 return function(element, previousPosition, connectedAtPosition) { 25964 var nextPosition = { 25965 x: previousPosition.x, 25966 y: previousPosition.y 25967 }; 25968 25969 [ 'x', 'y' ].forEach(function(axis) { 25970 25971 var nextPositionDirectionForAxis = nextPositionDirection[ axis ]; 25972 25973 if (!nextPositionDirectionForAxis) { 25974 return; 25975 } 25976 25977 var dimension = axis === 'x' ? 'width' : 'height'; 25978 25979 var margin = nextPositionDirectionForAxis.margin, 25980 minDistance = nextPositionDirectionForAxis.minDistance; 25981 25982 if (margin < 0) { 25983 nextPosition[ axis ] = Math.min( 25984 connectedAtPosition[ axis ] + margin - element[ dimension ] / 2, 25985 previousPosition[ axis ] - minDistance + margin 25986 ); 25987 } else { 25988 nextPosition[ axis ] = Math.max( 25989 connectedAtPosition[ axis ] + connectedAtPosition[ dimension ] + margin + element[ dimension ] / 2, 25990 previousPosition[ axis ] + minDistance + margin 25991 ); 25992 } 25993 }); 25994 25995 return nextPosition; 25996 }; 25997 } 25998 25999 /** 26000 * Return target at given position, if defined. 26001 * 26002 * This takes connected elements from host and attachers 26003 * into account, too. 26004 */ 26005 function getConnectedAtPosition(source, position, element) { 26006 26007 var bounds = { 26008 x: position.x - (element.width / 2), 26009 y: position.y - (element.height / 2), 26010 width: element.width, 26011 height: element.height 26012 }; 26013 26014 var closure = getAutoPlaceClosure(source); 26015 26016 return find(closure, function(target) { 26017 26018 if (target === element) { 26019 return false; 26020 } 26021 26022 var orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD); 26023 26024 return orientation === 'intersect'; 26025 }); 26026 } 26027 26028 /** 26029 * Compute optimal distance between source and target based on existing connections to and from source. 26030 * Assumes left-to-right and top-to-down modeling. 26031 * 26032 * @param {djs.model.Shape} source 26033 * @param {Object} [hints] 26034 * @param {number} [hints.defaultDistance] 26035 * @param {string} [hints.direction] 26036 * @param {Function} [hints.filter] 26037 * @param {Function} [hints.getWeight] 26038 * @param {number} [hints.maxDistance] 26039 * @param {string} [hints.reference] 26040 * 26041 * @return {number} 26042 */ 26043 function getConnectedDistance(source, hints) { 26044 if (!hints) { 26045 hints = {}; 26046 } 26047 26048 // targets > sources by default 26049 function getDefaultWeight(connection) { 26050 return connection.source === source ? 1 : -1; 26051 } 26052 26053 var defaultDistance = hints.defaultDistance || DEFAULT_DISTANCE, 26054 direction = hints.direction || 'e', 26055 filter = hints.filter, 26056 getWeight = hints.getWeight || getDefaultWeight, 26057 maxDistance = hints.maxDistance || DEFAULT_MAX_DISTANCE, 26058 reference = hints.reference || 'start'; 26059 26060 if (!filter) { 26061 filter = noneFilter; 26062 } 26063 26064 function getDistance(a, b) { 26065 if (direction === 'n') { 26066 if (reference === 'start') { 26067 return asTRBL(a).top - asTRBL(b).bottom; 26068 } else if (reference === 'center') { 26069 return asTRBL(a).top - getMid(b).y; 26070 } else { 26071 return asTRBL(a).top - asTRBL(b).top; 26072 } 26073 } else if (direction === 'w') { 26074 if (reference === 'start') { 26075 return asTRBL(a).left - asTRBL(b).right; 26076 } else if (reference === 'center') { 26077 return asTRBL(a).left - getMid(b).x; 26078 } else { 26079 return asTRBL(a).left - asTRBL(b).left; 26080 } 26081 } else if (direction === 's') { 26082 if (reference === 'start') { 26083 return asTRBL(b).top - asTRBL(a).bottom; 26084 } else if (reference === 'center') { 26085 return getMid(b).y - asTRBL(a).bottom; 26086 } else { 26087 return asTRBL(b).bottom - asTRBL(a).bottom; 26088 } 26089 } else { 26090 if (reference === 'start') { 26091 return asTRBL(b).left - asTRBL(a).right; 26092 } else if (reference === 'center') { 26093 return getMid(b).x - asTRBL(a).right; 26094 } else { 26095 return asTRBL(b).right - asTRBL(a).right; 26096 } 26097 } 26098 } 26099 26100 var sourcesDistances = source.incoming 26101 .filter(filter) 26102 .map(function(connection) { 26103 var weight = getWeight(connection); 26104 26105 var distance = weight < 0 26106 ? getDistance(connection.source, source) 26107 : getDistance(source, connection.source); 26108 26109 return { 26110 id: connection.source.id, 26111 distance: distance, 26112 weight: weight 26113 }; 26114 }); 26115 26116 var targetsDistances = source.outgoing 26117 .filter(filter) 26118 .map(function(connection) { 26119 var weight = getWeight(connection); 26120 26121 var distance = weight > 0 26122 ? getDistance(source, connection.target) 26123 : getDistance(connection.target, source); 26124 26125 return { 26126 id: connection.target.id, 26127 distance: distance, 26128 weight: weight 26129 }; 26130 }); 26131 26132 var distances = sourcesDistances.concat(targetsDistances).reduce(function(accumulator, currentValue) { 26133 accumulator[ currentValue.id + '__weight_' + currentValue.weight ] = currentValue; 26134 26135 return accumulator; 26136 }, {}); 26137 26138 var distancesGrouped = reduce(distances, function(accumulator, currentValue) { 26139 var distance = currentValue.distance, 26140 weight = currentValue.weight; 26141 26142 if (distance < 0 || distance > maxDistance) { 26143 return accumulator; 26144 } 26145 26146 if (!accumulator[ String(distance) ]) { 26147 accumulator[ String(distance) ] = 0; 26148 } 26149 26150 accumulator[ String(distance) ] += 1 * weight; 26151 26152 if (!accumulator.distance || accumulator[ accumulator.distance ] < accumulator[ String(distance) ]) { 26153 accumulator.distance = distance; 26154 } 26155 26156 return accumulator; 26157 }, {}); 26158 26159 return distancesGrouped.distance || defaultDistance; 26160 } 26161 26162 /** 26163 * Returns all connected elements around the given source. 26164 * 26165 * This includes: 26166 * 26167 * - connected elements 26168 * - host connected elements 26169 * - attachers connected elements 26170 * 26171 * @param {djs.model.Shape} source 26172 * 26173 * @return {Array<djs.model.Shape>} 26174 */ 26175 function getAutoPlaceClosure(source) { 26176 26177 var allConnected = getConnected(source); 26178 26179 if (source.host) { 26180 allConnected = allConnected.concat(getConnected(source.host)); 26181 } 26182 26183 if (source.attachers) { 26184 allConnected = allConnected.concat(source.attachers.reduce(function(shapes, attacher) { 26185 return shapes.concat(getConnected(attacher)); 26186 }, [])); 26187 } 26188 26189 return allConnected; 26190 } 26191 26192 function getConnected(element) { 26193 return getTargets(element).concat(getSources(element)); 26194 } 26195 26196 function getSources(shape) { 26197 return shape.incoming.map(function(connection) { 26198 return connection.source; 26199 }); 26200 } 26201 26202 function getTargets(shape) { 26203 return shape.outgoing.map(function(connection) { 26204 return connection.target; 26205 }); 26206 } 26207 26208 function noneFilter() { 26209 return true; 26210 } 26211 26212 var LOW_PRIORITY$i = 100; 26213 26214 26215 /** 26216 * A service that places elements connected to existing ones 26217 * to an appropriate position in an _automated_ fashion. 26218 * 26219 * @param {EventBus} eventBus 26220 * @param {Modeling} modeling 26221 */ 26222 function AutoPlace$1(eventBus, modeling, canvas) { 26223 26224 eventBus.on('autoPlace', LOW_PRIORITY$i, function(context) { 26225 var shape = context.shape, 26226 source = context.source; 26227 26228 return getNewShapePosition$1(source, shape); 26229 }); 26230 26231 eventBus.on('autoPlace.end', function(event) { 26232 canvas.scrollToElement(event.shape); 26233 }); 26234 26235 /** 26236 * Append shape to source at appropriate position. 26237 * 26238 * @param {djs.model.Shape} source 26239 * @param {djs.model.Shape} shape 26240 * 26241 * @return {djs.model.Shape} appended shape 26242 */ 26243 this.append = function(source, shape, hints) { 26244 26245 eventBus.fire('autoPlace.start', { 26246 source: source, 26247 shape: shape 26248 }); 26249 26250 // allow others to provide the position 26251 var position = eventBus.fire('autoPlace', { 26252 source: source, 26253 shape: shape 26254 }); 26255 26256 var newShape = modeling.appendShape(source, shape, position, source.parent, hints); 26257 26258 eventBus.fire('autoPlace.end', { 26259 source: source, 26260 shape: newShape 26261 }); 26262 26263 return newShape; 26264 }; 26265 26266 } 26267 26268 AutoPlace$1.$inject = [ 26269 'eventBus', 26270 'modeling', 26271 'canvas' 26272 ]; 26273 26274 // helpers ////////// 26275 26276 /** 26277 * Find the new position for the target element to 26278 * connect to source. 26279 * 26280 * @param {djs.model.Shape} source 26281 * @param {djs.model.Shape} element 26282 * @param {Object} [hints] 26283 * @param {Object} [hints.defaultDistance] 26284 * 26285 * @returns {Point} 26286 */ 26287 function getNewShapePosition$1(source, element, hints) { 26288 if (!hints) { 26289 hints = {}; 26290 } 26291 26292 var distance = hints.defaultDistance || DEFAULT_DISTANCE; 26293 26294 var sourceMid = getMid(source), 26295 sourceTrbl = asTRBL(source); 26296 26297 // simply put element right next to source 26298 return { 26299 x: sourceTrbl.right + distance + element.width / 2, 26300 y: sourceMid.y 26301 }; 26302 } 26303 26304 /** 26305 * Select element after auto placement. 26306 * 26307 * @param {EventBus} eventBus 26308 * @param {Selection} selection 26309 */ 26310 function AutoPlaceSelectionBehavior(eventBus, selection) { 26311 26312 eventBus.on('autoPlace.end', 500, function(e) { 26313 selection.select(e.shape); 26314 }); 26315 26316 } 26317 26318 AutoPlaceSelectionBehavior.$inject = [ 26319 'eventBus', 26320 'selection' 26321 ]; 26322 26323 var AutoPlaceModule$1 = { 26324 __init__: [ 'autoPlaceSelectionBehavior' ], 26325 autoPlace: [ 'type', AutoPlace$1 ], 26326 autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ] 26327 }; 26328 26329 /** 26330 * Return true if element has any of the given types. 26331 * 26332 * @param {djs.model.Base} element 26333 * @param {Array<string>} types 26334 * 26335 * @return {boolean} 26336 */ 26337 function isAny(element, types) { 26338 return some(types, function(t) { 26339 return is$1(element, t); 26340 }); 26341 } 26342 26343 26344 /** 26345 * Return the parent of the element with any of the given types. 26346 * 26347 * @param {djs.model.Base} element 26348 * @param {string|Array<string>} anyType 26349 * 26350 * @return {djs.model.Base} 26351 */ 26352 function getParent(element, anyType) { 26353 26354 if (typeof anyType === 'string') { 26355 anyType = [ anyType ]; 26356 } 26357 26358 while ((element = element.parent)) { 26359 if (isAny(element, anyType)) { 26360 return element; 26361 } 26362 } 26363 26364 return null; 26365 } 26366 26367 /** 26368 * Find the new position for the target element to 26369 * connect to source. 26370 * 26371 * @param {djs.model.Shape} source 26372 * @param {djs.model.Shape} element 26373 * 26374 * @return {Point} 26375 */ 26376 function getNewShapePosition(source, element) { 26377 26378 if (is$1(element, 'bpmn:TextAnnotation')) { 26379 return getTextAnnotationPosition(source, element); 26380 } 26381 26382 if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) { 26383 return getDataElementPosition(source, element); 26384 } 26385 26386 if (is$1(element, 'bpmn:FlowNode')) { 26387 return getFlowNodePosition(source, element); 26388 } 26389 } 26390 26391 /** 26392 * Always try to place element right of source; 26393 * compute actual distance from previous nodes in flow. 26394 */ 26395 function getFlowNodePosition(source, element) { 26396 26397 var sourceTrbl = asTRBL(source); 26398 var sourceMid = getMid(source); 26399 26400 var horizontalDistance = getConnectedDistance(source, { 26401 filter: function(connection) { 26402 return is$1(connection, 'bpmn:SequenceFlow'); 26403 } 26404 }); 26405 26406 var margin = 30, 26407 minDistance = 80, 26408 orientation = 'left'; 26409 26410 if (is$1(source, 'bpmn:BoundaryEvent')) { 26411 orientation = getOrientation(source, source.host, -25); 26412 26413 if (orientation.indexOf('top') !== -1) { 26414 margin *= -1; 26415 } 26416 } 26417 26418 var position = { 26419 x: sourceTrbl.right + horizontalDistance + element.width / 2, 26420 y: sourceMid.y + getVerticalDistance(orientation, minDistance) 26421 }; 26422 26423 var nextPositionDirection = { 26424 y: { 26425 margin: margin, 26426 minDistance: minDistance 26427 } 26428 }; 26429 26430 return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection)); 26431 } 26432 26433 26434 function getVerticalDistance(orientation, minDistance) { 26435 if (orientation.indexOf('top') != -1) { 26436 return -1 * minDistance; 26437 } else if (orientation.indexOf('bottom') != -1) { 26438 return minDistance; 26439 } else { 26440 return 0; 26441 } 26442 } 26443 26444 26445 /** 26446 * Always try to place text annotations top right of source. 26447 */ 26448 function getTextAnnotationPosition(source, element) { 26449 26450 var sourceTrbl = asTRBL(source); 26451 26452 var position = { 26453 x: sourceTrbl.right + element.width / 2, 26454 y: sourceTrbl.top - 50 - element.height / 2 26455 }; 26456 26457 var nextPositionDirection = { 26458 y: { 26459 margin: -30, 26460 minDistance: 20 26461 } 26462 }; 26463 26464 return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection)); 26465 } 26466 26467 26468 /** 26469 * Always put element bottom right of source. 26470 */ 26471 function getDataElementPosition(source, element) { 26472 26473 var sourceTrbl = asTRBL(source); 26474 26475 var position = { 26476 x: sourceTrbl.right - 10 + element.width / 2, 26477 y: sourceTrbl.bottom + 40 + element.width / 2 26478 }; 26479 26480 var nextPositionDirection = { 26481 x: { 26482 margin: 30, 26483 minDistance: 30 26484 } 26485 }; 26486 26487 return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection)); 26488 } 26489 26490 /** 26491 * BPMN auto-place behavior. 26492 * 26493 * @param {EventBus} eventBus 26494 */ 26495 function AutoPlace(eventBus) { 26496 eventBus.on('autoPlace', function(context) { 26497 var shape = context.shape, 26498 source = context.source; 26499 26500 return getNewShapePosition(source, shape); 26501 }); 26502 } 26503 26504 AutoPlace.$inject = [ 'eventBus' ]; 26505 26506 var AutoPlaceModule = { 26507 __depends__: [ AutoPlaceModule$1 ], 26508 __init__: [ 'bpmnAutoPlace' ], 26509 bpmnAutoPlace: [ 'type', AutoPlace ] 26510 }; 26511 26512 var DEFAULT_PRIORITY$3 = 1000; 26513 26514 /** 26515 * A utility that can be used to plug-in into the command execution for 26516 * extension and/or validation. 26517 * 26518 * @param {EventBus} eventBus 26519 * 26520 * @example 26521 * 26522 * import inherits from 'inherits'; 26523 * 26524 * import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; 26525 * 26526 * function CommandLogger(eventBus) { 26527 * CommandInterceptor.call(this, eventBus); 26528 * 26529 * this.preExecute(function(event) { 26530 * console.log('command pre-execute', event); 26531 * }); 26532 * } 26533 * 26534 * inherits(CommandLogger, CommandInterceptor); 26535 * 26536 */ 26537 function CommandInterceptor(eventBus) { 26538 this._eventBus = eventBus; 26539 } 26540 26541 CommandInterceptor.$inject = [ 'eventBus' ]; 26542 26543 function unwrapEvent(fn, that) { 26544 return function(event) { 26545 return fn.call(that || null, event.context, event.command, event); 26546 }; 26547 } 26548 26549 /** 26550 * Register an interceptor for a command execution 26551 * 26552 * @param {string|Array<string>} [events] list of commands to register on 26553 * @param {string} [hook] command hook, i.e. preExecute, executed to listen on 26554 * @param {number} [priority] the priority on which to hook into the execution 26555 * @param {Function} handlerFn interceptor to be invoked with (event) 26556 * @param {boolean} unwrap if true, unwrap the event and pass (context, command, event) to the 26557 * listener instead 26558 * @param {Object} [that] Pass context (`this`) to the handler function 26559 */ 26560 CommandInterceptor.prototype.on = function(events, hook, priority, handlerFn, unwrap, that) { 26561 26562 if (isFunction(hook) || isNumber(hook)) { 26563 that = unwrap; 26564 unwrap = handlerFn; 26565 handlerFn = priority; 26566 priority = hook; 26567 hook = null; 26568 } 26569 26570 if (isFunction(priority)) { 26571 that = unwrap; 26572 unwrap = handlerFn; 26573 handlerFn = priority; 26574 priority = DEFAULT_PRIORITY$3; 26575 } 26576 26577 if (isObject(unwrap)) { 26578 that = unwrap; 26579 unwrap = false; 26580 } 26581 26582 if (!isFunction(handlerFn)) { 26583 throw new Error('handlerFn must be a function'); 26584 } 26585 26586 if (!isArray$2(events)) { 26587 events = [ events ]; 26588 } 26589 26590 var eventBus = this._eventBus; 26591 26592 forEach(events, function(event) { 26593 26594 // concat commandStack(.event)?(.hook)? 26595 var fullEvent = [ 'commandStack', event, hook ].filter(function(e) { return e; }).join('.'); 26596 26597 eventBus.on(fullEvent, priority, unwrap ? unwrapEvent(handlerFn, that) : handlerFn, that); 26598 }); 26599 }; 26600 26601 26602 var hooks = [ 26603 'canExecute', 26604 'preExecute', 26605 'preExecuted', 26606 'execute', 26607 'executed', 26608 'postExecute', 26609 'postExecuted', 26610 'revert', 26611 'reverted' 26612 ]; 26613 26614 /* 26615 * Install hook shortcuts 26616 * 26617 * This will generate the CommandInterceptor#(preExecute|...|reverted) methods 26618 * which will in term forward to CommandInterceptor#on. 26619 */ 26620 forEach(hooks, function(hook) { 26621 26622 /** 26623 * {canExecute|preExecute|preExecuted|execute|executed|postExecute|postExecuted|revert|reverted} 26624 * 26625 * A named hook for plugging into the command execution 26626 * 26627 * @param {string|Array<string>} [events] list of commands to register on 26628 * @param {number} [priority] the priority on which to hook into the execution 26629 * @param {Function} handlerFn interceptor to be invoked with (event) 26630 * @param {boolean} [unwrap=false] if true, unwrap the event and pass (context, command, event) to the 26631 * listener instead 26632 * @param {Object} [that] Pass context (`this`) to the handler function 26633 */ 26634 CommandInterceptor.prototype[hook] = function(events, priority, handlerFn, unwrap, that) { 26635 26636 if (isFunction(events) || isNumber(events)) { 26637 that = unwrap; 26638 unwrap = handlerFn; 26639 handlerFn = priority; 26640 priority = events; 26641 events = null; 26642 } 26643 26644 this.on(events, hook, priority, handlerFn, unwrap, that); 26645 }; 26646 }); 26647 26648 /** 26649 * An auto resize component that takes care of expanding a parent element 26650 * if child elements are created or moved close the parents edge. 26651 * 26652 * @param {EventBus} eventBus 26653 * @param {ElementRegistry} elementRegistry 26654 * @param {Modeling} modeling 26655 * @param {Rules} rules 26656 */ 26657 function AutoResize(eventBus, elementRegistry, modeling, rules) { 26658 26659 CommandInterceptor.call(this, eventBus); 26660 26661 this._elementRegistry = elementRegistry; 26662 this._modeling = modeling; 26663 this._rules = rules; 26664 26665 var self = this; 26666 26667 this.postExecuted([ 'shape.create' ], function(event) { 26668 var context = event.context, 26669 hints = context.hints || {}, 26670 shape = context.shape, 26671 parent = context.parent || context.newParent; 26672 26673 if (hints.autoResize === false) { 26674 return; 26675 } 26676 26677 self._expand([ shape ], parent); 26678 }); 26679 26680 this.postExecuted([ 'elements.move' ], function(event) { 26681 var context = event.context, 26682 elements = flatten(values(context.closure.topLevel)), 26683 hints = context.hints; 26684 26685 var autoResize = hints ? hints.autoResize : true; 26686 26687 if (autoResize === false) { 26688 return; 26689 } 26690 26691 var expandings = groupBy(elements, function(element) { 26692 return element.parent.id; 26693 }); 26694 26695 forEach(expandings, function(elements, parentId) { 26696 26697 // optionally filter elements to be considered when resizing 26698 if (isArray$2(autoResize)) { 26699 elements = elements.filter(function(element) { 26700 return find(autoResize, matchPattern({ id: element.id })); 26701 }); 26702 } 26703 26704 self._expand(elements, parentId); 26705 }); 26706 }); 26707 26708 this.postExecuted([ 'shape.toggleCollapse' ], function(event) { 26709 var context = event.context, 26710 hints = context.hints, 26711 shape = context.shape; 26712 26713 if (hints && hints.autoResize === false) { 26714 return; 26715 } 26716 26717 if (shape.collapsed) { 26718 return; 26719 } 26720 26721 self._expand(shape.children || [], shape); 26722 }); 26723 26724 this.postExecuted([ 'shape.resize' ], function(event) { 26725 var context = event.context, 26726 hints = context.hints, 26727 shape = context.shape, 26728 parent = shape.parent; 26729 26730 if (hints && hints.autoResize === false) { 26731 return; 26732 } 26733 26734 if (parent) { 26735 self._expand([ shape ], parent); 26736 } 26737 }); 26738 26739 } 26740 26741 AutoResize.$inject = [ 26742 'eventBus', 26743 'elementRegistry', 26744 'modeling', 26745 'rules' 26746 ]; 26747 26748 inherits$1(AutoResize, CommandInterceptor); 26749 26750 26751 /** 26752 * Calculate the new bounds of the target shape, given 26753 * a number of elements have been moved or added into the parent. 26754 * 26755 * This method considers the current size, the added elements as well as 26756 * the provided padding for the new bounds. 26757 * 26758 * @param {Array<djs.model.Shape>} elements 26759 * @param {djs.model.Shape} target 26760 */ 26761 AutoResize.prototype._getOptimalBounds = function(elements, target) { 26762 26763 var offset = this.getOffset(target), 26764 padding = this.getPadding(target); 26765 26766 var elementsTrbl = asTRBL(getBBox(elements)), 26767 targetTrbl = asTRBL(target); 26768 26769 var newTrbl = {}; 26770 26771 if (elementsTrbl.top - targetTrbl.top < padding.top) { 26772 newTrbl.top = elementsTrbl.top - offset.top; 26773 } 26774 26775 if (elementsTrbl.left - targetTrbl.left < padding.left) { 26776 newTrbl.left = elementsTrbl.left - offset.left; 26777 } 26778 26779 if (targetTrbl.right - elementsTrbl.right < padding.right) { 26780 newTrbl.right = elementsTrbl.right + offset.right; 26781 } 26782 26783 if (targetTrbl.bottom - elementsTrbl.bottom < padding.bottom) { 26784 newTrbl.bottom = elementsTrbl.bottom + offset.bottom; 26785 } 26786 26787 return asBounds(assign({}, targetTrbl, newTrbl)); 26788 }; 26789 26790 26791 /** 26792 * Expand the target shape respecting rules, offset and padding 26793 * 26794 * @param {Array<djs.model.Shape>} elements 26795 * @param {djs.model.Shape|string} target|targetId 26796 */ 26797 AutoResize.prototype._expand = function(elements, target) { 26798 26799 if (typeof target === 'string') { 26800 target = this._elementRegistry.get(target); 26801 } 26802 26803 var allowed = this._rules.allowed('element.autoResize', { 26804 elements: elements, 26805 target: target 26806 }); 26807 26808 if (!allowed) { 26809 return; 26810 } 26811 26812 // calculate the new bounds 26813 var newBounds = this._getOptimalBounds(elements, target); 26814 26815 if (!boundsChanged$1(newBounds, target)) { 26816 return; 26817 } 26818 26819 var resizeDirections = getResizeDirections(pick(target, [ 'x', 'y', 'width', 'height' ]), newBounds); 26820 26821 // resize the parent shape 26822 this.resize(target, newBounds, { 26823 autoResize: resizeDirections 26824 }); 26825 26826 var parent = target.parent; 26827 26828 // recursively expand parent elements 26829 if (parent) { 26830 this._expand([ target ], parent); 26831 } 26832 }; 26833 26834 26835 /** 26836 * Get the amount to expand the given shape in each direction. 26837 * 26838 * @param {djs.model.Shape} shape 26839 * 26840 * @return {TRBL} 26841 */ 26842 AutoResize.prototype.getOffset = function(shape) { 26843 return { top: 60, bottom: 60, left: 100, right: 100 }; 26844 }; 26845 26846 26847 /** 26848 * Get the activation threshold for each side for which 26849 * resize triggers. 26850 * 26851 * @param {djs.model.Shape} shape 26852 * 26853 * @return {TRBL} 26854 */ 26855 AutoResize.prototype.getPadding = function(shape) { 26856 return { top: 2, bottom: 2, left: 15, right: 15 }; 26857 }; 26858 26859 26860 /** 26861 * Perform the actual resize operation. 26862 * 26863 * @param {djs.model.Shape} shape 26864 * @param {Bounds} newBounds 26865 * @param {Object} [hints] 26866 * @param {string} [hints.autoResize] 26867 */ 26868 AutoResize.prototype.resize = function(shape, newBounds, hints) { 26869 this._modeling.resizeShape(shape, newBounds, null, hints); 26870 }; 26871 26872 26873 function boundsChanged$1(newBounds, oldBounds) { 26874 return ( 26875 newBounds.x !== oldBounds.x || 26876 newBounds.y !== oldBounds.y || 26877 newBounds.width !== oldBounds.width || 26878 newBounds.height !== oldBounds.height 26879 ); 26880 } 26881 26882 /** 26883 * Get directions of resize as {n|w|s|e} e.g. "nw". 26884 * 26885 * @param {Bounds} oldBounds 26886 * @param {Bounds} newBounds 26887 * 26888 * @returns {string} Resize directions as {n|w|s|e}. 26889 */ 26890 function getResizeDirections(oldBounds, newBounds) { 26891 var directions = ''; 26892 26893 oldBounds = asTRBL(oldBounds); 26894 newBounds = asTRBL(newBounds); 26895 26896 if (oldBounds.top > newBounds.top) { 26897 directions = directions.concat('n'); 26898 } 26899 26900 if (oldBounds.right < newBounds.right) { 26901 directions = directions.concat('w'); 26902 } 26903 26904 if (oldBounds.bottom < newBounds.bottom) { 26905 directions = directions.concat('s'); 26906 } 26907 26908 if (oldBounds.left > newBounds.left) { 26909 directions = directions.concat('e'); 26910 } 26911 26912 return directions; 26913 } 26914 26915 /** 26916 * Sub class of the AutoResize module which implements a BPMN 26917 * specific resize function. 26918 */ 26919 function BpmnAutoResize(injector) { 26920 26921 injector.invoke(AutoResize, this); 26922 } 26923 26924 BpmnAutoResize.$inject = [ 26925 'injector' 26926 ]; 26927 26928 inherits$1(BpmnAutoResize, AutoResize); 26929 26930 26931 /** 26932 * Resize shapes and lanes. 26933 * 26934 * @param {djs.model.Shape} target 26935 * @param {Bounds} newBounds 26936 * @param {Object} hints 26937 */ 26938 BpmnAutoResize.prototype.resize = function(target, newBounds, hints) { 26939 26940 if (is$1(target, 'bpmn:Participant')) { 26941 this._modeling.resizeLane(target, newBounds, null, hints); 26942 } else { 26943 this._modeling.resizeShape(target, newBounds, null, hints); 26944 } 26945 }; 26946 26947 /** 26948 * A basic provider that may be extended to implement modeling rules. 26949 * 26950 * Extensions should implement the init method to actually add their custom 26951 * modeling checks. Checks may be added via the #addRule(action, fn) method. 26952 * 26953 * @param {EventBus} eventBus 26954 */ 26955 function RuleProvider(eventBus) { 26956 CommandInterceptor.call(this, eventBus); 26957 26958 this.init(); 26959 } 26960 26961 RuleProvider.$inject = [ 'eventBus' ]; 26962 26963 inherits$1(RuleProvider, CommandInterceptor); 26964 26965 26966 /** 26967 * Adds a modeling rule for the given action, implemented through 26968 * a callback function. 26969 * 26970 * The function will receive the modeling specific action context 26971 * to perform its check. It must return `false` to disallow the 26972 * action from happening or `true` to allow the action. 26973 * 26974 * A rule provider may pass over the evaluation to lower priority 26975 * rules by returning return nothing (or <code>undefined</code>). 26976 * 26977 * @example 26978 * 26979 * ResizableRules.prototype.init = function() { 26980 * 26981 * \/** 26982 * * Return `true`, `false` or nothing to denote 26983 * * _allowed_, _not allowed_ and _continue evaluating_. 26984 * *\/ 26985 * this.addRule('shape.resize', function(context) { 26986 * 26987 * var shape = context.shape; 26988 * 26989 * if (!context.newBounds) { 26990 * // check general resizability 26991 * if (!shape.resizable) { 26992 * return false; 26993 * } 26994 * 26995 * // not returning anything (read: undefined) 26996 * // will continue the evaluation of other rules 26997 * // (with lower priority) 26998 * return; 26999 * } else { 27000 * // element must have minimum size of 10*10 points 27001 * return context.newBounds.width > 10 && context.newBounds.height > 10; 27002 * } 27003 * }); 27004 * }; 27005 * 27006 * @param {string|Array<string>} actions the identifier for the modeling action to check 27007 * @param {number} [priority] the priority at which this rule is being applied 27008 * @param {Function} fn the callback function that performs the actual check 27009 */ 27010 RuleProvider.prototype.addRule = function(actions, priority, fn) { 27011 27012 var self = this; 27013 27014 if (typeof actions === 'string') { 27015 actions = [ actions ]; 27016 } 27017 27018 actions.forEach(function(action) { 27019 27020 self.canExecute(action, priority, function(context, action, event) { 27021 return fn(context); 27022 }, true); 27023 }); 27024 }; 27025 27026 /** 27027 * Implement this method to add new rules during provider initialization. 27028 */ 27029 RuleProvider.prototype.init = function() {}; 27030 27031 /** 27032 * This is a base rule provider for the element.autoResize rule. 27033 */ 27034 function AutoResizeProvider(eventBus) { 27035 27036 RuleProvider.call(this, eventBus); 27037 27038 var self = this; 27039 27040 this.addRule('element.autoResize', function(context) { 27041 return self.canResize(context.elements, context.target); 27042 }); 27043 } 27044 27045 AutoResizeProvider.$inject = [ 'eventBus' ]; 27046 27047 inherits$1(AutoResizeProvider, RuleProvider); 27048 27049 /** 27050 * Needs to be implemented by sub classes to allow actual auto resize 27051 * 27052 * @param {Array<djs.model.Shape>} elements 27053 * @param {djs.model.Shape} target 27054 * 27055 * @return {boolean} 27056 */ 27057 AutoResizeProvider.prototype.canResize = function(elements, target) { 27058 return false; 27059 }; 27060 27061 /** 27062 * This module is a provider for automatically resizing parent BPMN elements 27063 */ 27064 function BpmnAutoResizeProvider(eventBus, modeling) { 27065 AutoResizeProvider.call(this, eventBus); 27066 27067 this._modeling = modeling; 27068 } 27069 27070 inherits$1(BpmnAutoResizeProvider, AutoResizeProvider); 27071 27072 BpmnAutoResizeProvider.$inject = [ 27073 'eventBus', 27074 'modeling' 27075 ]; 27076 27077 27078 /** 27079 * Check if the given target can be expanded 27080 * 27081 * @param {djs.model.Shape} target 27082 * 27083 * @return {boolean} 27084 */ 27085 BpmnAutoResizeProvider.prototype.canResize = function(elements, target) { 27086 27087 if (!is$1(target, 'bpmn:Participant') && !is$1(target, 'bpmn:Lane') && !(is$1(target, 'bpmn:SubProcess'))) { 27088 return false; 27089 } 27090 27091 var canResize = true; 27092 27093 forEach(elements, function(element) { 27094 27095 if (is$1(element, 'bpmn:Lane') || element.labelTarget) { 27096 canResize = false; 27097 return; 27098 } 27099 }); 27100 27101 return canResize; 27102 }; 27103 27104 var AutoResizeModule = { 27105 __init__: [ 27106 'bpmnAutoResize', 27107 'bpmnAutoResizeProvider' 27108 ], 27109 bpmnAutoResize: [ 'type', BpmnAutoResize ], 27110 bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ] 27111 }; 27112 27113 var HIGH_PRIORITY$j = 1500; 27114 27115 27116 /** 27117 * Browsers may swallow certain events (hover, out ...) if users are to 27118 * fast with the mouse. 27119 * 27120 * @see http://stackoverflow.com/questions/7448468/why-cant-i-reliably-capture-a-mouseout-event 27121 * 27122 * The fix implemented in this component ensure that we 27123 * 27124 * 1) have a hover state after a successful drag.move event 27125 * 2) have an out event when dragging leaves an element 27126 * 27127 * @param {ElementRegistry} elementRegistry 27128 * @param {EventBus} eventBus 27129 * @param {Injector} injector 27130 */ 27131 function HoverFix(elementRegistry, eventBus, injector) { 27132 27133 var self = this; 27134 27135 var dragging = injector.get('dragging', false); 27136 27137 /** 27138 * Make sure we are god damn hovering! 27139 * 27140 * @param {Event} dragging event 27141 */ 27142 function ensureHover(event) { 27143 27144 if (event.hover) { 27145 return; 27146 } 27147 27148 var originalEvent = event.originalEvent; 27149 27150 var gfx = self._findTargetGfx(originalEvent); 27151 27152 var element = gfx && elementRegistry.get(gfx); 27153 27154 if (gfx && element) { 27155 27156 // 1) cancel current mousemove 27157 event.stopPropagation(); 27158 27159 // 2) emit fake hover for new target 27160 dragging.hover({ element: element, gfx: gfx }); 27161 27162 // 3) re-trigger move event 27163 dragging.move(originalEvent); 27164 } 27165 } 27166 27167 27168 if (dragging) { 27169 27170 /** 27171 * We wait for a specific sequence of events before 27172 * emitting a fake drag.hover event. 27173 * 27174 * Event Sequence: 27175 * 27176 * drag.start 27177 * drag.move >> ensure we are hovering 27178 */ 27179 eventBus.on('drag.start', function(event) { 27180 27181 eventBus.once('drag.move', HIGH_PRIORITY$j, function(event) { 27182 27183 ensureHover(event); 27184 27185 }); 27186 27187 }); 27188 } 27189 27190 27191 /** 27192 * We make sure that element.out is always fired, even if the 27193 * browser swallows an element.out event. 27194 * 27195 * Event sequence: 27196 * 27197 * element.hover 27198 * (element.out >> sometimes swallowed) 27199 * element.hover >> ensure we fired element.out 27200 */ 27201 (function() { 27202 var hoverGfx; 27203 var hover; 27204 27205 eventBus.on('element.hover', function(event) { 27206 27207 // (1) remember current hover element 27208 hoverGfx = event.gfx; 27209 hover = event.element; 27210 }); 27211 27212 eventBus.on('element.hover', HIGH_PRIORITY$j, function(event) { 27213 27214 // (3) am I on an element still? 27215 if (hover) { 27216 27217 // (4) that is a problem, gotta "simulate the out" 27218 eventBus.fire('element.out', { 27219 element: hover, 27220 gfx: hoverGfx 27221 }); 27222 } 27223 27224 }); 27225 27226 eventBus.on('element.out', function() { 27227 27228 // (2) unset hover state if we correctly outed us *GG* 27229 hoverGfx = null; 27230 hover = null; 27231 }); 27232 27233 })(); 27234 27235 this._findTargetGfx = function(event) { 27236 var position, 27237 target; 27238 27239 if (!(event instanceof MouseEvent)) { 27240 return; 27241 } 27242 27243 position = toPoint(event); 27244 27245 // damn expensive operation, ouch! 27246 target = document.elementFromPoint(position.x, position.y); 27247 27248 return getGfx(target); 27249 }; 27250 27251 } 27252 27253 HoverFix.$inject = [ 27254 'elementRegistry', 27255 'eventBus', 27256 'injector' 27257 ]; 27258 27259 27260 // helpers ///////////////////// 27261 27262 function getGfx(target) { 27263 return closest(target, 'svg, .djs-element', true); 27264 } 27265 27266 var HoverFixModule = { 27267 __init__: [ 27268 'hoverFix' 27269 ], 27270 hoverFix: [ 'type', HoverFix ], 27271 }; 27272 27273 var round$a = Math.round; 27274 27275 var DRAG_ACTIVE_CLS = 'djs-drag-active'; 27276 27277 27278 function preventDefault$1(event) { 27279 event.preventDefault(); 27280 } 27281 27282 function isTouchEvent(event) { 27283 27284 // check for TouchEvent being available first 27285 // (i.e. not available on desktop Firefox) 27286 return typeof TouchEvent !== 'undefined' && event instanceof TouchEvent; 27287 } 27288 27289 function getLength(point) { 27290 return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2)); 27291 } 27292 27293 /** 27294 * A helper that fires canvas localized drag events and realizes 27295 * the general "drag-and-drop" look and feel. 27296 * 27297 * Calling {@link Dragging#activate} activates dragging on a canvas. 27298 * 27299 * It provides the following: 27300 * 27301 * * emits life cycle events, namespaced with a prefix assigned 27302 * during dragging activation 27303 * * sets and restores the cursor 27304 * * sets and restores the selection if elements still exist 27305 * * ensures there can be only one drag operation active at a time 27306 * 27307 * Dragging may be canceled manually by calling {@link Dragging#cancel} 27308 * or by pressing ESC. 27309 * 27310 * 27311 * ## Life-cycle events 27312 * 27313 * Dragging can be in three different states, off, initialized 27314 * and active. 27315 * 27316 * (1) off: no dragging operation is in progress 27317 * (2) initialized: a new drag operation got initialized but not yet 27318 * started (i.e. because of no initial move) 27319 * (3) started: dragging is in progress 27320 * 27321 * Eventually dragging will be off again after a drag operation has 27322 * been ended or canceled via user click or ESC key press. 27323 * 27324 * To indicate transitions between these states dragging emits generic 27325 * life-cycle events with the `drag.` prefix _and_ events namespaced 27326 * to a prefix choosen by a user during drag initialization. 27327 * 27328 * The following events are emitted (appropriately prefixed) via 27329 * the {@link EventBus}. 27330 * 27331 * * `init` 27332 * * `start` 27333 * * `move` 27334 * * `end` 27335 * * `ended` (dragging already in off state) 27336 * * `cancel` (only if previously started) 27337 * * `canceled` (dragging already in off state, only if previously started) 27338 * * `cleanup` 27339 * 27340 * 27341 * @example 27342 * 27343 * function MyDragComponent(eventBus, dragging) { 27344 * 27345 * eventBus.on('mydrag.start', function(event) { 27346 * console.log('yes, we start dragging'); 27347 * }); 27348 * 27349 * eventBus.on('mydrag.move', function(event) { 27350 * console.log('canvas local coordinates', event.x, event.y, event.dx, event.dy); 27351 * 27352 * // local drag data is passed with the event 27353 * event.context.foo; // "BAR" 27354 * 27355 * // the original mouse event, too 27356 * event.originalEvent; // MouseEvent(...) 27357 * }); 27358 * 27359 * eventBus.on('element.click', function(event) { 27360 * dragging.init(event, 'mydrag', { 27361 * cursor: 'grabbing', 27362 * data: { 27363 * context: { 27364 * foo: "BAR" 27365 * } 27366 * } 27367 * }); 27368 * }); 27369 * } 27370 */ 27371 function Dragging(eventBus, canvas, selection, elementRegistry) { 27372 27373 var defaultOptions = { 27374 threshold: 5, 27375 trapClick: true 27376 }; 27377 27378 // the currently active drag operation 27379 // dragging is active as soon as this context exists. 27380 // 27381 // it is visually _active_ only when a context.active flag is set to true. 27382 var context; 27383 27384 /* convert a global event into local coordinates */ 27385 function toLocalPoint(globalPosition) { 27386 27387 var viewbox = canvas.viewbox(); 27388 27389 var clientRect = canvas._container.getBoundingClientRect(); 27390 27391 return { 27392 x: viewbox.x + (globalPosition.x - clientRect.left) / viewbox.scale, 27393 y: viewbox.y + (globalPosition.y - clientRect.top) / viewbox.scale 27394 }; 27395 } 27396 27397 // helpers 27398 27399 function fire(type, dragContext) { 27400 dragContext = dragContext || context; 27401 27402 var event = eventBus.createEvent( 27403 assign( 27404 {}, 27405 dragContext.payload, 27406 dragContext.data, 27407 { isTouch: dragContext.isTouch } 27408 ) 27409 ); 27410 27411 // default integration 27412 if (eventBus.fire('drag.' + type, event) === false) { 27413 return false; 27414 } 27415 27416 return eventBus.fire(dragContext.prefix + '.' + type, event); 27417 } 27418 27419 function restoreSelection(previousSelection) { 27420 var existingSelection = previousSelection.filter(function(element) { 27421 return elementRegistry.get(element.id); 27422 }); 27423 27424 existingSelection.length && selection.select(existingSelection); 27425 } 27426 27427 // event listeners 27428 27429 function move(event, activate) { 27430 var payload = context.payload, 27431 displacement = context.displacement; 27432 27433 var globalStart = context.globalStart, 27434 globalCurrent = toPoint(event), 27435 globalDelta = delta(globalCurrent, globalStart); 27436 27437 var localStart = context.localStart, 27438 localCurrent = toLocalPoint(globalCurrent), 27439 localDelta = delta(localCurrent, localStart); 27440 27441 27442 // activate context explicitly or once threshold is reached 27443 if (!context.active && (activate || getLength(globalDelta) > context.threshold)) { 27444 27445 // fire start event with original 27446 // starting coordinates 27447 27448 assign(payload, { 27449 x: round$a(localStart.x + displacement.x), 27450 y: round$a(localStart.y + displacement.y), 27451 dx: 0, 27452 dy: 0 27453 }, { originalEvent: event }); 27454 27455 if (false === fire('start')) { 27456 return cancel(); 27457 } 27458 27459 context.active = true; 27460 27461 // unset selection and remember old selection 27462 // the previous (old) selection will always passed 27463 // with the event via the event.previousSelection property 27464 if (!context.keepSelection) { 27465 payload.previousSelection = selection.get(); 27466 selection.select(null); 27467 } 27468 27469 // allow custom cursor 27470 if (context.cursor) { 27471 set(context.cursor); 27472 } 27473 27474 // indicate dragging via marker on root element 27475 canvas.addMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS); 27476 } 27477 27478 stopPropagation$1(event); 27479 27480 if (context.active) { 27481 27482 // update payload with actual coordinates 27483 assign(payload, { 27484 x: round$a(localCurrent.x + displacement.x), 27485 y: round$a(localCurrent.y + displacement.y), 27486 dx: round$a(localDelta.x), 27487 dy: round$a(localDelta.y) 27488 }, { originalEvent: event }); 27489 27490 // emit move event 27491 fire('move'); 27492 } 27493 } 27494 27495 function end(event) { 27496 var previousContext, 27497 returnValue = true; 27498 27499 if (context.active) { 27500 27501 if (event) { 27502 context.payload.originalEvent = event; 27503 27504 // suppress original event (click, ...) 27505 // because we just ended a drag operation 27506 stopPropagation$1(event); 27507 } 27508 27509 // implementations may stop restoring the 27510 // original state (selections, ...) by preventing the 27511 // end events default action 27512 returnValue = fire('end'); 27513 } 27514 27515 if (returnValue === false) { 27516 fire('rejected'); 27517 } 27518 27519 previousContext = cleanup(returnValue !== true); 27520 27521 // last event to be fired when all drag operations are done 27522 // at this point in time no drag operation is in progress anymore 27523 fire('ended', previousContext); 27524 } 27525 27526 27527 // cancel active drag operation if the user presses 27528 // the ESC key on the keyboard 27529 27530 function checkCancel(event) { 27531 27532 if (event.which === 27) { 27533 preventDefault$1(event); 27534 27535 cancel(); 27536 } 27537 } 27538 27539 27540 // prevent ghost click that might occur after a finished 27541 // drag and drop session 27542 27543 function trapClickAndEnd(event) { 27544 27545 var untrap; 27546 27547 // trap the click in case we are part of an active 27548 // drag operation. This will effectively prevent 27549 // the ghost click that cannot be canceled otherwise. 27550 if (context.active) { 27551 27552 untrap = install(eventBus); 27553 27554 // remove trap after minimal delay 27555 setTimeout(untrap, 400); 27556 27557 // prevent default action (click) 27558 preventDefault$1(event); 27559 } 27560 27561 end(event); 27562 } 27563 27564 function trapTouch(event) { 27565 move(event); 27566 } 27567 27568 // update the drag events hover (djs.model.Base) and hoverGfx (Snap<SVGElement>) 27569 // properties during hover and out and fire {prefix}.hover and {prefix}.out properties 27570 // respectively 27571 27572 function hover(event) { 27573 var payload = context.payload; 27574 27575 payload.hoverGfx = event.gfx; 27576 payload.hover = event.element; 27577 27578 fire('hover'); 27579 } 27580 27581 function out(event) { 27582 fire('out'); 27583 27584 var payload = context.payload; 27585 27586 payload.hoverGfx = null; 27587 payload.hover = null; 27588 } 27589 27590 27591 // life-cycle methods 27592 27593 function cancel(restore) { 27594 var previousContext; 27595 27596 if (!context) { 27597 return; 27598 } 27599 27600 var wasActive = context.active; 27601 27602 if (wasActive) { 27603 fire('cancel'); 27604 } 27605 27606 previousContext = cleanup(restore); 27607 27608 if (wasActive) { 27609 27610 // last event to be fired when all drag operations are done 27611 // at this point in time no drag operation is in progress anymore 27612 fire('canceled', previousContext); 27613 } 27614 } 27615 27616 function cleanup(restore) { 27617 var previousContext, 27618 endDrag; 27619 27620 fire('cleanup'); 27621 27622 // reset cursor 27623 unset(); 27624 27625 if (context.trapClick) { 27626 endDrag = trapClickAndEnd; 27627 } else { 27628 endDrag = end; 27629 } 27630 27631 // reset dom listeners 27632 componentEvent.unbind(document, 'mousemove', move); 27633 27634 componentEvent.unbind(document, 'dragstart', preventDefault$1); 27635 componentEvent.unbind(document, 'selectstart', preventDefault$1); 27636 27637 componentEvent.unbind(document, 'mousedown', endDrag, true); 27638 componentEvent.unbind(document, 'mouseup', endDrag, true); 27639 27640 componentEvent.unbind(document, 'keyup', checkCancel); 27641 27642 componentEvent.unbind(document, 'touchstart', trapTouch, true); 27643 componentEvent.unbind(document, 'touchcancel', cancel, true); 27644 componentEvent.unbind(document, 'touchmove', move, true); 27645 componentEvent.unbind(document, 'touchend', end, true); 27646 27647 eventBus.off('element.hover', hover); 27648 eventBus.off('element.out', out); 27649 27650 // remove drag marker on root element 27651 canvas.removeMarker(canvas.getRootElement(), DRAG_ACTIVE_CLS); 27652 27653 // restore selection, unless it has changed 27654 var previousSelection = context.payload.previousSelection; 27655 27656 if (restore !== false && previousSelection && !selection.get().length) { 27657 restoreSelection(previousSelection); 27658 } 27659 27660 previousContext = context; 27661 27662 context = null; 27663 27664 return previousContext; 27665 } 27666 27667 /** 27668 * Initialize a drag operation. 27669 * 27670 * If `localPosition` is given, drag events will be emitted 27671 * relative to it. 27672 * 27673 * @param {MouseEvent|TouchEvent} [event] 27674 * @param {Point} [localPosition] actual diagram local position this drag operation should start at 27675 * @param {string} prefix 27676 * @param {Object} [options] 27677 */ 27678 function init(event, relativeTo, prefix, options) { 27679 27680 // only one drag operation may be active, at a time 27681 if (context) { 27682 cancel(false); 27683 } 27684 27685 if (typeof relativeTo === 'string') { 27686 options = prefix; 27687 prefix = relativeTo; 27688 relativeTo = null; 27689 } 27690 27691 options = assign({}, defaultOptions, options || {}); 27692 27693 var data = options.data || {}, 27694 originalEvent, 27695 globalStart, 27696 localStart, 27697 endDrag, 27698 isTouch; 27699 27700 if (options.trapClick) { 27701 endDrag = trapClickAndEnd; 27702 } else { 27703 endDrag = end; 27704 } 27705 27706 if (event) { 27707 originalEvent = getOriginal$1(event) || event; 27708 globalStart = toPoint(event); 27709 27710 stopPropagation$1(event); 27711 27712 // prevent default browser dragging behavior 27713 if (originalEvent.type === 'dragstart') { 27714 preventDefault$1(originalEvent); 27715 } 27716 } else { 27717 originalEvent = null; 27718 globalStart = { x: 0, y: 0 }; 27719 } 27720 27721 localStart = toLocalPoint(globalStart); 27722 27723 if (!relativeTo) { 27724 relativeTo = localStart; 27725 } 27726 27727 isTouch = isTouchEvent(originalEvent); 27728 27729 context = assign({ 27730 prefix: prefix, 27731 data: data, 27732 payload: {}, 27733 globalStart: globalStart, 27734 displacement: delta(relativeTo, localStart), 27735 localStart: localStart, 27736 isTouch: isTouch 27737 }, options); 27738 27739 // skip dom registration if trigger 27740 // is set to manual (during testing) 27741 if (!options.manual) { 27742 27743 // add dom listeners 27744 27745 if (isTouch) { 27746 componentEvent.bind(document, 'touchstart', trapTouch, true); 27747 componentEvent.bind(document, 'touchcancel', cancel, true); 27748 componentEvent.bind(document, 'touchmove', move, true); 27749 componentEvent.bind(document, 'touchend', end, true); 27750 } else { 27751 27752 // assume we use the mouse to interact per default 27753 componentEvent.bind(document, 'mousemove', move); 27754 27755 // prevent default browser drag and text selection behavior 27756 componentEvent.bind(document, 'dragstart', preventDefault$1); 27757 componentEvent.bind(document, 'selectstart', preventDefault$1); 27758 27759 componentEvent.bind(document, 'mousedown', endDrag, true); 27760 componentEvent.bind(document, 'mouseup', endDrag, true); 27761 } 27762 27763 componentEvent.bind(document, 'keyup', checkCancel); 27764 27765 eventBus.on('element.hover', hover); 27766 eventBus.on('element.out', out); 27767 } 27768 27769 fire('init'); 27770 27771 if (options.autoActivate) { 27772 move(event, true); 27773 } 27774 } 27775 27776 // cancel on diagram destruction 27777 eventBus.on('diagram.destroy', cancel); 27778 27779 27780 // API 27781 27782 this.init = init; 27783 this.move = move; 27784 this.hover = hover; 27785 this.out = out; 27786 this.end = end; 27787 27788 this.cancel = cancel; 27789 27790 // for introspection 27791 27792 this.context = function() { 27793 return context; 27794 }; 27795 27796 this.setOptions = function(options) { 27797 assign(defaultOptions, options); 27798 }; 27799 } 27800 27801 Dragging.$inject = [ 27802 'eventBus', 27803 'canvas', 27804 'selection', 27805 'elementRegistry' 27806 ]; 27807 27808 var DraggingModule = { 27809 __depends__: [ 27810 HoverFixModule, 27811 SelectionModule, 27812 ], 27813 dragging: [ 'type', Dragging ], 27814 }; 27815 27816 /** 27817 * Initiates canvas scrolling if current cursor point is close to a border. 27818 * Cancelled when current point moves back inside the scrolling borders 27819 * or cancelled manually. 27820 * 27821 * Default options : 27822 * scrollThresholdIn: [ 20, 20, 20, 20 ], 27823 * scrollThresholdOut: [ 0, 0, 0, 0 ], 27824 * scrollRepeatTimeout: 15, 27825 * scrollStep: 10 27826 * 27827 * Threshold order: 27828 * [ left, top, right, bottom ] 27829 */ 27830 function AutoScroll(config, eventBus, canvas) { 27831 27832 this._canvas = canvas; 27833 27834 this._opts = assign({ 27835 scrollThresholdIn: [ 20, 20, 20, 20 ], 27836 scrollThresholdOut: [ 0, 0, 0, 0 ], 27837 scrollRepeatTimeout: 15, 27838 scrollStep: 10 27839 }, config); 27840 27841 var self = this; 27842 27843 eventBus.on('drag.move', function(e) { 27844 var point = self._toBorderPoint(e); 27845 27846 self.startScroll(point); 27847 }); 27848 27849 eventBus.on([ 'drag.cleanup' ], function() { 27850 self.stopScroll(); 27851 }); 27852 } 27853 27854 AutoScroll.$inject = [ 27855 'config.autoScroll', 27856 'eventBus', 27857 'canvas' 27858 ]; 27859 27860 27861 /** 27862 * Starts scrolling loop. 27863 * Point is given in global scale in canvas container box plane. 27864 * 27865 * @param {Object} point { x: X, y: Y } 27866 */ 27867 AutoScroll.prototype.startScroll = function(point) { 27868 27869 var canvas = this._canvas; 27870 var opts = this._opts; 27871 var self = this; 27872 27873 var clientRect = canvas.getContainer().getBoundingClientRect(); 27874 27875 var diff = [ 27876 point.x, 27877 point.y, 27878 clientRect.width - point.x, 27879 clientRect.height - point.y 27880 ]; 27881 27882 this.stopScroll(); 27883 27884 var dx = 0, 27885 dy = 0; 27886 27887 for (var i = 0; i < 4; i++) { 27888 if (between(diff[i], opts.scrollThresholdOut[i], opts.scrollThresholdIn[i])) { 27889 if (i === 0) { 27890 dx = opts.scrollStep; 27891 } else if (i == 1) { 27892 dy = opts.scrollStep; 27893 } else if (i == 2) { 27894 dx = -opts.scrollStep; 27895 } else if (i == 3) { 27896 dy = -opts.scrollStep; 27897 } 27898 } 27899 } 27900 27901 if (dx !== 0 || dy !== 0) { 27902 canvas.scroll({ dx: dx, dy: dy }); 27903 27904 this._scrolling = setTimeout(function() { 27905 self.startScroll(point); 27906 }, opts.scrollRepeatTimeout); 27907 } 27908 }; 27909 27910 function between(val, start, end) { 27911 if (start < val && val < end) { 27912 return true; 27913 } 27914 27915 return false; 27916 } 27917 27918 27919 /** 27920 * Stops scrolling loop. 27921 */ 27922 AutoScroll.prototype.stopScroll = function() { 27923 clearTimeout(this._scrolling); 27924 }; 27925 27926 27927 /** 27928 * Overrides defaults options. 27929 * 27930 * @param {Object} options 27931 */ 27932 AutoScroll.prototype.setOptions = function(options) { 27933 this._opts = assign({}, this._opts, options); 27934 }; 27935 27936 27937 /** 27938 * Converts event to a point in canvas container plane in global scale. 27939 * 27940 * @param {Event} event 27941 * @return {Point} 27942 */ 27943 AutoScroll.prototype._toBorderPoint = function(event) { 27944 var clientRect = this._canvas._container.getBoundingClientRect(); 27945 27946 var globalPosition = toPoint(event.originalEvent); 27947 27948 return { 27949 x: globalPosition.x - clientRect.left, 27950 y: globalPosition.y - clientRect.top 27951 }; 27952 }; 27953 27954 var AutoScrollModule = { 27955 __depends__: [ 27956 DraggingModule, 27957 ], 27958 __init__: [ 'autoScroll' ], 27959 autoScroll: [ 'type', AutoScroll ] 27960 }; 27961 27962 /** 27963 * A service that provides rules for certain diagram actions. 27964 * 27965 * The default implementation will hook into the {@link CommandStack} 27966 * to perform the actual rule evaluation. Make sure to provide the 27967 * `commandStack` service with this module if you plan to use it. 27968 * 27969 * Together with this implementation you may use the {@link RuleProvider} 27970 * to implement your own rule checkers. 27971 * 27972 * This module is ment to be easily replaced, thus the tiny foot print. 27973 * 27974 * @param {Injector} injector 27975 */ 27976 function Rules(injector) { 27977 this._commandStack = injector.get('commandStack', false); 27978 } 27979 27980 Rules.$inject = [ 'injector' ]; 27981 27982 27983 /** 27984 * Returns whether or not a given modeling action can be executed 27985 * in the specified context. 27986 * 27987 * This implementation will respond with allow unless anyone 27988 * objects. 27989 * 27990 * @param {string} action the action to be checked 27991 * @param {Object} [context] the context to check the action in 27992 * 27993 * @return {boolean} returns true, false or null depending on whether the 27994 * operation is allowed, not allowed or should be ignored. 27995 */ 27996 Rules.prototype.allowed = function(action, context) { 27997 var allowed = true; 27998 27999 var commandStack = this._commandStack; 28000 28001 if (commandStack) { 28002 allowed = commandStack.canExecute(action, context); 28003 } 28004 28005 // map undefined to true, i.e. no rules 28006 return allowed === undefined ? true : allowed; 28007 }; 28008 28009 var RulesModule$1 = { 28010 __init__: [ 'rules' ], 28011 rules: [ 'type', Rules ] 28012 }; 28013 28014 var round$9 = Math.round, 28015 max$6 = Math.max; 28016 28017 28018 function circlePath(center, r) { 28019 var x = center.x, 28020 y = center.y; 28021 28022 return [ 28023 ['M', x, y], 28024 ['m', 0, -r], 28025 ['a', r, r, 0, 1, 1, 0, 2 * r], 28026 ['a', r, r, 0, 1, 1, 0, -2 * r], 28027 ['z'] 28028 ]; 28029 } 28030 28031 function linePath(points) { 28032 var segments = []; 28033 28034 points.forEach(function(p, idx) { 28035 segments.push([ idx === 0 ? 'M' : 'L', p.x, p.y ]); 28036 }); 28037 28038 return segments; 28039 } 28040 28041 28042 var INTERSECTION_THRESHOLD$1 = 10; 28043 28044 function getBendpointIntersection(waypoints, reference) { 28045 28046 var i, w; 28047 28048 for (i = 0; (w = waypoints[i]); i++) { 28049 28050 if (pointDistance(w, reference) <= INTERSECTION_THRESHOLD$1) { 28051 return { 28052 point: waypoints[i], 28053 bendpoint: true, 28054 index: i 28055 }; 28056 } 28057 } 28058 28059 return null; 28060 } 28061 28062 function getPathIntersection(waypoints, reference) { 28063 28064 var intersections = intersect(circlePath(reference, INTERSECTION_THRESHOLD$1), linePath(waypoints)); 28065 28066 var a = intersections[0], 28067 b = intersections[intersections.length - 1], 28068 idx; 28069 28070 if (!a) { 28071 28072 // no intersection 28073 return null; 28074 } 28075 28076 if (a !== b) { 28077 28078 if (a.segment2 !== b.segment2) { 28079 28080 // we use the bendpoint in between both segments 28081 // as the intersection point 28082 28083 idx = max$6(a.segment2, b.segment2) - 1; 28084 28085 return { 28086 point: waypoints[idx], 28087 bendpoint: true, 28088 index: idx 28089 }; 28090 } 28091 28092 return { 28093 point: { 28094 x: (round$9(a.x + b.x) / 2), 28095 y: (round$9(a.y + b.y) / 2) 28096 }, 28097 index: a.segment2 28098 }; 28099 } 28100 28101 return { 28102 point: { 28103 x: round$9(a.x), 28104 y: round$9(a.y) 28105 }, 28106 index: a.segment2 28107 }; 28108 } 28109 28110 /** 28111 * Returns the closest point on the connection towards a given reference point. 28112 * 28113 * @param {Array<Point>} waypoints 28114 * @param {Point} reference 28115 * 28116 * @return {Object} intersection data (segment, point) 28117 */ 28118 function getApproxIntersection(waypoints, reference) { 28119 return getBendpointIntersection(waypoints, reference) || getPathIntersection(waypoints, reference); 28120 } 28121 28122 var BENDPOINT_CLS = 'djs-bendpoint'; 28123 var SEGMENT_DRAGGER_CLS = 'djs-segment-dragger'; 28124 28125 function toCanvasCoordinates(canvas, event) { 28126 28127 var position = toPoint(event), 28128 clientRect = canvas._container.getBoundingClientRect(), 28129 offset; 28130 28131 // canvas relative position 28132 28133 offset = { 28134 x: clientRect.left, 28135 y: clientRect.top 28136 }; 28137 28138 // update actual event payload with canvas relative measures 28139 28140 var viewbox = canvas.viewbox(); 28141 28142 return { 28143 x: viewbox.x + (position.x - offset.x) / viewbox.scale, 28144 y: viewbox.y + (position.y - offset.y) / viewbox.scale 28145 }; 28146 } 28147 28148 function getConnectionIntersection(canvas, waypoints, event) { 28149 var localPosition = toCanvasCoordinates(canvas, event), 28150 intersection = getApproxIntersection(waypoints, localPosition); 28151 28152 return intersection; 28153 } 28154 28155 function addBendpoint(parentGfx, cls) { 28156 var groupGfx = create$1('g'); 28157 classes(groupGfx).add(BENDPOINT_CLS); 28158 28159 append(parentGfx, groupGfx); 28160 28161 var visual = create$1('circle'); 28162 attr(visual, { 28163 cx: 0, 28164 cy: 0, 28165 r: 4 28166 }); 28167 classes(visual).add('djs-visual'); 28168 28169 append(groupGfx, visual); 28170 28171 var hit = create$1('circle'); 28172 attr(hit, { 28173 cx: 0, 28174 cy: 0, 28175 r: 10 28176 }); 28177 classes(hit).add('djs-hit'); 28178 28179 append(groupGfx, hit); 28180 28181 if (cls) { 28182 classes(groupGfx).add(cls); 28183 } 28184 28185 return groupGfx; 28186 } 28187 28188 function createParallelDragger(parentGfx, segmentStart, segmentEnd, alignment) { 28189 var draggerGfx = create$1('g'); 28190 28191 append(parentGfx, draggerGfx); 28192 28193 var width = 14, 28194 height = 3, 28195 padding = 11, 28196 hitWidth = calculateHitWidth(segmentStart, segmentEnd, alignment), 28197 hitHeight = height + padding; 28198 28199 var visual = create$1('rect'); 28200 attr(visual, { 28201 x: -width / 2, 28202 y: -height / 2, 28203 width: width, 28204 height: height 28205 }); 28206 classes(visual).add('djs-visual'); 28207 28208 append(draggerGfx, visual); 28209 28210 var hit = create$1('rect'); 28211 attr(hit, { 28212 x: -hitWidth / 2, 28213 y: -hitHeight / 2, 28214 width: hitWidth, 28215 height: hitHeight 28216 }); 28217 classes(hit).add('djs-hit'); 28218 28219 append(draggerGfx, hit); 28220 28221 rotate(draggerGfx, alignment === 'v' ? 90 : 0); 28222 28223 return draggerGfx; 28224 } 28225 28226 28227 function addSegmentDragger(parentGfx, segmentStart, segmentEnd) { 28228 28229 var groupGfx = create$1('g'), 28230 mid = getMidPoint(segmentStart, segmentEnd), 28231 alignment = pointsAligned(segmentStart, segmentEnd); 28232 28233 append(parentGfx, groupGfx); 28234 28235 createParallelDragger(groupGfx, segmentStart, segmentEnd, alignment); 28236 28237 classes(groupGfx).add(SEGMENT_DRAGGER_CLS); 28238 classes(groupGfx).add(alignment === 'h' ? 'horizontal' : 'vertical'); 28239 28240 translate$2(groupGfx, mid.x, mid.y); 28241 28242 return groupGfx; 28243 } 28244 28245 /** 28246 * Calculates region for segment move which is 2/3 of the full segment length 28247 * @param {number} segmentLength 28248 * 28249 * @return {number} 28250 */ 28251 function calculateSegmentMoveRegion(segmentLength) { 28252 return Math.abs(Math.round(segmentLength * 2 / 3)); 28253 } 28254 28255 // helper ////////// 28256 28257 function calculateHitWidth(segmentStart, segmentEnd, alignment) { 28258 var segmentLengthXAxis = segmentEnd.x - segmentStart.x, 28259 segmentLengthYAxis = segmentEnd.y - segmentStart.y; 28260 28261 return alignment === 'h' ? 28262 calculateSegmentMoveRegion(segmentLengthXAxis) : 28263 calculateSegmentMoveRegion(segmentLengthYAxis); 28264 } 28265 28266 var css_escape = {exports: {}}; 28267 28268 /*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */ 28269 28270 (function (module, exports) { 28271 (function(root, factory) { 28272 // https://github.com/umdjs/umd/blob/master/returnExports.js 28273 { 28274 // For Node.js. 28275 module.exports = factory(root); 28276 } 28277 }(typeof commonjsGlobal != 'undefined' ? commonjsGlobal : commonjsGlobal, function(root) { 28278 28279 if (root.CSS && root.CSS.escape) { 28280 return root.CSS.escape; 28281 } 28282 28283 // https://drafts.csswg.org/cssom/#serialize-an-identifier 28284 var cssEscape = function(value) { 28285 if (arguments.length == 0) { 28286 throw new TypeError('`CSS.escape` requires an argument.'); 28287 } 28288 var string = String(value); 28289 var length = string.length; 28290 var index = -1; 28291 var codeUnit; 28292 var result = ''; 28293 var firstCodeUnit = string.charCodeAt(0); 28294 while (++index < length) { 28295 codeUnit = string.charCodeAt(index); 28296 // Note: there’s no need to special-case astral symbols, surrogate 28297 // pairs, or lone surrogates. 28298 28299 // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER 28300 // (U+FFFD). 28301 if (codeUnit == 0x0000) { 28302 result += '\uFFFD'; 28303 continue; 28304 } 28305 28306 if ( 28307 // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is 28308 // U+007F, […] 28309 (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || 28310 // If the character is the first character and is in the range [0-9] 28311 // (U+0030 to U+0039), […] 28312 (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || 28313 // If the character is the second character and is in the range [0-9] 28314 // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] 28315 ( 28316 index == 1 && 28317 codeUnit >= 0x0030 && codeUnit <= 0x0039 && 28318 firstCodeUnit == 0x002D 28319 ) 28320 ) { 28321 // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point 28322 result += '\\' + codeUnit.toString(16) + ' '; 28323 continue; 28324 } 28325 28326 if ( 28327 // If the character is the first character and is a `-` (U+002D), and 28328 // there is no second character, […] 28329 index == 0 && 28330 length == 1 && 28331 codeUnit == 0x002D 28332 ) { 28333 result += '\\' + string.charAt(index); 28334 continue; 28335 } 28336 28337 // If the character is not handled by one of the above rules and is 28338 // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or 28339 // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to 28340 // U+005A), or [a-z] (U+0061 to U+007A), […] 28341 if ( 28342 codeUnit >= 0x0080 || 28343 codeUnit == 0x002D || 28344 codeUnit == 0x005F || 28345 codeUnit >= 0x0030 && codeUnit <= 0x0039 || 28346 codeUnit >= 0x0041 && codeUnit <= 0x005A || 28347 codeUnit >= 0x0061 && codeUnit <= 0x007A 28348 ) { 28349 // the character itself 28350 result += string.charAt(index); 28351 continue; 28352 } 28353 28354 // Otherwise, the escaped character. 28355 // https://drafts.csswg.org/cssom/#escape-a-character 28356 result += '\\' + string.charAt(index); 28357 28358 } 28359 return result; 28360 }; 28361 28362 if (!root.CSS) { 28363 root.CSS = {}; 28364 } 28365 28366 root.CSS.escape = cssEscape; 28367 return cssEscape; 28368 28369 })); 28370 }(css_escape)); 28371 28372 var cssEscape = css_escape.exports; 28373 28374 var HTML_ESCAPE_MAP = { 28375 '&': '&', 28376 '<': '<', 28377 '>': '>', 28378 '"': '"', 28379 '\'': ''' 28380 }; 28381 28382 function escapeHTML(str) { 28383 str = '' + str; 28384 28385 return str && str.replace(/[&<>"']/g, function(match) { 28386 return HTML_ESCAPE_MAP[match]; 28387 }); 28388 } 28389 28390 /** 28391 * A service that adds editable bendpoints to connections. 28392 */ 28393 function Bendpoints( 28394 eventBus, canvas, interactionEvents, 28395 bendpointMove, connectionSegmentMove) { 28396 28397 /** 28398 * Returns true if intersection point is inside middle region of segment, adjusted by 28399 * optional threshold 28400 */ 28401 function isIntersectionMiddle(intersection, waypoints, treshold) { 28402 var idx = intersection.index, 28403 p = intersection.point, 28404 p0, p1, mid, aligned, xDelta, yDelta; 28405 28406 if (idx <= 0 || intersection.bendpoint) { 28407 return false; 28408 } 28409 28410 p0 = waypoints[idx - 1]; 28411 p1 = waypoints[idx]; 28412 mid = getMidPoint(p0, p1), 28413 aligned = pointsAligned(p0, p1); 28414 xDelta = Math.abs(p.x - mid.x); 28415 yDelta = Math.abs(p.y - mid.y); 28416 28417 return aligned && xDelta <= treshold && yDelta <= treshold; 28418 } 28419 28420 /** 28421 * Calculates the threshold from a connection's middle which fits the two-third-region 28422 */ 28423 function calculateIntersectionThreshold(connection, intersection) { 28424 var waypoints = connection.waypoints, 28425 relevantSegment, alignment, segmentLength, threshold; 28426 28427 if (intersection.index <= 0 || intersection.bendpoint) { 28428 return null; 28429 } 28430 28431 // segment relative to connection intersection 28432 relevantSegment = { 28433 start: waypoints[intersection.index - 1], 28434 end: waypoints[intersection.index] 28435 }; 28436 28437 alignment = pointsAligned(relevantSegment.start, relevantSegment.end); 28438 28439 if (!alignment) { 28440 return null; 28441 } 28442 28443 if (alignment === 'h') { 28444 segmentLength = relevantSegment.end.x - relevantSegment.start.x; 28445 } else { 28446 segmentLength = relevantSegment.end.y - relevantSegment.start.y; 28447 } 28448 28449 // calculate threshold relative to 2/3 of segment length 28450 threshold = calculateSegmentMoveRegion(segmentLength) / 2; 28451 28452 return threshold; 28453 } 28454 28455 function activateBendpointMove(event, connection) { 28456 var waypoints = connection.waypoints, 28457 intersection = getConnectionIntersection(canvas, waypoints, event), 28458 threshold; 28459 28460 if (!intersection) { 28461 return; 28462 } 28463 28464 threshold = calculateIntersectionThreshold(connection, intersection); 28465 28466 if (isIntersectionMiddle(intersection, waypoints, threshold)) { 28467 connectionSegmentMove.start(event, connection, intersection.index); 28468 } else { 28469 bendpointMove.start(event, connection, intersection.index, !intersection.bendpoint); 28470 } 28471 28472 // we've handled the event 28473 return true; 28474 } 28475 28476 function bindInteractionEvents(node, eventName, element) { 28477 28478 componentEvent.bind(node, eventName, function(event) { 28479 interactionEvents.triggerMouseEvent(eventName, event, element); 28480 event.stopPropagation(); 28481 }); 28482 } 28483 28484 function getBendpointsContainer(element, create) { 28485 28486 var layer = canvas.getLayer('overlays'), 28487 gfx = query('.djs-bendpoints[data-element-id="' + cssEscape(element.id) + '"]', layer); 28488 28489 if (!gfx && create) { 28490 gfx = create$1('g'); 28491 attr(gfx, { 'data-element-id': element.id }); 28492 classes(gfx).add('djs-bendpoints'); 28493 28494 append(layer, gfx); 28495 28496 bindInteractionEvents(gfx, 'mousedown', element); 28497 bindInteractionEvents(gfx, 'click', element); 28498 bindInteractionEvents(gfx, 'dblclick', element); 28499 } 28500 28501 return gfx; 28502 } 28503 28504 function getSegmentDragger(idx, parentGfx) { 28505 return query( 28506 '.djs-segment-dragger[data-segment-idx="' + idx + '"]', 28507 parentGfx 28508 ); 28509 } 28510 28511 function createBendpoints(gfx, connection) { 28512 connection.waypoints.forEach(function(p, idx) { 28513 var bendpoint = addBendpoint(gfx); 28514 28515 append(gfx, bendpoint); 28516 28517 translate$2(bendpoint, p.x, p.y); 28518 }); 28519 28520 // add floating bendpoint 28521 addBendpoint(gfx, 'floating'); 28522 } 28523 28524 function createSegmentDraggers(gfx, connection) { 28525 28526 var waypoints = connection.waypoints; 28527 28528 var segmentStart, 28529 segmentEnd, 28530 segmentDraggerGfx; 28531 28532 for (var i = 1; i < waypoints.length; i++) { 28533 28534 segmentStart = waypoints[i - 1]; 28535 segmentEnd = waypoints[i]; 28536 28537 if (pointsAligned(segmentStart, segmentEnd)) { 28538 segmentDraggerGfx = addSegmentDragger(gfx, segmentStart, segmentEnd); 28539 28540 attr(segmentDraggerGfx, { 'data-segment-idx': i }); 28541 28542 bindInteractionEvents(segmentDraggerGfx, 'mousemove', connection); 28543 } 28544 } 28545 } 28546 28547 function clearBendpoints(gfx) { 28548 forEach(all('.' + BENDPOINT_CLS, gfx), function(node) { 28549 remove$1(node); 28550 }); 28551 } 28552 28553 function clearSegmentDraggers(gfx) { 28554 forEach(all('.' + SEGMENT_DRAGGER_CLS, gfx), function(node) { 28555 remove$1(node); 28556 }); 28557 } 28558 28559 function addHandles(connection) { 28560 28561 var gfx = getBendpointsContainer(connection); 28562 28563 if (!gfx) { 28564 gfx = getBendpointsContainer(connection, true); 28565 28566 createBendpoints(gfx, connection); 28567 createSegmentDraggers(gfx, connection); 28568 } 28569 28570 return gfx; 28571 } 28572 28573 function updateHandles(connection) { 28574 28575 var gfx = getBendpointsContainer(connection); 28576 28577 if (gfx) { 28578 clearSegmentDraggers(gfx); 28579 clearBendpoints(gfx); 28580 createSegmentDraggers(gfx, connection); 28581 createBendpoints(gfx, connection); 28582 } 28583 } 28584 28585 function updateFloatingBendpointPosition(parentGfx, intersection) { 28586 var floating = query('.floating', parentGfx), 28587 point = intersection.point; 28588 28589 if (!floating) { 28590 return; 28591 } 28592 28593 translate$2(floating, point.x, point.y); 28594 28595 } 28596 28597 function updateSegmentDraggerPosition(parentGfx, intersection, waypoints) { 28598 28599 var draggerGfx = getSegmentDragger(intersection.index, parentGfx), 28600 segmentStart = waypoints[intersection.index - 1], 28601 segmentEnd = waypoints[intersection.index], 28602 point = intersection.point, 28603 mid = getMidPoint(segmentStart, segmentEnd), 28604 alignment = pointsAligned(segmentStart, segmentEnd), 28605 draggerVisual, relativePosition; 28606 28607 if (!draggerGfx) { 28608 return; 28609 } 28610 28611 draggerVisual = getDraggerVisual(draggerGfx); 28612 28613 relativePosition = { 28614 x: point.x - mid.x, 28615 y: point.y - mid.y 28616 }; 28617 28618 if (alignment === 'v') { 28619 28620 // rotate position 28621 relativePosition = { 28622 x: relativePosition.y, 28623 y: relativePosition.x 28624 }; 28625 } 28626 28627 translate$2(draggerVisual, relativePosition.x, relativePosition.y); 28628 } 28629 28630 eventBus.on('connection.changed', function(event) { 28631 updateHandles(event.element); 28632 }); 28633 28634 eventBus.on('connection.remove', function(event) { 28635 var gfx = getBendpointsContainer(event.element); 28636 28637 if (gfx) { 28638 remove$1(gfx); 28639 } 28640 }); 28641 28642 eventBus.on('element.marker.update', function(event) { 28643 28644 var element = event.element, 28645 bendpointsGfx; 28646 28647 if (!element.waypoints) { 28648 return; 28649 } 28650 28651 bendpointsGfx = addHandles(element); 28652 28653 if (event.add) { 28654 classes(bendpointsGfx).add(event.marker); 28655 } else { 28656 classes(bendpointsGfx).remove(event.marker); 28657 } 28658 }); 28659 28660 eventBus.on('element.mousemove', function(event) { 28661 28662 var element = event.element, 28663 waypoints = element.waypoints, 28664 bendpointsGfx, 28665 intersection; 28666 28667 if (waypoints) { 28668 bendpointsGfx = getBendpointsContainer(element, true); 28669 28670 intersection = getConnectionIntersection(canvas, waypoints, event.originalEvent); 28671 28672 if (!intersection) { 28673 return; 28674 } 28675 28676 updateFloatingBendpointPosition(bendpointsGfx, intersection); 28677 28678 if (!intersection.bendpoint) { 28679 updateSegmentDraggerPosition(bendpointsGfx, intersection, waypoints); 28680 } 28681 28682 } 28683 }); 28684 28685 eventBus.on('element.mousedown', function(event) { 28686 28687 if (!isPrimaryButton(event)) { 28688 return; 28689 } 28690 28691 var originalEvent = event.originalEvent, 28692 element = event.element; 28693 28694 if (!element.waypoints) { 28695 return; 28696 } 28697 28698 return activateBendpointMove(originalEvent, element); 28699 }); 28700 28701 eventBus.on('selection.changed', function(event) { 28702 var newSelection = event.newSelection, 28703 primary = newSelection[0]; 28704 28705 if (primary && primary.waypoints) { 28706 addHandles(primary); 28707 } 28708 }); 28709 28710 eventBus.on('element.hover', function(event) { 28711 var element = event.element; 28712 28713 if (element.waypoints) { 28714 addHandles(element); 28715 interactionEvents.registerEvent(event.gfx, 'mousemove', 'element.mousemove'); 28716 } 28717 }); 28718 28719 eventBus.on('element.out', function(event) { 28720 interactionEvents.unregisterEvent(event.gfx, 'mousemove', 'element.mousemove'); 28721 }); 28722 28723 // update bendpoint container data attribute on element ID change 28724 eventBus.on('element.updateId', function(context) { 28725 var element = context.element, 28726 newId = context.newId; 28727 28728 if (element.waypoints) { 28729 var bendpointContainer = getBendpointsContainer(element); 28730 28731 if (bendpointContainer) { 28732 attr(bendpointContainer, { 'data-element-id': newId }); 28733 } 28734 } 28735 }); 28736 28737 // API 28738 28739 this.addHandles = addHandles; 28740 this.updateHandles = updateHandles; 28741 this.getBendpointsContainer = getBendpointsContainer; 28742 this.getSegmentDragger = getSegmentDragger; 28743 } 28744 28745 Bendpoints.$inject = [ 28746 'eventBus', 28747 'canvas', 28748 'interactionEvents', 28749 'bendpointMove', 28750 'connectionSegmentMove' 28751 ]; 28752 28753 28754 28755 // helper ///////////// 28756 28757 function getDraggerVisual(draggerGfx) { 28758 return query('.djs-visual', draggerGfx); 28759 } 28760 28761 var round$8 = Math.round; 28762 28763 var RECONNECT_START$1 = 'reconnectStart', 28764 RECONNECT_END$1 = 'reconnectEnd', 28765 UPDATE_WAYPOINTS$1 = 'updateWaypoints'; 28766 28767 28768 /** 28769 * Move bendpoints through drag and drop to add/remove bendpoints or reconnect connection. 28770 */ 28771 function BendpointMove(injector, eventBus, canvas, dragging, rules, modeling) { 28772 this._injector = injector; 28773 28774 this.start = function(event, connection, bendpointIndex, insert) { 28775 var gfx = canvas.getGraphics(connection), 28776 source = connection.source, 28777 target = connection.target, 28778 waypoints = connection.waypoints, 28779 type; 28780 28781 if (!insert && bendpointIndex === 0) { 28782 type = RECONNECT_START$1; 28783 } else 28784 if (!insert && bendpointIndex === waypoints.length - 1) { 28785 type = RECONNECT_END$1; 28786 } else { 28787 type = UPDATE_WAYPOINTS$1; 28788 } 28789 28790 var command = type === UPDATE_WAYPOINTS$1 ? 'connection.updateWaypoints' : 'connection.reconnect'; 28791 28792 var allowed = rules.allowed(command, { 28793 connection: connection, 28794 source: source, 28795 target: target 28796 }); 28797 28798 if (allowed === false) { 28799 allowed = rules.allowed(command, { 28800 connection: connection, 28801 source: target, 28802 target: source 28803 }); 28804 } 28805 28806 if (allowed === false) { 28807 return; 28808 } 28809 28810 dragging.init(event, 'bendpoint.move', { 28811 data: { 28812 connection: connection, 28813 connectionGfx: gfx, 28814 context: { 28815 allowed: allowed, 28816 bendpointIndex: bendpointIndex, 28817 connection: connection, 28818 source: source, 28819 target: target, 28820 insert: insert, 28821 type: type 28822 } 28823 } 28824 }); 28825 }; 28826 28827 eventBus.on('bendpoint.move.hover', function(event) { 28828 var context = event.context, 28829 connection = context.connection, 28830 source = connection.source, 28831 target = connection.target, 28832 hover = event.hover, 28833 type = context.type; 28834 28835 // cache hover state 28836 context.hover = hover; 28837 28838 var allowed; 28839 28840 if (!hover) { 28841 return; 28842 } 28843 28844 var command = type === UPDATE_WAYPOINTS$1 ? 'connection.updateWaypoints' : 'connection.reconnect'; 28845 28846 allowed = context.allowed = rules.allowed(command, { 28847 connection: connection, 28848 source: type === RECONNECT_START$1 ? hover : source, 28849 target: type === RECONNECT_END$1 ? hover : target 28850 }); 28851 28852 if (allowed) { 28853 context.source = type === RECONNECT_START$1 ? hover : source; 28854 context.target = type === RECONNECT_END$1 ? hover : target; 28855 28856 return; 28857 } 28858 28859 if (allowed === false) { 28860 allowed = context.allowed = rules.allowed(command, { 28861 connection: connection, 28862 source: type === RECONNECT_END$1 ? hover : target, 28863 target: type === RECONNECT_START$1 ? hover : source 28864 }); 28865 } 28866 28867 if (allowed) { 28868 context.source = type === RECONNECT_END$1 ? hover : target; 28869 context.target = type === RECONNECT_START$1 ? hover : source; 28870 } 28871 }); 28872 28873 eventBus.on([ 'bendpoint.move.out', 'bendpoint.move.cleanup' ], function(event) { 28874 var context = event.context, 28875 type = context.type; 28876 28877 context.hover = null; 28878 context.source = null; 28879 context.target = null; 28880 28881 if (type !== UPDATE_WAYPOINTS$1) { 28882 context.allowed = false; 28883 } 28884 }); 28885 28886 eventBus.on('bendpoint.move.end', function(event) { 28887 var context = event.context, 28888 allowed = context.allowed, 28889 bendpointIndex = context.bendpointIndex, 28890 connection = context.connection, 28891 insert = context.insert, 28892 newWaypoints = connection.waypoints.slice(), 28893 source = context.source, 28894 target = context.target, 28895 type = context.type, 28896 hints = context.hints || {}; 28897 28898 // ensure integer values (important if zoom level was > 1 during move) 28899 var docking = { 28900 x: round$8(event.x), 28901 y: round$8(event.y) 28902 }; 28903 28904 if (!allowed) { 28905 return false; 28906 } 28907 28908 if (type === UPDATE_WAYPOINTS$1) { 28909 if (insert) { 28910 28911 // insert new bendpoint 28912 newWaypoints.splice(bendpointIndex, 0, docking); 28913 } else { 28914 28915 // swap previous waypoint with moved one 28916 newWaypoints[bendpointIndex] = docking; 28917 } 28918 28919 // pass hints about actual moved bendpoint 28920 // useful for connection/label layout 28921 hints.bendpointMove = { 28922 insert: insert, 28923 bendpointIndex: bendpointIndex 28924 }; 28925 28926 newWaypoints = this.cropWaypoints(connection, newWaypoints); 28927 28928 modeling.updateWaypoints(connection, filterRedundantWaypoints(newWaypoints), hints); 28929 } else { 28930 if (type === RECONNECT_START$1) { 28931 hints.docking = 'source'; 28932 28933 if (isReverse$2(context)) { 28934 hints.docking = 'target'; 28935 28936 hints.newWaypoints = newWaypoints.reverse(); 28937 } 28938 } else if (type === RECONNECT_END$1) { 28939 hints.docking = 'target'; 28940 28941 if (isReverse$2(context)) { 28942 hints.docking = 'source'; 28943 28944 hints.newWaypoints = newWaypoints.reverse(); 28945 } 28946 } 28947 28948 modeling.reconnect(connection, source, target, docking, hints); 28949 } 28950 }, this); 28951 } 28952 28953 BendpointMove.$inject = [ 28954 'injector', 28955 'eventBus', 28956 'canvas', 28957 'dragging', 28958 'rules', 28959 'modeling' 28960 ]; 28961 28962 BendpointMove.prototype.cropWaypoints = function(connection, newWaypoints) { 28963 var connectionDocking = this._injector.get('connectionDocking', false); 28964 28965 if (!connectionDocking) { 28966 return newWaypoints; 28967 } 28968 28969 var waypoints = connection.waypoints; 28970 28971 connection.waypoints = newWaypoints; 28972 28973 connection.waypoints = connectionDocking.getCroppedWaypoints(connection); 28974 28975 newWaypoints = connection.waypoints; 28976 28977 connection.waypoints = waypoints; 28978 28979 return newWaypoints; 28980 }; 28981 28982 28983 // helpers ////////// 28984 28985 function isReverse$2(context) { 28986 var hover = context.hover, 28987 source = context.source, 28988 target = context.target, 28989 type = context.type; 28990 28991 if (type === RECONNECT_START$1) { 28992 return hover && target && hover === target && source !== target; 28993 } 28994 28995 if (type === RECONNECT_END$1) { 28996 return hover && source && hover === source && source !== target; 28997 } 28998 } 28999 29000 var RECONNECT_START = 'reconnectStart', 29001 RECONNECT_END = 'reconnectEnd', 29002 UPDATE_WAYPOINTS = 'updateWaypoints'; 29003 29004 var MARKER_OK$4 = 'connect-ok', 29005 MARKER_NOT_OK$4 = 'connect-not-ok', 29006 MARKER_CONNECT_HOVER$1 = 'connect-hover', 29007 MARKER_CONNECT_UPDATING$1 = 'djs-updating', 29008 MARKER_ELEMENT_HIDDEN = 'djs-element-hidden'; 29009 29010 var HIGH_PRIORITY$i = 1100; 29011 29012 /** 29013 * Preview connection while moving bendpoints. 29014 */ 29015 function BendpointMovePreview(bendpointMove, injector, eventBus, canvas) { 29016 this._injector = injector; 29017 29018 var connectionPreview = injector.get('connectionPreview', false); 29019 29020 eventBus.on('bendpoint.move.start', function(event) { 29021 var context = event.context, 29022 bendpointIndex = context.bendpointIndex, 29023 connection = context.connection, 29024 insert = context.insert, 29025 waypoints = connection.waypoints, 29026 newWaypoints = waypoints.slice(); 29027 29028 context.waypoints = waypoints; 29029 29030 if (insert) { 29031 29032 // insert placeholder for new bendpoint 29033 newWaypoints.splice(bendpointIndex, 0, { x: event.x, y: event.y }); 29034 } 29035 29036 connection.waypoints = newWaypoints; 29037 29038 // add dragger gfx 29039 var draggerGfx = context.draggerGfx = addBendpoint(canvas.getLayer('overlays')); 29040 29041 classes(draggerGfx).add('djs-dragging'); 29042 29043 canvas.addMarker(connection, MARKER_ELEMENT_HIDDEN); 29044 canvas.addMarker(connection, MARKER_CONNECT_UPDATING$1); 29045 }); 29046 29047 eventBus.on('bendpoint.move.hover', function(event) { 29048 var context = event.context, 29049 allowed = context.allowed, 29050 hover = context.hover, 29051 type = context.type; 29052 29053 if (hover) { 29054 canvas.addMarker(hover, MARKER_CONNECT_HOVER$1); 29055 29056 if (type === UPDATE_WAYPOINTS) { 29057 return; 29058 } 29059 29060 if (allowed) { 29061 canvas.removeMarker(hover, MARKER_NOT_OK$4); 29062 canvas.addMarker(hover, MARKER_OK$4); 29063 } else if (allowed === false) { 29064 canvas.removeMarker(hover, MARKER_OK$4); 29065 canvas.addMarker(hover, MARKER_NOT_OK$4); 29066 } 29067 } 29068 }); 29069 29070 eventBus.on([ 29071 'bendpoint.move.out', 29072 'bendpoint.move.cleanup' 29073 ], HIGH_PRIORITY$i, function(event) { 29074 var context = event.context, 29075 hover = context.hover, 29076 target = context.target; 29077 29078 if (hover) { 29079 canvas.removeMarker(hover, MARKER_CONNECT_HOVER$1); 29080 canvas.removeMarker(hover, target ? MARKER_OK$4 : MARKER_NOT_OK$4); 29081 } 29082 }); 29083 29084 eventBus.on('bendpoint.move.move', function(event) { 29085 var context = event.context, 29086 allowed = context.allowed, 29087 bendpointIndex = context.bendpointIndex, 29088 draggerGfx = context.draggerGfx, 29089 hover = context.hover, 29090 type = context.type, 29091 connection = context.connection, 29092 source = connection.source, 29093 target = connection.target, 29094 newWaypoints = connection.waypoints.slice(), 29095 bendpoint = { x: event.x, y: event.y }, 29096 hints = context.hints || {}, 29097 drawPreviewHints = {}; 29098 29099 if (connectionPreview) { 29100 if (hints.connectionStart) { 29101 drawPreviewHints.connectionStart = hints.connectionStart; 29102 } 29103 29104 if (hints.connectionEnd) { 29105 drawPreviewHints.connectionEnd = hints.connectionEnd; 29106 } 29107 29108 29109 if (type === RECONNECT_START) { 29110 if (isReverse$2(context)) { 29111 drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint; 29112 29113 drawPreviewHints.source = target; 29114 drawPreviewHints.target = hover || source; 29115 29116 newWaypoints = newWaypoints.reverse(); 29117 } else { 29118 drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint; 29119 29120 drawPreviewHints.source = hover || source; 29121 drawPreviewHints.target = target; 29122 } 29123 } else if (type === RECONNECT_END) { 29124 if (isReverse$2(context)) { 29125 drawPreviewHints.connectionStart = drawPreviewHints.connectionStart || bendpoint; 29126 29127 drawPreviewHints.source = hover || target; 29128 drawPreviewHints.target = source; 29129 29130 newWaypoints = newWaypoints.reverse(); 29131 } else { 29132 drawPreviewHints.connectionEnd = drawPreviewHints.connectionEnd || bendpoint; 29133 29134 drawPreviewHints.source = source; 29135 drawPreviewHints.target = hover || target; 29136 } 29137 29138 } else { 29139 drawPreviewHints.noCropping = true; 29140 drawPreviewHints.noLayout = true; 29141 newWaypoints[ bendpointIndex ] = bendpoint; 29142 } 29143 29144 if (type === UPDATE_WAYPOINTS) { 29145 newWaypoints = bendpointMove.cropWaypoints(connection, newWaypoints); 29146 } 29147 29148 drawPreviewHints.waypoints = newWaypoints; 29149 29150 connectionPreview.drawPreview(context, allowed, drawPreviewHints); 29151 } 29152 29153 translate$2(draggerGfx, event.x, event.y); 29154 }, this); 29155 29156 eventBus.on([ 29157 'bendpoint.move.end', 29158 'bendpoint.move.cancel' 29159 ], HIGH_PRIORITY$i, function(event) { 29160 var context = event.context, 29161 connection = context.connection, 29162 draggerGfx = context.draggerGfx, 29163 hover = context.hover, 29164 target = context.target, 29165 waypoints = context.waypoints; 29166 29167 connection.waypoints = waypoints; 29168 29169 // remove dragger gfx 29170 remove$1(draggerGfx); 29171 29172 canvas.removeMarker(connection, MARKER_CONNECT_UPDATING$1); 29173 canvas.removeMarker(connection, MARKER_ELEMENT_HIDDEN); 29174 29175 if (hover) { 29176 canvas.removeMarker(hover, MARKER_OK$4); 29177 canvas.removeMarker(hover, target ? MARKER_OK$4 : MARKER_NOT_OK$4); 29178 } 29179 29180 if (connectionPreview) { 29181 connectionPreview.cleanUp(context); 29182 } 29183 }); 29184 } 29185 29186 BendpointMovePreview.$inject = [ 29187 'bendpointMove', 29188 'injector', 29189 'eventBus', 29190 'canvas' 29191 ]; 29192 29193 var MARKER_CONNECT_HOVER = 'connect-hover', 29194 MARKER_CONNECT_UPDATING = 'djs-updating'; 29195 29196 29197 function axisAdd(point, axis, delta) { 29198 return axisSet(point, axis, point[axis] + delta); 29199 } 29200 29201 function axisSet(point, axis, value) { 29202 return { 29203 x: (axis === 'x' ? value : point.x), 29204 y: (axis === 'y' ? value : point.y) 29205 }; 29206 } 29207 29208 function axisFenced(position, segmentStart, segmentEnd, axis) { 29209 29210 var maxValue = Math.max(segmentStart[axis], segmentEnd[axis]), 29211 minValue = Math.min(segmentStart[axis], segmentEnd[axis]); 29212 29213 var padding = 20; 29214 29215 var fencedValue = Math.min(Math.max(minValue + padding, position[axis]), maxValue - padding); 29216 29217 return axisSet(segmentStart, axis, fencedValue); 29218 } 29219 29220 function flipAxis(axis) { 29221 return axis === 'x' ? 'y' : 'x'; 29222 } 29223 29224 /** 29225 * Get the docking point on the given element. 29226 * 29227 * Compute a reasonable docking, if non exists. 29228 * 29229 * @param {Point} point 29230 * @param {djs.model.Shape} referenceElement 29231 * @param {string} moveAxis (x|y) 29232 * 29233 * @return {Point} 29234 */ 29235 function getDocking$2(point, referenceElement, moveAxis) { 29236 29237 var referenceMid, 29238 inverseAxis; 29239 29240 if (point.original) { 29241 return point.original; 29242 } else { 29243 referenceMid = getMid(referenceElement); 29244 inverseAxis = flipAxis(moveAxis); 29245 29246 return axisSet(point, inverseAxis, referenceMid[inverseAxis]); 29247 } 29248 } 29249 29250 /** 29251 * A component that implements moving of bendpoints 29252 */ 29253 function ConnectionSegmentMove( 29254 injector, eventBus, canvas, 29255 dragging, graphicsFactory, modeling) { 29256 29257 // optional connection docking integration 29258 var connectionDocking = injector.get('connectionDocking', false); 29259 29260 29261 // API 29262 29263 this.start = function(event, connection, idx) { 29264 29265 var context, 29266 gfx = canvas.getGraphics(connection), 29267 segmentStartIndex = idx - 1, 29268 segmentEndIndex = idx, 29269 waypoints = connection.waypoints, 29270 segmentStart = waypoints[segmentStartIndex], 29271 segmentEnd = waypoints[segmentEndIndex], 29272 intersection = getConnectionIntersection(canvas, waypoints, event), 29273 direction, axis, dragPosition; 29274 29275 direction = pointsAligned(segmentStart, segmentEnd); 29276 29277 // do not move diagonal connection 29278 if (!direction) { 29279 return; 29280 } 29281 29282 // the axis where we are going to move things 29283 axis = direction === 'v' ? 'x' : 'y'; 29284 29285 if (segmentStartIndex === 0) { 29286 segmentStart = getDocking$2(segmentStart, connection.source, axis); 29287 } 29288 29289 if (segmentEndIndex === waypoints.length - 1) { 29290 segmentEnd = getDocking$2(segmentEnd, connection.target, axis); 29291 } 29292 29293 if (intersection) { 29294 dragPosition = intersection.point; 29295 } else { 29296 29297 // set to segment center as default 29298 dragPosition = { 29299 x: (segmentStart.x + segmentEnd.x) / 2, 29300 y: (segmentStart.y + segmentEnd.y) / 2 29301 }; 29302 } 29303 29304 context = { 29305 connection: connection, 29306 segmentStartIndex: segmentStartIndex, 29307 segmentEndIndex: segmentEndIndex, 29308 segmentStart: segmentStart, 29309 segmentEnd: segmentEnd, 29310 axis: axis, 29311 dragPosition: dragPosition 29312 }; 29313 29314 dragging.init(event, dragPosition, 'connectionSegment.move', { 29315 cursor: axis === 'x' ? 'resize-ew' : 'resize-ns', 29316 data: { 29317 connection: connection, 29318 connectionGfx: gfx, 29319 context: context 29320 } 29321 }); 29322 }; 29323 29324 /** 29325 * Crop connection if connection cropping is provided. 29326 * 29327 * @param {Connection} connection 29328 * @param {Array<Point>} newWaypoints 29329 * 29330 * @return {Array<Point>} cropped connection waypoints 29331 */ 29332 function cropConnection(connection, newWaypoints) { 29333 29334 // crop connection, if docking service is provided only 29335 if (!connectionDocking) { 29336 return newWaypoints; 29337 } 29338 29339 var oldWaypoints = connection.waypoints, 29340 croppedWaypoints; 29341 29342 // temporary set new waypoints 29343 connection.waypoints = newWaypoints; 29344 29345 croppedWaypoints = connectionDocking.getCroppedWaypoints(connection); 29346 29347 // restore old waypoints 29348 connection.waypoints = oldWaypoints; 29349 29350 return croppedWaypoints; 29351 } 29352 29353 // DRAGGING IMPLEMENTATION 29354 29355 function redrawConnection(data) { 29356 graphicsFactory.update('connection', data.connection, data.connectionGfx); 29357 } 29358 29359 function updateDragger(context, segmentOffset, event) { 29360 29361 var newWaypoints = context.newWaypoints, 29362 segmentStartIndex = context.segmentStartIndex + segmentOffset, 29363 segmentStart = newWaypoints[segmentStartIndex], 29364 segmentEndIndex = context.segmentEndIndex + segmentOffset, 29365 segmentEnd = newWaypoints[segmentEndIndex], 29366 axis = flipAxis(context.axis); 29367 29368 // make sure the dragger does not move 29369 // outside the connection 29370 var draggerPosition = axisFenced(event, segmentStart, segmentEnd, axis); 29371 29372 // update dragger 29373 translate$2(context.draggerGfx, draggerPosition.x, draggerPosition.y); 29374 } 29375 29376 /** 29377 * Filter waypoints for redundant ones (i.e. on the same axis). 29378 * Returns the filtered waypoints and the offset related to the segment move. 29379 * 29380 * @param {Array<Point>} waypoints 29381 * @param {Integer} segmentStartIndex of moved segment start 29382 * 29383 * @return {Object} { filteredWaypoints, segmentOffset } 29384 */ 29385 function filterRedundantWaypoints(waypoints, segmentStartIndex) { 29386 29387 var segmentOffset = 0; 29388 29389 var filteredWaypoints = waypoints.filter(function(r, idx) { 29390 if (pointsOnLine(waypoints[idx - 1], waypoints[idx + 1], r)) { 29391 29392 // remove point and increment offset 29393 segmentOffset = idx <= segmentStartIndex ? segmentOffset - 1 : segmentOffset; 29394 return false; 29395 } 29396 29397 // dont remove point 29398 return true; 29399 }); 29400 29401 return { 29402 waypoints: filteredWaypoints, 29403 segmentOffset: segmentOffset 29404 }; 29405 } 29406 29407 eventBus.on('connectionSegment.move.start', function(event) { 29408 29409 var context = event.context, 29410 connection = event.connection, 29411 layer = canvas.getLayer('overlays'); 29412 29413 context.originalWaypoints = connection.waypoints.slice(); 29414 29415 // add dragger gfx 29416 context.draggerGfx = addSegmentDragger(layer, context.segmentStart, context.segmentEnd); 29417 classes(context.draggerGfx).add('djs-dragging'); 29418 29419 canvas.addMarker(connection, MARKER_CONNECT_UPDATING); 29420 }); 29421 29422 eventBus.on('connectionSegment.move.move', function(event) { 29423 29424 var context = event.context, 29425 connection = context.connection, 29426 segmentStartIndex = context.segmentStartIndex, 29427 segmentEndIndex = context.segmentEndIndex, 29428 segmentStart = context.segmentStart, 29429 segmentEnd = context.segmentEnd, 29430 axis = context.axis; 29431 29432 var newWaypoints = context.originalWaypoints.slice(), 29433 newSegmentStart = axisAdd(segmentStart, axis, event['d' + axis]), 29434 newSegmentEnd = axisAdd(segmentEnd, axis, event['d' + axis]); 29435 29436 // original waypoint count and added / removed 29437 // from start waypoint delta. We use the later 29438 // to retrieve the updated segmentStartIndex / segmentEndIndex 29439 var waypointCount = newWaypoints.length, 29440 segmentOffset = 0; 29441 29442 // move segment start / end by axis delta 29443 newWaypoints[segmentStartIndex] = newSegmentStart; 29444 newWaypoints[segmentEndIndex] = newSegmentEnd; 29445 29446 var sourceToSegmentOrientation, 29447 targetToSegmentOrientation; 29448 29449 // handle first segment 29450 if (segmentStartIndex < 2) { 29451 sourceToSegmentOrientation = getOrientation(connection.source, newSegmentStart); 29452 29453 // first bendpoint, remove first segment if intersecting 29454 if (segmentStartIndex === 1) { 29455 29456 if (sourceToSegmentOrientation === 'intersect') { 29457 newWaypoints.shift(); 29458 newWaypoints[0] = newSegmentStart; 29459 segmentOffset--; 29460 } 29461 } 29462 29463 // docking point, add segment if not intersecting anymore 29464 else { 29465 if (sourceToSegmentOrientation !== 'intersect') { 29466 newWaypoints.unshift(segmentStart); 29467 segmentOffset++; 29468 } 29469 } 29470 } 29471 29472 // handle last segment 29473 if (segmentEndIndex > waypointCount - 3) { 29474 targetToSegmentOrientation = getOrientation(connection.target, newSegmentEnd); 29475 29476 // last bendpoint, remove last segment if intersecting 29477 if (segmentEndIndex === waypointCount - 2) { 29478 29479 if (targetToSegmentOrientation === 'intersect') { 29480 newWaypoints.pop(); 29481 newWaypoints[newWaypoints.length - 1] = newSegmentEnd; 29482 } 29483 } 29484 29485 // last bendpoint, remove last segment if intersecting 29486 else { 29487 if (targetToSegmentOrientation !== 'intersect') { 29488 newWaypoints.push(segmentEnd); 29489 } 29490 } 29491 } 29492 29493 // update connection waypoints 29494 context.newWaypoints = connection.waypoints = cropConnection(connection, newWaypoints); 29495 29496 // update dragger position 29497 updateDragger(context, segmentOffset, event); 29498 29499 // save segmentOffset in context 29500 context.newSegmentStartIndex = segmentStartIndex + segmentOffset; 29501 29502 // redraw connection 29503 redrawConnection(event); 29504 }); 29505 29506 eventBus.on('connectionSegment.move.hover', function(event) { 29507 29508 event.context.hover = event.hover; 29509 canvas.addMarker(event.hover, MARKER_CONNECT_HOVER); 29510 }); 29511 29512 eventBus.on([ 29513 'connectionSegment.move.out', 29514 'connectionSegment.move.cleanup' 29515 ], function(event) { 29516 29517 // remove connect marker 29518 // if it was added 29519 var hover = event.context.hover; 29520 29521 if (hover) { 29522 canvas.removeMarker(hover, MARKER_CONNECT_HOVER); 29523 } 29524 }); 29525 29526 eventBus.on('connectionSegment.move.cleanup', function(event) { 29527 29528 var context = event.context, 29529 connection = context.connection; 29530 29531 // remove dragger gfx 29532 if (context.draggerGfx) { 29533 remove$1(context.draggerGfx); 29534 } 29535 29536 canvas.removeMarker(connection, MARKER_CONNECT_UPDATING); 29537 }); 29538 29539 eventBus.on([ 29540 'connectionSegment.move.cancel', 29541 'connectionSegment.move.end' 29542 ], function(event) { 29543 var context = event.context, 29544 connection = context.connection; 29545 29546 connection.waypoints = context.originalWaypoints; 29547 29548 redrawConnection(event); 29549 }); 29550 29551 eventBus.on('connectionSegment.move.end', function(event) { 29552 29553 var context = event.context, 29554 connection = context.connection, 29555 newWaypoints = context.newWaypoints, 29556 newSegmentStartIndex = context.newSegmentStartIndex; 29557 29558 // ensure we have actual pixel values bendpoint 29559 // coordinates (important when zoom level was > 1 during move) 29560 newWaypoints = newWaypoints.map(function(p) { 29561 return { 29562 original: p.original, 29563 x: Math.round(p.x), 29564 y: Math.round(p.y) 29565 }; 29566 }); 29567 29568 // apply filter redunant waypoints 29569 var filtered = filterRedundantWaypoints(newWaypoints, newSegmentStartIndex); 29570 29571 // get filtered waypoints 29572 var filteredWaypoints = filtered.waypoints, 29573 croppedWaypoints = cropConnection(connection, filteredWaypoints), 29574 segmentOffset = filtered.segmentOffset; 29575 29576 var hints = { 29577 segmentMove: { 29578 segmentStartIndex: context.segmentStartIndex, 29579 newSegmentStartIndex: newSegmentStartIndex + segmentOffset 29580 } 29581 }; 29582 29583 modeling.updateWaypoints(connection, croppedWaypoints, hints); 29584 }); 29585 } 29586 29587 ConnectionSegmentMove.$inject = [ 29588 'injector', 29589 'eventBus', 29590 'canvas', 29591 'dragging', 29592 'graphicsFactory', 29593 'modeling' 29594 ]; 29595 29596 var abs$6 = Math.abs, 29597 round$7 = Math.round; 29598 29599 29600 /** 29601 * Snap value to a collection of reference values. 29602 * 29603 * @param {number} value 29604 * @param {Array<number>} values 29605 * @param {number} [tolerance=10] 29606 * 29607 * @return {number} the value we snapped to or null, if none snapped 29608 */ 29609 function snapTo(value, values, tolerance) { 29610 tolerance = tolerance === undefined ? 10 : tolerance; 29611 29612 var idx, snapValue; 29613 29614 for (idx = 0; idx < values.length; idx++) { 29615 snapValue = values[idx]; 29616 29617 if (abs$6(snapValue - value) <= tolerance) { 29618 return snapValue; 29619 } 29620 } 29621 } 29622 29623 29624 function topLeft(bounds) { 29625 return { 29626 x: bounds.x, 29627 y: bounds.y 29628 }; 29629 } 29630 29631 function bottomRight(bounds) { 29632 return { 29633 x: bounds.x + bounds.width, 29634 y: bounds.y + bounds.height 29635 }; 29636 } 29637 29638 function mid$2(bounds, defaultValue) { 29639 29640 if (!bounds || isNaN(bounds.x) || isNaN(bounds.y)) { 29641 return defaultValue; 29642 } 29643 29644 return { 29645 x: round$7(bounds.x + bounds.width / 2), 29646 y: round$7(bounds.y + bounds.height / 2) 29647 }; 29648 } 29649 29650 29651 /** 29652 * Retrieve the snap state of the given event. 29653 * 29654 * @param {Event} event 29655 * @param {string} axis 29656 * 29657 * @return {boolean} the snapped state 29658 * 29659 */ 29660 function isSnapped(event, axis) { 29661 var snapped = event.snapped; 29662 29663 if (!snapped) { 29664 return false; 29665 } 29666 29667 if (typeof axis === 'string') { 29668 return snapped[axis]; 29669 } 29670 29671 return snapped.x && snapped.y; 29672 } 29673 29674 29675 /** 29676 * Set the given event as snapped. 29677 * 29678 * This method may change the x and/or y position of the shape 29679 * from the given event! 29680 * 29681 * @param {Event} event 29682 * @param {string} axis 29683 * @param {number|boolean} value 29684 * 29685 * @return {number} old value 29686 */ 29687 function setSnapped(event, axis, value) { 29688 if (typeof axis !== 'string') { 29689 throw new Error('axis must be in [x, y]'); 29690 } 29691 29692 if (typeof value !== 'number' && value !== false) { 29693 throw new Error('value must be Number or false'); 29694 } 29695 29696 var delta, 29697 previousValue = event[axis]; 29698 29699 var snapped = event.snapped = (event.snapped || {}); 29700 29701 29702 if (value === false) { 29703 snapped[axis] = false; 29704 } else { 29705 snapped[axis] = true; 29706 29707 delta = value - previousValue; 29708 29709 event[axis] += delta; 29710 event['d' + axis] += delta; 29711 } 29712 29713 return previousValue; 29714 } 29715 29716 /** 29717 * Get children of a shape. 29718 * 29719 * @param {djs.model.Shape} parent 29720 * 29721 * @returns {Array<djs.model.Shape|djs.model.Connection>} 29722 */ 29723 function getChildren(parent) { 29724 return parent.children || []; 29725 } 29726 29727 var abs$5= Math.abs, 29728 round$6 = Math.round; 29729 29730 var TOLERANCE = 10; 29731 29732 29733 function BendpointSnapping(eventBus) { 29734 29735 function snapTo(values, value) { 29736 29737 if (isArray$2(values)) { 29738 var i = values.length; 29739 29740 while (i--) if (abs$5(values[i] - value) <= TOLERANCE) { 29741 return values[i]; 29742 } 29743 } else { 29744 values = +values; 29745 var rem = value % values; 29746 29747 if (rem < TOLERANCE) { 29748 return value - rem; 29749 } 29750 29751 if (rem > values - TOLERANCE) { 29752 return value - rem + values; 29753 } 29754 } 29755 29756 return value; 29757 } 29758 29759 function mid(element) { 29760 if (element.width) { 29761 return { 29762 x: round$6(element.width / 2 + element.x), 29763 y: round$6(element.height / 2 + element.y) 29764 }; 29765 } 29766 } 29767 29768 // connection segment snapping ////////////////////// 29769 29770 function getConnectionSegmentSnaps(context) { 29771 29772 var snapPoints = context.snapPoints, 29773 connection = context.connection, 29774 waypoints = connection.waypoints, 29775 segmentStart = context.segmentStart, 29776 segmentStartIndex = context.segmentStartIndex, 29777 segmentEnd = context.segmentEnd, 29778 segmentEndIndex = context.segmentEndIndex, 29779 axis = context.axis; 29780 29781 if (snapPoints) { 29782 return snapPoints; 29783 } 29784 29785 var referenceWaypoints = [ 29786 waypoints[segmentStartIndex - 1], 29787 segmentStart, 29788 segmentEnd, 29789 waypoints[segmentEndIndex + 1] 29790 ]; 29791 29792 if (segmentStartIndex < 2) { 29793 referenceWaypoints.unshift(mid(connection.source)); 29794 } 29795 29796 if (segmentEndIndex > waypoints.length - 3) { 29797 referenceWaypoints.unshift(mid(connection.target)); 29798 } 29799 29800 context.snapPoints = snapPoints = { horizontal: [] , vertical: [] }; 29801 29802 forEach(referenceWaypoints, function(p) { 29803 29804 // we snap on existing bendpoints only, 29805 // not placeholders that are inserted during add 29806 if (p) { 29807 p = p.original || p; 29808 29809 if (axis === 'y') { 29810 snapPoints.horizontal.push(p.y); 29811 } 29812 29813 if (axis === 'x') { 29814 snapPoints.vertical.push(p.x); 29815 } 29816 } 29817 }); 29818 29819 return snapPoints; 29820 } 29821 29822 eventBus.on('connectionSegment.move.move', 1500, function(event) { 29823 var context = event.context, 29824 snapPoints = getConnectionSegmentSnaps(context), 29825 x = event.x, 29826 y = event.y, 29827 sx, sy; 29828 29829 if (!snapPoints) { 29830 return; 29831 } 29832 29833 // snap 29834 sx = snapTo(snapPoints.vertical, x); 29835 sy = snapTo(snapPoints.horizontal, y); 29836 29837 29838 // correction x/y 29839 var cx = (x - sx), 29840 cy = (y - sy); 29841 29842 // update delta 29843 assign(event, { 29844 dx: event.dx - cx, 29845 dy: event.dy - cy, 29846 x: sx, 29847 y: sy 29848 }); 29849 29850 // only set snapped if actually snapped 29851 if (cx || snapPoints.vertical.indexOf(x) !== -1) { 29852 setSnapped(event, 'x', sx); 29853 } 29854 29855 if (cy || snapPoints.horizontal.indexOf(y) !== -1) { 29856 setSnapped(event, 'y', sy); 29857 } 29858 }); 29859 29860 29861 // bendpoint snapping ////////////////////// 29862 29863 function getBendpointSnaps(context) { 29864 29865 var snapPoints = context.snapPoints, 29866 waypoints = context.connection.waypoints, 29867 bendpointIndex = context.bendpointIndex; 29868 29869 if (snapPoints) { 29870 return snapPoints; 29871 } 29872 29873 var referenceWaypoints = [ waypoints[bendpointIndex - 1], waypoints[bendpointIndex + 1] ]; 29874 29875 context.snapPoints = snapPoints = { horizontal: [] , vertical: [] }; 29876 29877 forEach(referenceWaypoints, function(p) { 29878 29879 // we snap on existing bendpoints only, 29880 // not placeholders that are inserted during add 29881 if (p) { 29882 p = p.original || p; 29883 29884 snapPoints.horizontal.push(p.y); 29885 snapPoints.vertical.push(p.x); 29886 } 29887 }); 29888 29889 return snapPoints; 29890 } 29891 29892 29893 eventBus.on([ 'bendpoint.move.move', 'bendpoint.move.end' ], 1500, function(event) { 29894 29895 var context = event.context, 29896 snapPoints = getBendpointSnaps(context), 29897 hover = context.hover, 29898 hoverMid = hover && mid(hover), 29899 x = event.x, 29900 y = event.y, 29901 sx, sy; 29902 29903 if (!snapPoints) { 29904 return; 29905 } 29906 29907 // snap to hover mid 29908 sx = snapTo(hoverMid ? snapPoints.vertical.concat([ hoverMid.x ]) : snapPoints.vertical, x); 29909 sy = snapTo(hoverMid ? snapPoints.horizontal.concat([ hoverMid.y ]) : snapPoints.horizontal, y); 29910 29911 // correction x/y 29912 var cx = (x - sx), 29913 cy = (y - sy); 29914 29915 // update delta 29916 assign(event, { 29917 dx: event.dx - cx, 29918 dy: event.dy - cy, 29919 x: event.x - cx, 29920 y: event.y - cy 29921 }); 29922 29923 // only set snapped if actually snapped 29924 if (cx || snapPoints.vertical.indexOf(x) !== -1) { 29925 setSnapped(event, 'x', sx); 29926 } 29927 29928 if (cy || snapPoints.horizontal.indexOf(y) !== -1) { 29929 setSnapped(event, 'y', sy); 29930 } 29931 }); 29932 } 29933 29934 29935 BendpointSnapping.$inject = [ 'eventBus' ]; 29936 29937 var BendpointsModule = { 29938 __depends__: [ 29939 DraggingModule, 29940 RulesModule$1 29941 ], 29942 __init__: [ 'bendpoints', 'bendpointSnapping', 'bendpointMovePreview' ], 29943 bendpoints: [ 'type', Bendpoints ], 29944 bendpointMove: [ 'type', BendpointMove ], 29945 bendpointMovePreview: [ 'type', BendpointMovePreview ], 29946 connectionSegmentMove: [ 'type', ConnectionSegmentMove ], 29947 bendpointSnapping: [ 'type', BendpointSnapping ] 29948 }; 29949 29950 function Connect(eventBus, dragging, modeling, rules) { 29951 29952 // rules 29953 29954 function canConnect(source, target) { 29955 return rules.allowed('connection.create', { 29956 source: source, 29957 target: target 29958 }); 29959 } 29960 29961 function canConnectReverse(source, target) { 29962 return canConnect(target, source); 29963 } 29964 29965 29966 // event handlers 29967 29968 eventBus.on('connect.hover', function(event) { 29969 var context = event.context, 29970 start = context.start, 29971 hover = event.hover, 29972 canExecute; 29973 29974 // cache hover state 29975 context.hover = hover; 29976 29977 canExecute = context.canExecute = canConnect(start, hover); 29978 29979 // ignore hover 29980 if (isNil(canExecute)) { 29981 return; 29982 } 29983 29984 if (canExecute !== false) { 29985 context.source = start; 29986 context.target = hover; 29987 29988 return; 29989 } 29990 29991 canExecute = context.canExecute = canConnectReverse(start, hover); 29992 29993 // ignore hover 29994 if (isNil(canExecute)) { 29995 return; 29996 } 29997 29998 if (canExecute !== false) { 29999 context.source = hover; 30000 context.target = start; 30001 } 30002 }); 30003 30004 eventBus.on([ 'connect.out', 'connect.cleanup' ], function(event) { 30005 var context = event.context; 30006 30007 context.hover = null; 30008 context.source = null; 30009 context.target = null; 30010 30011 context.canExecute = false; 30012 }); 30013 30014 eventBus.on('connect.end', function(event) { 30015 var context = event.context, 30016 canExecute = context.canExecute, 30017 connectionStart = context.connectionStart, 30018 connectionEnd = { 30019 x: event.x, 30020 y: event.y 30021 }, 30022 source = context.source, 30023 target = context.target; 30024 30025 if (!canExecute) { 30026 return false; 30027 } 30028 30029 var attrs = null, 30030 hints = { 30031 connectionStart: isReverse$1(context) ? connectionEnd : connectionStart, 30032 connectionEnd: isReverse$1(context) ? connectionStart : connectionEnd 30033 }; 30034 30035 if (isObject(canExecute)) { 30036 attrs = canExecute; 30037 } 30038 30039 modeling.connect(source, target, attrs, hints); 30040 }); 30041 30042 30043 // API 30044 30045 /** 30046 * Start connect operation. 30047 * 30048 * @param {DOMEvent} event 30049 * @param {djs.model.Base} start 30050 * @param {Point} [connectionStart] 30051 * @param {boolean} [autoActivate=false] 30052 */ 30053 this.start = function(event, start, connectionStart, autoActivate) { 30054 if (!isObject(connectionStart)) { 30055 autoActivate = connectionStart; 30056 connectionStart = getMid(start); 30057 } 30058 30059 dragging.init(event, 'connect', { 30060 autoActivate: autoActivate, 30061 data: { 30062 shape: start, 30063 context: { 30064 start: start, 30065 connectionStart: connectionStart 30066 } 30067 } 30068 }); 30069 }; 30070 } 30071 30072 Connect.$inject = [ 30073 'eventBus', 30074 'dragging', 30075 'modeling', 30076 'rules' 30077 ]; 30078 30079 30080 // helpers ////////// 30081 30082 function isReverse$1(context) { 30083 var hover = context.hover, 30084 source = context.source, 30085 target = context.target; 30086 30087 return hover && source && hover === source && source !== target; 30088 } 30089 30090 var HIGH_PRIORITY$h = 1100, 30091 LOW_PRIORITY$h = 900; 30092 30093 var MARKER_OK$3 = 'connect-ok', 30094 MARKER_NOT_OK$3 = 'connect-not-ok'; 30095 30096 /** 30097 * Shows connection preview during connect. 30098 * 30099 * @param {didi.Injector} injector 30100 * @param {EventBus} eventBus 30101 * @param {Canvas} canvas 30102 */ 30103 function ConnectPreview(injector, eventBus, canvas) { 30104 var connectionPreview = injector.get('connectionPreview', false); 30105 30106 connectionPreview && eventBus.on('connect.move', function(event) { 30107 var context = event.context, 30108 canConnect = context.canExecute, 30109 hover = context.hover, 30110 source = context.source, 30111 start = context.start, 30112 startPosition = context.startPosition, 30113 target = context.target, 30114 connectionStart = context.connectionStart || startPosition, 30115 connectionEnd = context.connectionEnd || { 30116 x: event.x, 30117 y: event.y 30118 }, 30119 previewStart = connectionStart, 30120 previewEnd = connectionEnd; 30121 30122 if (isReverse$1(context)) { 30123 previewStart = connectionEnd; 30124 previewEnd = connectionStart; 30125 } 30126 30127 connectionPreview.drawPreview(context, canConnect, { 30128 source: source || start, 30129 target: target || hover, 30130 connectionStart: previewStart, 30131 connectionEnd: previewEnd 30132 }); 30133 }); 30134 30135 eventBus.on('connect.hover', LOW_PRIORITY$h, function(event) { 30136 var context = event.context, 30137 hover = event.hover, 30138 canExecute = context.canExecute; 30139 30140 // ignore hover 30141 if (canExecute === null) { 30142 return; 30143 } 30144 30145 canvas.addMarker(hover, canExecute ? MARKER_OK$3 : MARKER_NOT_OK$3); 30146 }); 30147 30148 eventBus.on([ 30149 'connect.out', 30150 'connect.cleanup' 30151 ], HIGH_PRIORITY$h, function(event) { 30152 var hover = event.hover; 30153 30154 if (hover) { 30155 canvas.removeMarker(hover, MARKER_OK$3); 30156 canvas.removeMarker(hover, MARKER_NOT_OK$3); 30157 } 30158 }); 30159 30160 connectionPreview && eventBus.on('connect.cleanup', function(event) { 30161 connectionPreview.cleanUp(event.context); 30162 }); 30163 } 30164 30165 ConnectPreview.$inject = [ 30166 'injector', 30167 'eventBus', 30168 'canvas' 30169 ]; 30170 30171 var ConnectModule = { 30172 __depends__: [ 30173 SelectionModule, 30174 RulesModule$1, 30175 DraggingModule 30176 ], 30177 __init__: [ 30178 'connectPreview' 30179 ], 30180 connect: [ 'type', Connect ], 30181 connectPreview: [ 'type', ConnectPreview ] 30182 }; 30183 30184 var MARKER_CONNECTION_PREVIEW = 'djs-connection-preview'; 30185 30186 /** 30187 * Draws connection preview. Optionally, this can use layouter and connection docking to draw 30188 * better looking previews. 30189 * 30190 * @param {didi.Injector} injector 30191 * @param {Canvas} canvas 30192 * @param {GraphicsFactory} graphicsFactory 30193 * @param {ElementFactory} elementFactory 30194 */ 30195 function ConnectionPreview( 30196 injector, 30197 canvas, 30198 graphicsFactory, 30199 elementFactory 30200 ) { 30201 this._canvas = canvas; 30202 this._graphicsFactory = graphicsFactory; 30203 this._elementFactory = elementFactory; 30204 30205 // optional components 30206 this._connectionDocking = injector.get('connectionDocking', false); 30207 this._layouter = injector.get('layouter', false); 30208 } 30209 30210 ConnectionPreview.$inject = [ 30211 'injector', 30212 'canvas', 30213 'graphicsFactory', 30214 'elementFactory' 30215 ]; 30216 30217 /** 30218 * Draw connection preview. 30219 * 30220 * Provide at least one of <source, connectionStart> and <target, connectionEnd> to create a preview. 30221 * In the clean up stage, call `connectionPreview#cleanUp` with the context to remove preview. 30222 * 30223 * @param {Object} context 30224 * @param {Object|boolean} canConnect 30225 * @param {Object} hints 30226 * @param {djs.model.shape} [hints.source] source element 30227 * @param {djs.model.shape} [hints.target] target element 30228 * @param {Point} [hints.connectionStart] connection preview start 30229 * @param {Point} [hints.connectionEnd] connection preview end 30230 * @param {Array<Point>} [hints.waypoints] provided waypoints for preview 30231 * @param {boolean} [hints.noLayout] true if preview should not be laid out 30232 * @param {boolean} [hints.noCropping] true if preview should not be cropped 30233 * @param {boolean} [hints.noNoop] true if simple connection should not be drawn 30234 */ 30235 ConnectionPreview.prototype.drawPreview = function(context, canConnect, hints) { 30236 30237 hints = hints || {}; 30238 30239 var connectionPreviewGfx = context.connectionPreviewGfx, 30240 getConnection = context.getConnection, 30241 source = hints.source, 30242 target = hints.target, 30243 waypoints = hints.waypoints, 30244 connectionStart = hints.connectionStart, 30245 connectionEnd = hints.connectionEnd, 30246 noLayout = hints.noLayout, 30247 noCropping = hints.noCropping, 30248 noNoop = hints.noNoop, 30249 connection; 30250 30251 var self = this; 30252 30253 if (!connectionPreviewGfx) { 30254 connectionPreviewGfx = context.connectionPreviewGfx = this.createConnectionPreviewGfx(); 30255 } 30256 30257 clear(connectionPreviewGfx); 30258 30259 if (!getConnection) { 30260 getConnection = context.getConnection = cacheReturnValues(function(canConnect, source, target) { 30261 return self.getConnection(canConnect, source, target); 30262 }); 30263 } 30264 30265 if (canConnect) { 30266 connection = getConnection(canConnect, source, target); 30267 } 30268 30269 if (!connection) { 30270 !noNoop && this.drawNoopPreview(connectionPreviewGfx, hints); 30271 return; 30272 } 30273 30274 connection.waypoints = waypoints || []; 30275 30276 // optional layout 30277 if (this._layouter && !noLayout) { 30278 connection.waypoints = this._layouter.layoutConnection(connection, { 30279 source: source, 30280 target: target, 30281 connectionStart: connectionStart, 30282 connectionEnd: connectionEnd, 30283 waypoints: hints.waypoints || connection.waypoints 30284 }); 30285 } 30286 30287 // fallback if no waypoints were provided nor created with layouter 30288 if (!connection.waypoints || !connection.waypoints.length) { 30289 connection.waypoints = [ 30290 source ? getMid(source) : connectionStart, 30291 target ? getMid(target) : connectionEnd 30292 ]; 30293 } 30294 30295 // optional cropping 30296 if (this._connectionDocking && (source || target) && !noCropping) { 30297 connection.waypoints = this._connectionDocking.getCroppedWaypoints(connection, source, target); 30298 } 30299 30300 this._graphicsFactory.drawConnection(connectionPreviewGfx, connection); 30301 }; 30302 30303 /** 30304 * Draw simple connection between source and target or provided points. 30305 * 30306 * @param {SVGElement} connectionPreviewGfx container for the connection 30307 * @param {Object} hints 30308 * @param {djs.model.shape} [hints.source] source element 30309 * @param {djs.model.shape} [hints.target] target element 30310 * @param {Point} [hints.connectionStart] required if source is not provided 30311 * @param {Point} [hints.connectionEnd] required if target is not provided 30312 */ 30313 ConnectionPreview.prototype.drawNoopPreview = function(connectionPreviewGfx, hints) { 30314 var source = hints.source, 30315 target = hints.target, 30316 start = hints.connectionStart || getMid(source), 30317 end = hints.connectionEnd || getMid(target); 30318 30319 var waypoints = this.cropWaypoints(start, end, source, target); 30320 30321 var connection = this.createNoopConnection(waypoints[0], waypoints[1]); 30322 30323 append(connectionPreviewGfx, connection); 30324 }; 30325 30326 /** 30327 * Return cropped waypoints. 30328 * 30329 * @param {Point} start 30330 * @param {Point} end 30331 * @param {djs.model.shape} source 30332 * @param {djs.model.shape} target 30333 * 30334 * @returns {Array} 30335 */ 30336 ConnectionPreview.prototype.cropWaypoints = function(start, end, source, target) { 30337 var graphicsFactory = this._graphicsFactory, 30338 sourcePath = source && graphicsFactory.getShapePath(source), 30339 targetPath = target && graphicsFactory.getShapePath(target), 30340 connectionPath = graphicsFactory.getConnectionPath({ waypoints: [ start, end ] }); 30341 30342 start = (source && getElementLineIntersection(sourcePath, connectionPath, true)) || start; 30343 end = (target && getElementLineIntersection(targetPath, connectionPath, false)) || end; 30344 30345 return [ start, end ]; 30346 }; 30347 30348 /** 30349 * Remove connection preview container if it exists. 30350 * 30351 * @param {Object} [context] 30352 * @param {SVGElement} [context.connectionPreviewGfx] preview container 30353 */ 30354 ConnectionPreview.prototype.cleanUp = function(context) { 30355 if (context && context.connectionPreviewGfx) { 30356 remove$1(context.connectionPreviewGfx); 30357 } 30358 }; 30359 30360 /** 30361 * Get connection that connects source and target. 30362 * 30363 * @param {Object|boolean} canConnect 30364 * 30365 * @returns {djs.model.connection} 30366 */ 30367 ConnectionPreview.prototype.getConnection = function(canConnect) { 30368 var attrs = ensureConnectionAttrs(canConnect); 30369 30370 return this._elementFactory.createConnection(attrs); 30371 }; 30372 30373 30374 /** 30375 * Add and return preview graphics. 30376 * 30377 * @returns {SVGElement} 30378 */ 30379 ConnectionPreview.prototype.createConnectionPreviewGfx = function() { 30380 var gfx = create$1('g'); 30381 30382 attr(gfx, { 30383 pointerEvents: 'none' 30384 }); 30385 30386 classes(gfx).add(MARKER_CONNECTION_PREVIEW); 30387 30388 append(this._canvas.getActiveLayer(), gfx); 30389 30390 return gfx; 30391 }; 30392 30393 /** 30394 * Create and return simple connection. 30395 * 30396 * @param {Point} start 30397 * @param {Point} end 30398 * 30399 * @returns {SVGElement} 30400 */ 30401 ConnectionPreview.prototype.createNoopConnection = function(start, end) { 30402 var connection = create$1('polyline'); 30403 30404 attr(connection, { 30405 'stroke': '#333', 30406 'strokeDasharray': [ 1 ], 30407 'strokeWidth': 2, 30408 'pointer-events': 'none' 30409 }); 30410 30411 attr(connection, { 'points': [ start.x, start.y, end.x, end.y ] }); 30412 30413 return connection; 30414 }; 30415 30416 // helpers ////////// 30417 30418 /** 30419 * Returns function that returns cached return values referenced by stringified first argument. 30420 * 30421 * @param {Function} fn 30422 * 30423 * @return {Function} 30424 */ 30425 function cacheReturnValues(fn) { 30426 var returnValues = {}; 30427 30428 /** 30429 * Return cached return value referenced by stringified first argument. 30430 * 30431 * @returns {*} 30432 */ 30433 return function(firstArgument) { 30434 var key = JSON.stringify(firstArgument); 30435 30436 var returnValue = returnValues[key]; 30437 30438 if (!returnValue) { 30439 returnValue = returnValues[key] = fn.apply(null, arguments); 30440 } 30441 30442 return returnValue; 30443 }; 30444 } 30445 30446 /** 30447 * Ensure connection attributes is object. 30448 * 30449 * @param {Object|boolean} canConnect 30450 * 30451 * @returns {Object} 30452 */ 30453 function ensureConnectionAttrs(canConnect) { 30454 if (isObject(canConnect)) { 30455 return canConnect; 30456 } else { 30457 return {}; 30458 } 30459 } 30460 30461 var ConnectionPreviewModule = { 30462 __init__: [ 'connectionPreview' ], 30463 connectionPreview: [ 'type', ConnectionPreview ] 30464 }; 30465 30466 var min$3 = Math.min, 30467 max$5 = Math.max; 30468 30469 function preventDefault(e) { 30470 e.preventDefault(); 30471 } 30472 30473 function stopPropagation(e) { 30474 e.stopPropagation(); 30475 } 30476 30477 function isTextNode(node) { 30478 return node.nodeType === Node.TEXT_NODE; 30479 } 30480 30481 function toArray(nodeList) { 30482 return [].slice.call(nodeList); 30483 } 30484 30485 /** 30486 * Initializes a container for a content editable div. 30487 * 30488 * Structure: 30489 * 30490 * container 30491 * parent 30492 * content 30493 * resize-handle 30494 * 30495 * @param {object} options 30496 * @param {DOMElement} options.container The DOM element to append the contentContainer to 30497 * @param {Function} options.keyHandler Handler for key events 30498 * @param {Function} options.resizeHandler Handler for resize events 30499 */ 30500 function TextBox(options) { 30501 this.container = options.container; 30502 30503 this.parent = domify( 30504 '<div class="djs-direct-editing-parent">' + 30505 '<div class="djs-direct-editing-content" contenteditable="true"></div>' + 30506 '</div>' 30507 ); 30508 30509 this.content = query('[contenteditable]', this.parent); 30510 30511 this.keyHandler = options.keyHandler || function() {}; 30512 this.resizeHandler = options.resizeHandler || function() {}; 30513 30514 this.autoResize = bind$2(this.autoResize, this); 30515 this.handlePaste = bind$2(this.handlePaste, this); 30516 } 30517 30518 30519 /** 30520 * Create a text box with the given position, size, style and text content 30521 * 30522 * @param {Object} bounds 30523 * @param {Number} bounds.x absolute x position 30524 * @param {Number} bounds.y absolute y position 30525 * @param {Number} [bounds.width] fixed width value 30526 * @param {Number} [bounds.height] fixed height value 30527 * @param {Number} [bounds.maxWidth] maximum width value 30528 * @param {Number} [bounds.maxHeight] maximum height value 30529 * @param {Number} [bounds.minWidth] minimum width value 30530 * @param {Number} [bounds.minHeight] minimum height value 30531 * @param {Object} [style] 30532 * @param {String} value text content 30533 * 30534 * @return {DOMElement} The created content DOM element 30535 */ 30536 TextBox.prototype.create = function(bounds, style, value, options) { 30537 var self = this; 30538 30539 var parent = this.parent, 30540 content = this.content, 30541 container = this.container; 30542 30543 options = this.options = options || {}; 30544 30545 style = this.style = style || {}; 30546 30547 var parentStyle = pick(style, [ 30548 'width', 30549 'height', 30550 'maxWidth', 30551 'maxHeight', 30552 'minWidth', 30553 'minHeight', 30554 'left', 30555 'top', 30556 'backgroundColor', 30557 'position', 30558 'overflow', 30559 'border', 30560 'wordWrap', 30561 'textAlign', 30562 'outline', 30563 'transform' 30564 ]); 30565 30566 assign(parent.style, { 30567 width: bounds.width + 'px', 30568 height: bounds.height + 'px', 30569 maxWidth: bounds.maxWidth + 'px', 30570 maxHeight: bounds.maxHeight + 'px', 30571 minWidth: bounds.minWidth + 'px', 30572 minHeight: bounds.minHeight + 'px', 30573 left: bounds.x + 'px', 30574 top: bounds.y + 'px', 30575 backgroundColor: '#ffffff', 30576 position: 'absolute', 30577 overflow: 'visible', 30578 border: '1px solid #ccc', 30579 boxSizing: 'border-box', 30580 wordWrap: 'normal', 30581 textAlign: 'center', 30582 outline: 'none' 30583 }, parentStyle); 30584 30585 var contentStyle = pick(style, [ 30586 'fontFamily', 30587 'fontSize', 30588 'fontWeight', 30589 'lineHeight', 30590 'padding', 30591 'paddingTop', 30592 'paddingRight', 30593 'paddingBottom', 30594 'paddingLeft' 30595 ]); 30596 30597 assign(content.style, { 30598 boxSizing: 'border-box', 30599 width: '100%', 30600 outline: 'none', 30601 wordWrap: 'break-word' 30602 }, contentStyle); 30603 30604 if (options.centerVertically) { 30605 assign(content.style, { 30606 position: 'absolute', 30607 top: '50%', 30608 transform: 'translate(0, -50%)' 30609 }, contentStyle); 30610 } 30611 30612 content.innerText = value; 30613 30614 componentEvent.bind(content, 'keydown', this.keyHandler); 30615 componentEvent.bind(content, 'mousedown', stopPropagation); 30616 componentEvent.bind(content, 'paste', self.handlePaste); 30617 30618 if (options.autoResize) { 30619 componentEvent.bind(content, 'input', this.autoResize); 30620 } 30621 30622 if (options.resizable) { 30623 this.resizable(style); 30624 } 30625 30626 container.appendChild(parent); 30627 30628 // set selection to end of text 30629 this.setSelection(content.lastChild, content.lastChild && content.lastChild.length); 30630 30631 return parent; 30632 }; 30633 30634 /** 30635 * Intercept paste events to remove formatting from pasted text. 30636 */ 30637 TextBox.prototype.handlePaste = function(e) { 30638 var options = this.options, 30639 style = this.style; 30640 30641 e.preventDefault(); 30642 30643 var text; 30644 30645 if (e.clipboardData) { 30646 30647 // Chrome, Firefox, Safari 30648 text = e.clipboardData.getData('text/plain'); 30649 } else { 30650 30651 // Internet Explorer 30652 text = window.clipboardData.getData('Text'); 30653 } 30654 30655 this.insertText(text); 30656 30657 if (options.autoResize) { 30658 var hasResized = this.autoResize(style); 30659 30660 if (hasResized) { 30661 this.resizeHandler(hasResized); 30662 } 30663 } 30664 }; 30665 30666 TextBox.prototype.insertText = function(text) { 30667 text = normalizeEndOfLineSequences(text); 30668 30669 // insertText command not supported by Internet Explorer 30670 var success = document.execCommand('insertText', false, text); 30671 30672 if (success) { 30673 return; 30674 } 30675 30676 this._insertTextIE(text); 30677 }; 30678 30679 TextBox.prototype._insertTextIE = function(text) { 30680 30681 // Internet Explorer 30682 var range = this.getSelection(), 30683 startContainer = range.startContainer, 30684 endContainer = range.endContainer, 30685 startOffset = range.startOffset, 30686 endOffset = range.endOffset, 30687 commonAncestorContainer = range.commonAncestorContainer; 30688 30689 var childNodesArray = toArray(commonAncestorContainer.childNodes); 30690 30691 var container, 30692 offset; 30693 30694 if (isTextNode(commonAncestorContainer)) { 30695 var containerTextContent = startContainer.textContent; 30696 30697 startContainer.textContent = 30698 containerTextContent.substring(0, startOffset) 30699 + text 30700 + containerTextContent.substring(endOffset); 30701 30702 container = startContainer; 30703 offset = startOffset + text.length; 30704 30705 } else if (startContainer === this.content && endContainer === this.content) { 30706 var textNode = document.createTextNode(text); 30707 30708 this.content.insertBefore(textNode, childNodesArray[startOffset]); 30709 30710 container = textNode; 30711 offset = textNode.textContent.length; 30712 } else { 30713 var startContainerChildIndex = childNodesArray.indexOf(startContainer), 30714 endContainerChildIndex = childNodesArray.indexOf(endContainer); 30715 30716 childNodesArray.forEach(function(childNode, index) { 30717 30718 if (index === startContainerChildIndex) { 30719 childNode.textContent = 30720 startContainer.textContent.substring(0, startOffset) + 30721 text + 30722 endContainer.textContent.substring(endOffset); 30723 } else if (index > startContainerChildIndex && index <= endContainerChildIndex) { 30724 remove$2(childNode); 30725 } 30726 }); 30727 30728 container = startContainer; 30729 offset = startOffset + text.length; 30730 } 30731 30732 if (container && offset !== undefined) { 30733 30734 // is necessary in Internet Explorer 30735 setTimeout(function() { 30736 self.setSelection(container, offset); 30737 }); 30738 } 30739 }; 30740 30741 /** 30742 * Automatically resize element vertically to fit its content. 30743 */ 30744 TextBox.prototype.autoResize = function() { 30745 var parent = this.parent, 30746 content = this.content; 30747 30748 var fontSize = parseInt(this.style.fontSize) || 12; 30749 30750 if (content.scrollHeight > parent.offsetHeight || 30751 content.scrollHeight < parent.offsetHeight - fontSize) { 30752 var bounds = parent.getBoundingClientRect(); 30753 30754 var height = content.scrollHeight; 30755 parent.style.height = height + 'px'; 30756 30757 this.resizeHandler({ 30758 width: bounds.width, 30759 height: bounds.height, 30760 dx: 0, 30761 dy: height - bounds.height 30762 }); 30763 } 30764 }; 30765 30766 /** 30767 * Make an element resizable by adding a resize handle. 30768 */ 30769 TextBox.prototype.resizable = function() { 30770 var self = this; 30771 30772 var parent = this.parent, 30773 resizeHandle = this.resizeHandle; 30774 30775 var minWidth = parseInt(this.style.minWidth) || 0, 30776 minHeight = parseInt(this.style.minHeight) || 0, 30777 maxWidth = parseInt(this.style.maxWidth) || Infinity, 30778 maxHeight = parseInt(this.style.maxHeight) || Infinity; 30779 30780 if (!resizeHandle) { 30781 resizeHandle = this.resizeHandle = domify( 30782 '<div class="djs-direct-editing-resize-handle"></div>' 30783 ); 30784 30785 var startX, startY, startWidth, startHeight; 30786 30787 var onMouseDown = function(e) { 30788 preventDefault(e); 30789 stopPropagation(e); 30790 30791 startX = e.clientX; 30792 startY = e.clientY; 30793 30794 var bounds = parent.getBoundingClientRect(); 30795 30796 startWidth = bounds.width; 30797 startHeight = bounds.height; 30798 30799 componentEvent.bind(document, 'mousemove', onMouseMove); 30800 componentEvent.bind(document, 'mouseup', onMouseUp); 30801 }; 30802 30803 var onMouseMove = function(e) { 30804 preventDefault(e); 30805 stopPropagation(e); 30806 30807 var newWidth = min$3(max$5(startWidth + e.clientX - startX, minWidth), maxWidth); 30808 var newHeight = min$3(max$5(startHeight + e.clientY - startY, minHeight), maxHeight); 30809 30810 parent.style.width = newWidth + 'px'; 30811 parent.style.height = newHeight + 'px'; 30812 30813 self.resizeHandler({ 30814 width: startWidth, 30815 height: startHeight, 30816 dx: e.clientX - startX, 30817 dy: e.clientY - startY 30818 }); 30819 }; 30820 30821 var onMouseUp = function(e) { 30822 preventDefault(e); 30823 stopPropagation(e); 30824 30825 componentEvent.unbind(document,'mousemove', onMouseMove, false); 30826 componentEvent.unbind(document, 'mouseup', onMouseUp, false); 30827 }; 30828 30829 componentEvent.bind(resizeHandle, 'mousedown', onMouseDown); 30830 } 30831 30832 assign(resizeHandle.style, { 30833 position: 'absolute', 30834 bottom: '0px', 30835 right: '0px', 30836 cursor: 'nwse-resize', 30837 width: '0', 30838 height: '0', 30839 borderTop: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent', 30840 borderRight: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc', 30841 borderBottom: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid #ccc', 30842 borderLeft: (parseInt(this.style.fontSize) / 4 || 3) + 'px solid transparent' 30843 }); 30844 30845 parent.appendChild(resizeHandle); 30846 }; 30847 30848 30849 /** 30850 * Clear content and style of the textbox, unbind listeners and 30851 * reset CSS style. 30852 */ 30853 TextBox.prototype.destroy = function() { 30854 var parent = this.parent, 30855 content = this.content, 30856 resizeHandle = this.resizeHandle; 30857 30858 // clear content 30859 content.innerText = ''; 30860 30861 // clear styles 30862 parent.removeAttribute('style'); 30863 content.removeAttribute('style'); 30864 30865 componentEvent.unbind(content, 'keydown', this.keyHandler); 30866 componentEvent.unbind(content, 'mousedown', stopPropagation); 30867 componentEvent.unbind(content, 'input', this.autoResize); 30868 componentEvent.unbind(content, 'paste', this.handlePaste); 30869 30870 if (resizeHandle) { 30871 resizeHandle.removeAttribute('style'); 30872 30873 remove$2(resizeHandle); 30874 } 30875 30876 remove$2(parent); 30877 }; 30878 30879 30880 TextBox.prototype.getValue = function() { 30881 return this.content.innerText.trim(); 30882 }; 30883 30884 30885 TextBox.prototype.getSelection = function() { 30886 var selection = window.getSelection(), 30887 range = selection.getRangeAt(0); 30888 30889 return range; 30890 }; 30891 30892 30893 TextBox.prototype.setSelection = function(container, offset) { 30894 var range = document.createRange(); 30895 30896 if (container === null) { 30897 range.selectNodeContents(this.content); 30898 } else { 30899 range.setStart(container, offset); 30900 range.setEnd(container, offset); 30901 } 30902 30903 var selection = window.getSelection(); 30904 30905 selection.removeAllRanges(); 30906 selection.addRange(range); 30907 }; 30908 30909 // helpers ////////// 30910 30911 function normalizeEndOfLineSequences(string) { 30912 return string.replace(/\r\n|\r|\n/g, '\n'); 30913 } 30914 30915 /** 30916 * A direct editing component that allows users 30917 * to edit an elements text directly in the diagram 30918 * 30919 * @param {EventBus} eventBus the event bus 30920 */ 30921 function DirectEditing(eventBus, canvas) { 30922 30923 this._eventBus = eventBus; 30924 30925 this._providers = []; 30926 this._textbox = new TextBox({ 30927 container: canvas.getContainer(), 30928 keyHandler: bind$2(this._handleKey, this), 30929 resizeHandler: bind$2(this._handleResize, this) 30930 }); 30931 } 30932 30933 DirectEditing.$inject = [ 'eventBus', 'canvas' ]; 30934 30935 30936 /** 30937 * Register a direct editing provider 30938 30939 * @param {Object} provider the provider, must expose an #activate(element) method that returns 30940 * an activation context ({ bounds: {x, y, width, height }, text }) if 30941 * direct editing is available for the given element. 30942 * Additionally the provider must expose a #update(element, value) method 30943 * to receive direct editing updates. 30944 */ 30945 DirectEditing.prototype.registerProvider = function(provider) { 30946 this._providers.push(provider); 30947 }; 30948 30949 30950 /** 30951 * Returns true if direct editing is currently active 30952 * 30953 * @return {Boolean} 30954 */ 30955 DirectEditing.prototype.isActive = function() { 30956 return !!this._active; 30957 }; 30958 30959 30960 /** 30961 * Cancel direct editing, if it is currently active 30962 */ 30963 DirectEditing.prototype.cancel = function() { 30964 if (!this._active) { 30965 return; 30966 } 30967 30968 this._fire('cancel'); 30969 this.close(); 30970 }; 30971 30972 30973 DirectEditing.prototype._fire = function(event, context) { 30974 this._eventBus.fire('directEditing.' + event, context || { active: this._active }); 30975 }; 30976 30977 DirectEditing.prototype.close = function() { 30978 this._textbox.destroy(); 30979 30980 this._fire('deactivate'); 30981 30982 this._active = null; 30983 30984 this.resizable = undefined; 30985 }; 30986 30987 30988 DirectEditing.prototype.complete = function() { 30989 30990 var active = this._active; 30991 30992 if (!active) { 30993 return; 30994 } 30995 30996 var containerBounds, 30997 previousBounds = active.context.bounds, 30998 newBounds = this.$textbox.getBoundingClientRect(), 30999 newText = this.getValue(), 31000 previousText = active.context.text; 31001 31002 if ( 31003 newText !== previousText || 31004 newBounds.height !== previousBounds.height || 31005 newBounds.width !== previousBounds.width 31006 ) { 31007 containerBounds = this._textbox.container.getBoundingClientRect(); 31008 31009 active.provider.update(active.element, newText, active.context.text, { 31010 x: newBounds.left - containerBounds.left, 31011 y: newBounds.top - containerBounds.top, 31012 width: newBounds.width, 31013 height: newBounds.height 31014 }); 31015 } 31016 31017 this._fire('complete'); 31018 31019 this.close(); 31020 }; 31021 31022 31023 DirectEditing.prototype.getValue = function() { 31024 return this._textbox.getValue(); 31025 }; 31026 31027 31028 DirectEditing.prototype._handleKey = function(e) { 31029 31030 // stop bubble 31031 e.stopPropagation(); 31032 31033 var key = e.keyCode || e.charCode; 31034 31035 // ESC 31036 if (key === 27) { 31037 e.preventDefault(); 31038 return this.cancel(); 31039 } 31040 31041 // Enter 31042 if (key === 13 && !e.shiftKey) { 31043 e.preventDefault(); 31044 return this.complete(); 31045 } 31046 }; 31047 31048 31049 DirectEditing.prototype._handleResize = function(event) { 31050 this._fire('resize', event); 31051 }; 31052 31053 31054 /** 31055 * Activate direct editing on the given element 31056 * 31057 * @param {Object} ElementDescriptor the descriptor for a shape or connection 31058 * @return {Boolean} true if the activation was possible 31059 */ 31060 DirectEditing.prototype.activate = function(element) { 31061 if (this.isActive()) { 31062 this.cancel(); 31063 } 31064 31065 // the direct editing context 31066 var context; 31067 31068 var provider = find(this._providers, function(p) { 31069 return (context = p.activate(element)) ? p : null; 31070 }); 31071 31072 // check if activation took place 31073 if (context) { 31074 this.$textbox = this._textbox.create( 31075 context.bounds, 31076 context.style, 31077 context.text, 31078 context.options 31079 ); 31080 31081 this._active = { 31082 element: element, 31083 context: context, 31084 provider: provider 31085 }; 31086 31087 if (context.options && context.options.resizable) { 31088 this.resizable = true; 31089 } 31090 31091 this._fire('activate'); 31092 } 31093 31094 return !!context; 31095 }; 31096 31097 var DirectEditingModule = { 31098 __depends__: [ 31099 InteractionEventsModule$1 31100 ], 31101 __init__: [ 'directEditing' ], 31102 directEditing: [ 'type', DirectEditing ] 31103 }; 31104 31105 var entrySelector = '.entry'; 31106 31107 var DEFAULT_PRIORITY$2 = 1000; 31108 31109 31110 /** 31111 * A context pad that displays element specific, contextual actions next 31112 * to a diagram element. 31113 * 31114 * @param {Object} config 31115 * @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }] 31116 * @param {number} [config.scale.min] 31117 * @param {number} [config.scale.max] 31118 * @param {EventBus} eventBus 31119 * @param {Overlays} overlays 31120 */ 31121 function ContextPad(config, eventBus, overlays) { 31122 31123 this._eventBus = eventBus; 31124 this._overlays = overlays; 31125 31126 var scale = isDefined(config && config.scale) ? config.scale : { 31127 min: 1, 31128 max: 1.5 31129 }; 31130 31131 this._overlaysConfig = { 31132 position: { 31133 right: -9, 31134 top: -6 31135 }, 31136 scale: scale 31137 }; 31138 31139 this._current = null; 31140 31141 this._init(); 31142 } 31143 31144 ContextPad.$inject = [ 31145 'config.contextPad', 31146 'eventBus', 31147 'overlays' 31148 ]; 31149 31150 31151 /** 31152 * Registers events needed for interaction with other components 31153 */ 31154 ContextPad.prototype._init = function() { 31155 31156 var eventBus = this._eventBus; 31157 31158 var self = this; 31159 31160 eventBus.on('selection.changed', function(e) { 31161 31162 var selection = e.newSelection; 31163 31164 if (selection.length === 1) { 31165 self.open(selection[0]); 31166 } else { 31167 self.close(); 31168 } 31169 }); 31170 31171 eventBus.on('elements.delete', function(event) { 31172 var elements = event.elements; 31173 31174 forEach(elements, function(e) { 31175 if (self.isOpen(e)) { 31176 self.close(); 31177 } 31178 }); 31179 }); 31180 31181 eventBus.on('element.changed', function(event) { 31182 var element = event.element, 31183 current = self._current; 31184 31185 // force reopen if element for which we are currently opened changed 31186 if (current && current.element === element) { 31187 self.open(element, true); 31188 } 31189 }); 31190 }; 31191 31192 31193 /** 31194 * Register a provider with the context pad 31195 * 31196 * @param {number} [priority=1000] 31197 * @param {ContextPadProvider} provider 31198 * 31199 * @example 31200 * const contextPadProvider = { 31201 * getContextPadEntries: function(element) { 31202 * return function(entries) { 31203 * return { 31204 * ...entries, 31205 * 'entry-1': { 31206 * label: 'My Entry', 31207 * action: function() { alert("I have been clicked!"); } 31208 * } 31209 * }; 31210 * } 31211 * } 31212 * }; 31213 * 31214 * contextPad.registerProvider(800, contextPadProvider); 31215 */ 31216 ContextPad.prototype.registerProvider = function(priority, provider) { 31217 if (!provider) { 31218 provider = priority; 31219 priority = DEFAULT_PRIORITY$2; 31220 } 31221 31222 this._eventBus.on('contextPad.getProviders', priority, function(event) { 31223 event.providers.push(provider); 31224 }); 31225 }; 31226 31227 31228 /** 31229 * Returns the context pad entries for a given element 31230 * 31231 * @param {djs.element.Base} element 31232 * 31233 * @return {Array<ContextPadEntryDescriptor>} list of entries 31234 */ 31235 ContextPad.prototype.getEntries = function(element) { 31236 var providers = this._getProviders(); 31237 31238 var entries = {}; 31239 31240 // loop through all providers and their entries. 31241 // group entries by id so that overriding an entry is possible 31242 forEach(providers, function(provider) { 31243 var entriesOrUpdater = provider.getContextPadEntries(element); 31244 31245 if (isFunction(entriesOrUpdater)) { 31246 entries = entriesOrUpdater(entries); 31247 } else { 31248 forEach(entriesOrUpdater, function(entry, id) { 31249 entries[id] = entry; 31250 }); 31251 } 31252 }); 31253 31254 return entries; 31255 }; 31256 31257 31258 /** 31259 * Trigger an action available on the opened context pad 31260 * 31261 * @param {string} action 31262 * @param {Event} event 31263 * @param {boolean} [autoActivate=false] 31264 */ 31265 ContextPad.prototype.trigger = function(action, event, autoActivate) { 31266 31267 var element = this._current.element, 31268 entries = this._current.entries, 31269 entry, 31270 handler, 31271 originalEvent, 31272 button = event.delegateTarget || event.target; 31273 31274 if (!button) { 31275 return event.preventDefault(); 31276 } 31277 31278 entry = entries[attr$1(button, 'data-action')]; 31279 handler = entry.action; 31280 31281 originalEvent = event.originalEvent || event; 31282 31283 // simple action (via callback function) 31284 if (isFunction(handler)) { 31285 if (action === 'click') { 31286 return handler(originalEvent, element, autoActivate); 31287 } 31288 } else { 31289 if (handler[action]) { 31290 return handler[action](originalEvent, element, autoActivate); 31291 } 31292 } 31293 31294 // silence other actions 31295 event.preventDefault(); 31296 }; 31297 31298 31299 /** 31300 * Open the context pad for the given element 31301 * 31302 * @param {djs.model.Base} element 31303 * @param {boolean} force if true, force reopening the context pad 31304 */ 31305 ContextPad.prototype.open = function(element, force) { 31306 if (!force && this.isOpen(element)) { 31307 return; 31308 } 31309 31310 this.close(); 31311 this._updateAndOpen(element); 31312 }; 31313 31314 ContextPad.prototype._getProviders = function() { 31315 31316 var event = this._eventBus.createEvent({ 31317 type: 'contextPad.getProviders', 31318 providers: [] 31319 }); 31320 31321 this._eventBus.fire(event); 31322 31323 return event.providers; 31324 }; 31325 31326 ContextPad.prototype._updateAndOpen = function(element) { 31327 31328 var entries = this.getEntries(element), 31329 pad = this.getPad(element), 31330 html = pad.html; 31331 31332 forEach(entries, function(entry, id) { 31333 var grouping = entry.group || 'default', 31334 control = domify(entry.html || '<div class="entry" draggable="true"></div>'), 31335 container; 31336 31337 attr$1(control, 'data-action', id); 31338 31339 container = query('[data-group=' + grouping + ']', html); 31340 if (!container) { 31341 container = domify('<div class="group" data-group="' + grouping + '"></div>'); 31342 html.appendChild(container); 31343 } 31344 31345 container.appendChild(control); 31346 31347 if (entry.className) { 31348 addClasses$1(control, entry.className); 31349 } 31350 31351 if (entry.title) { 31352 attr$1(control, 'title', entry.title); 31353 } 31354 31355 if (entry.imageUrl) { 31356 control.appendChild(domify('<img src="' + entry.imageUrl + '">')); 31357 } 31358 }); 31359 31360 classes$1(html).add('open'); 31361 31362 this._current = { 31363 element: element, 31364 pad: pad, 31365 entries: entries 31366 }; 31367 31368 this._eventBus.fire('contextPad.open', { current: this._current }); 31369 }; 31370 31371 31372 ContextPad.prototype.getPad = function(element) { 31373 if (this.isOpen()) { 31374 return this._current.pad; 31375 } 31376 31377 var self = this; 31378 31379 var overlays = this._overlays; 31380 31381 var html = domify('<div class="djs-context-pad"></div>'); 31382 31383 var overlaysConfig = assign({ 31384 html: html 31385 }, this._overlaysConfig); 31386 31387 delegate.bind(html, entrySelector, 'click', function(event) { 31388 self.trigger('click', event); 31389 }); 31390 31391 delegate.bind(html, entrySelector, 'dragstart', function(event) { 31392 self.trigger('dragstart', event); 31393 }); 31394 31395 // stop propagation of mouse events 31396 componentEvent.bind(html, 'mousedown', function(event) { 31397 event.stopPropagation(); 31398 }); 31399 31400 this._overlayId = overlays.add(element, 'context-pad', overlaysConfig); 31401 31402 var pad = overlays.get(this._overlayId); 31403 31404 this._eventBus.fire('contextPad.create', { element: element, pad: pad }); 31405 31406 return pad; 31407 }; 31408 31409 31410 /** 31411 * Close the context pad 31412 */ 31413 ContextPad.prototype.close = function() { 31414 if (!this.isOpen()) { 31415 return; 31416 } 31417 31418 this._overlays.remove(this._overlayId); 31419 31420 this._overlayId = null; 31421 31422 this._eventBus.fire('contextPad.close', { current: this._current }); 31423 31424 this._current = null; 31425 }; 31426 31427 /** 31428 * Check if pad is open. If element is given, will check 31429 * if pad is opened with given element. 31430 * 31431 * @param {Element} element 31432 * @return {boolean} 31433 */ 31434 ContextPad.prototype.isOpen = function(element) { 31435 return !!this._current && (!element ? true : this._current.element === element); 31436 }; 31437 31438 31439 31440 31441 // helpers ////////////////////// 31442 31443 function addClasses$1(element, classNames) { 31444 31445 var classes = classes$1(element); 31446 31447 var actualClassNames = isArray$2(classNames) ? classNames : classNames.split(/\s+/g); 31448 actualClassNames.forEach(function(cls) { 31449 classes.add(cls); 31450 }); 31451 } 31452 31453 var ContextPadModule$1 = { 31454 __depends__: [ 31455 InteractionEventsModule$1, 31456 OverlaysModule 31457 ], 31458 contextPad: [ 'type', ContextPad ] 31459 }; 31460 31461 var MARKER_TYPES = [ 31462 'marker-start', 31463 'marker-mid', 31464 'marker-end' 31465 ]; 31466 31467 var NODES_CAN_HAVE_MARKER = [ 31468 'circle', 31469 'ellipse', 31470 'line', 31471 'path', 31472 'polygon', 31473 'polyline', 31474 'rect' 31475 ]; 31476 31477 31478 /** 31479 * Adds support for previews of moving/resizing elements. 31480 */ 31481 function PreviewSupport(elementRegistry, eventBus, canvas, styles) { 31482 this._elementRegistry = elementRegistry; 31483 this._canvas = canvas; 31484 this._styles = styles; 31485 31486 this._clonedMarkers = {}; 31487 31488 var self = this; 31489 31490 eventBus.on('drag.cleanup', function() { 31491 forEach(self._clonedMarkers, function(clonedMarker) { 31492 remove$1(clonedMarker); 31493 }); 31494 31495 self._clonedMarkers = {}; 31496 }); 31497 } 31498 31499 PreviewSupport.$inject = [ 31500 'elementRegistry', 31501 'eventBus', 31502 'canvas', 31503 'styles' 31504 ]; 31505 31506 31507 /** 31508 * Returns graphics of an element. 31509 * 31510 * @param {djs.model.Base} element 31511 * 31512 * @return {SVGElement} 31513 */ 31514 PreviewSupport.prototype.getGfx = function(element) { 31515 return this._elementRegistry.getGraphics(element); 31516 }; 31517 31518 /** 31519 * Adds a move preview of a given shape to a given svg group. 31520 * 31521 * @param {djs.model.Base} element 31522 * @param {SVGElement} group 31523 * @param {SVGElement} [gfx] 31524 * 31525 * @return {SVGElement} dragger 31526 */ 31527 PreviewSupport.prototype.addDragger = function(element, group, gfx) { 31528 gfx = gfx || this.getGfx(element); 31529 31530 var dragger = clone$1(gfx); 31531 var bbox = gfx.getBoundingClientRect(); 31532 31533 this._cloneMarkers(getVisual(dragger)); 31534 31535 attr(dragger, this._styles.cls('djs-dragger', [], { 31536 x: bbox.top, 31537 y: bbox.left 31538 })); 31539 31540 append(group, dragger); 31541 31542 return dragger; 31543 }; 31544 31545 /** 31546 * Adds a resize preview of a given shape to a given svg group. 31547 * 31548 * @param {djs.model.Base} element 31549 * @param {SVGElement} group 31550 * 31551 * @return {SVGElement} frame 31552 */ 31553 PreviewSupport.prototype.addFrame = function(shape, group) { 31554 31555 var frame = create$1('rect', { 31556 class: 'djs-resize-overlay', 31557 width: shape.width, 31558 height: shape.height, 31559 x: shape.x, 31560 y: shape.y 31561 }); 31562 31563 append(group, frame); 31564 31565 return frame; 31566 }; 31567 31568 /** 31569 * Clone all markers referenced by a node and its child nodes. 31570 * 31571 * @param {SVGElement} gfx 31572 */ 31573 PreviewSupport.prototype._cloneMarkers = function(gfx) { 31574 var self = this; 31575 31576 if (gfx.childNodes) { 31577 31578 // TODO: use forEach once we drop PhantomJS 31579 for (var i = 0; i < gfx.childNodes.length; i++) { 31580 31581 // recursively clone markers of child nodes 31582 self._cloneMarkers(gfx.childNodes[ i ]); 31583 } 31584 } 31585 31586 if (!canHaveMarker(gfx)) { 31587 return; 31588 } 31589 31590 MARKER_TYPES.forEach(function(markerType) { 31591 if (attr(gfx, markerType)) { 31592 var marker = getMarker(gfx, markerType, self._canvas.getContainer()); 31593 31594 self._cloneMarker(gfx, marker, markerType); 31595 } 31596 }); 31597 }; 31598 31599 /** 31600 * Clone marker referenced by an element. 31601 * 31602 * @param {SVGElement} gfx 31603 * @param {SVGElement} marker 31604 * @param {string} markerType 31605 */ 31606 PreviewSupport.prototype._cloneMarker = function(gfx, marker, markerType) { 31607 var markerId = marker.id; 31608 31609 var clonedMarker = this._clonedMarkers[ markerId ]; 31610 31611 if (!clonedMarker) { 31612 clonedMarker = clone$1(marker); 31613 31614 var clonedMarkerId = markerId + '-clone'; 31615 31616 clonedMarker.id = clonedMarkerId; 31617 31618 classes(clonedMarker) 31619 .add('djs-dragger') 31620 .add('djs-dragger-marker'); 31621 31622 this._clonedMarkers[ markerId ] = clonedMarker; 31623 31624 var defs = query('defs', this._canvas._svg); 31625 31626 if (!defs) { 31627 defs = create$1('defs'); 31628 31629 append(this._canvas._svg, defs); 31630 } 31631 31632 append(defs, clonedMarker); 31633 } 31634 31635 var reference = idToReference(this._clonedMarkers[ markerId ].id); 31636 31637 attr(gfx, markerType, reference); 31638 }; 31639 31640 // helpers ////////// 31641 31642 /** 31643 * Get marker of given type referenced by node. 31644 * 31645 * @param {Node} node 31646 * @param {string} markerType 31647 * @param {Node} [parentNode] 31648 * 31649 * @param {Node} 31650 */ 31651 function getMarker(node, markerType, parentNode) { 31652 var id = referenceToId(attr(node, markerType)); 31653 31654 return query('marker#' + id, parentNode || document); 31655 } 31656 31657 /** 31658 * Get ID of fragment within current document from its functional IRI reference. 31659 * References may use single or double quotes. 31660 * 31661 * @param {string} reference 31662 * 31663 * @returns {string} 31664 */ 31665 function referenceToId(reference) { 31666 return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1]; 31667 } 31668 31669 /** 31670 * Get functional IRI reference for given ID of fragment within current document. 31671 * 31672 * @param {string} id 31673 * 31674 * @returns {string} 31675 */ 31676 function idToReference(id) { 31677 return 'url(#' + id + ')'; 31678 } 31679 31680 /** 31681 * Check wether node type can have marker attributes. 31682 * 31683 * @param {Node} node 31684 * 31685 * @returns {boolean} 31686 */ 31687 function canHaveMarker(node) { 31688 return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1; 31689 } 31690 31691 var PreviewSupportModule = { 31692 __init__: [ 'previewSupport' ], 31693 previewSupport: [ 'type', PreviewSupport ] 31694 }; 31695 31696 var MARKER_OK$2 = 'drop-ok', 31697 MARKER_NOT_OK$2 = 'drop-not-ok', 31698 MARKER_ATTACH$2 = 'attach-ok', 31699 MARKER_NEW_PARENT$1 = 'new-parent'; 31700 31701 var PREFIX = 'create'; 31702 31703 var HIGH_PRIORITY$g = 2000; 31704 31705 31706 /** 31707 * Create new elements through drag and drop. 31708 * 31709 * @param {Canvas} canvas 31710 * @param {Dragging} dragging 31711 * @param {EventBus} eventBus 31712 * @param {Modeling} modeling 31713 * @param {Rules} rules 31714 */ 31715 function Create( 31716 canvas, 31717 dragging, 31718 eventBus, 31719 modeling, 31720 rules 31721 ) { 31722 31723 // rules ////////// 31724 31725 /** 31726 * Check wether elements can be created. 31727 * 31728 * @param {Array<djs.model.Base>} elements 31729 * @param {djs.model.Base} target 31730 * @param {Point} position 31731 * @param {djs.model.Base} [source] 31732 * 31733 * @returns {boolean|null|Object} 31734 */ 31735 function canCreate(elements, target, position, source, hints) { 31736 if (!target) { 31737 return false; 31738 } 31739 31740 // ignore child elements and external labels 31741 elements = filter(elements, function(element) { 31742 var labelTarget = element.labelTarget; 31743 31744 return !element.parent && !(isLabel$5(element) && elements.indexOf(labelTarget) !== -1); 31745 }); 31746 31747 var shape = find(elements, function(element) { 31748 return !isConnection$b(element); 31749 }); 31750 31751 var attach = false, 31752 connect = false, 31753 create = false; 31754 31755 // (1) attaching single shapes 31756 if (isSingleShape(elements)) { 31757 attach = rules.allowed('shape.attach', { 31758 position: position, 31759 shape: shape, 31760 target: target 31761 }); 31762 } 31763 31764 if (!attach) { 31765 31766 // (2) creating elements 31767 if (isSingleShape(elements)) { 31768 create = rules.allowed('shape.create', { 31769 position: position, 31770 shape: shape, 31771 source: source, 31772 target: target 31773 }); 31774 } else { 31775 create = rules.allowed('elements.create', { 31776 elements: elements, 31777 position: position, 31778 target: target 31779 }); 31780 } 31781 31782 } 31783 31784 var connectionTarget = hints.connectionTarget; 31785 31786 // (3) appending single shapes 31787 if (create || attach) { 31788 if (shape && source) { 31789 connect = rules.allowed('connection.create', { 31790 source: connectionTarget === source ? shape : source, 31791 target: connectionTarget === source ? source : shape, 31792 hints: { 31793 targetParent: target, 31794 targetAttach: attach 31795 } 31796 }); 31797 } 31798 31799 return { 31800 attach: attach, 31801 connect: connect 31802 }; 31803 } 31804 31805 // ignore wether or not elements can be created 31806 if (create === null || attach === null) { 31807 return null; 31808 } 31809 31810 return false; 31811 } 31812 31813 function setMarker(element, marker) { 31814 [ MARKER_ATTACH$2, MARKER_OK$2, MARKER_NOT_OK$2, MARKER_NEW_PARENT$1 ].forEach(function(m) { 31815 31816 if (m === marker) { 31817 canvas.addMarker(element, m); 31818 } else { 31819 canvas.removeMarker(element, m); 31820 } 31821 }); 31822 } 31823 31824 // event handling ////////// 31825 31826 eventBus.on([ 'create.move', 'create.hover' ], function(event) { 31827 var context = event.context, 31828 elements = context.elements, 31829 hover = event.hover, 31830 source = context.source, 31831 hints = context.hints || {}; 31832 31833 if (!hover) { 31834 context.canExecute = false; 31835 context.target = null; 31836 31837 return; 31838 } 31839 31840 ensureConstraints$2(event); 31841 31842 var position = { 31843 x: event.x, 31844 y: event.y 31845 }; 31846 31847 var canExecute = context.canExecute = hover && canCreate(elements, hover, position, source, hints); 31848 31849 if (hover && canExecute !== null) { 31850 context.target = hover; 31851 31852 if (canExecute && canExecute.attach) { 31853 setMarker(hover, MARKER_ATTACH$2); 31854 } else { 31855 setMarker(hover, canExecute ? MARKER_NEW_PARENT$1 : MARKER_NOT_OK$2); 31856 } 31857 } 31858 }); 31859 31860 eventBus.on([ 'create.end', 'create.out', 'create.cleanup' ], function(event) { 31861 var hover = event.hover; 31862 31863 if (hover) { 31864 setMarker(hover, null); 31865 } 31866 }); 31867 31868 eventBus.on('create.end', function(event) { 31869 var context = event.context, 31870 source = context.source, 31871 shape = context.shape, 31872 elements = context.elements, 31873 target = context.target, 31874 canExecute = context.canExecute, 31875 attach = canExecute && canExecute.attach, 31876 connect = canExecute && canExecute.connect, 31877 hints = context.hints || {}; 31878 31879 if (canExecute === false || !target) { 31880 return false; 31881 } 31882 31883 ensureConstraints$2(event); 31884 31885 var position = { 31886 x: event.x, 31887 y: event.y 31888 }; 31889 31890 if (connect) { 31891 shape = modeling.appendShape(source, shape, position, target, { 31892 attach: attach, 31893 connection: connect === true ? {} : connect, 31894 connectionTarget: hints.connectionTarget 31895 }); 31896 } else { 31897 elements = modeling.createElements(elements, position, target, assign({}, hints, { 31898 attach: attach 31899 })); 31900 31901 // update shape 31902 shape = find(elements, function(element) { 31903 return !isConnection$b(element); 31904 }); 31905 } 31906 31907 // update elements and shape 31908 assign(context, { 31909 elements: elements, 31910 shape: shape 31911 }); 31912 31913 assign(event, { 31914 elements: elements, 31915 shape: shape 31916 }); 31917 }); 31918 31919 function cancel() { 31920 var context = dragging.context(); 31921 31922 if (context && context.prefix === PREFIX) { 31923 dragging.cancel(); 31924 } 31925 } 31926 31927 // cancel on <elements.changed> that is not result of <drag.end> 31928 eventBus.on('create.init', function() { 31929 eventBus.on('elements.changed', cancel); 31930 31931 eventBus.once([ 'create.cancel', 'create.end' ], HIGH_PRIORITY$g, function() { 31932 eventBus.off('elements.changed', cancel); 31933 }); 31934 }); 31935 31936 // API ////////// 31937 31938 this.start = function(event, elements, context) { 31939 if (!isArray$2(elements)) { 31940 elements = [ elements ]; 31941 } 31942 31943 var shape = find(elements, function(element) { 31944 return !isConnection$b(element); 31945 }); 31946 31947 if (!shape) { 31948 31949 // at least one shape is required 31950 return; 31951 } 31952 31953 context = assign({ 31954 elements: elements, 31955 hints: {}, 31956 shape: shape 31957 }, context || {}); 31958 31959 // make sure each element has x and y 31960 forEach(elements, function(element) { 31961 if (!isNumber(element.x)) { 31962 element.x = 0; 31963 } 31964 31965 if (!isNumber(element.y)) { 31966 element.y = 0; 31967 } 31968 }); 31969 31970 var bbox = getBBox(elements); 31971 31972 // center elements around cursor 31973 forEach(elements, function(element) { 31974 if (isConnection$b(element)) { 31975 element.waypoints = map$1(element.waypoints, function(waypoint) { 31976 return { 31977 x: waypoint.x - bbox.x - bbox.width / 2, 31978 y: waypoint.y - bbox.y - bbox.height / 2 31979 }; 31980 }); 31981 } 31982 31983 assign(element, { 31984 x: element.x - bbox.x - bbox.width / 2, 31985 y: element.y - bbox.y - bbox.height / 2 31986 }); 31987 }); 31988 31989 dragging.init(event, PREFIX, { 31990 cursor: 'grabbing', 31991 autoActivate: true, 31992 data: { 31993 shape: shape, 31994 elements: elements, 31995 context: context 31996 } 31997 }); 31998 }; 31999 } 32000 32001 Create.$inject = [ 32002 'canvas', 32003 'dragging', 32004 'eventBus', 32005 'modeling', 32006 'rules' 32007 ]; 32008 32009 // helpers ////////// 32010 32011 function ensureConstraints$2(event) { 32012 var context = event.context, 32013 createConstraints = context.createConstraints; 32014 32015 if (!createConstraints) { 32016 return; 32017 } 32018 32019 if (createConstraints.left) { 32020 event.x = Math.max(event.x, createConstraints.left); 32021 } 32022 32023 if (createConstraints.right) { 32024 event.x = Math.min(event.x, createConstraints.right); 32025 } 32026 32027 if (createConstraints.top) { 32028 event.y = Math.max(event.y, createConstraints.top); 32029 } 32030 32031 if (createConstraints.bottom) { 32032 event.y = Math.min(event.y, createConstraints.bottom); 32033 } 32034 } 32035 32036 function isConnection$b(element) { 32037 return !!element.waypoints; 32038 } 32039 32040 function isSingleShape(elements) { 32041 return elements && elements.length === 1 && !isConnection$b(elements[0]); 32042 } 32043 32044 function isLabel$5(element) { 32045 return !!element.labelTarget; 32046 } 32047 32048 var LOW_PRIORITY$g = 750; 32049 32050 32051 function CreatePreview( 32052 canvas, 32053 eventBus, 32054 graphicsFactory, 32055 previewSupport, 32056 styles 32057 ) { 32058 function createDragGroup(elements) { 32059 var dragGroup = create$1('g'); 32060 32061 attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ])); 32062 32063 var childrenGfx = create$1('g'); 32064 32065 elements.forEach(function(element) { 32066 32067 // create graphics 32068 var gfx; 32069 32070 if (element.hidden) { 32071 return; 32072 } 32073 32074 if (element.waypoints) { 32075 gfx = graphicsFactory._createContainer('connection', childrenGfx); 32076 32077 graphicsFactory.drawConnection(getVisual(gfx), element); 32078 } else { 32079 gfx = graphicsFactory._createContainer('shape', childrenGfx); 32080 32081 graphicsFactory.drawShape(getVisual(gfx), element); 32082 32083 translate$2(gfx, element.x, element.y); 32084 } 32085 32086 // add preview 32087 previewSupport.addDragger(element, dragGroup, gfx); 32088 }); 32089 32090 return dragGroup; 32091 } 32092 32093 eventBus.on('create.move', LOW_PRIORITY$g, function(event) { 32094 32095 var hover = event.hover, 32096 context = event.context, 32097 elements = context.elements, 32098 dragGroup = context.dragGroup; 32099 32100 // lazily create previews 32101 if (!dragGroup) { 32102 dragGroup = context.dragGroup = createDragGroup(elements); 32103 } 32104 32105 var activeLayer; 32106 32107 if (hover) { 32108 if (!dragGroup.parentNode) { 32109 activeLayer = canvas.getActiveLayer(); 32110 32111 append(activeLayer, dragGroup); 32112 } 32113 32114 translate$2(dragGroup, event.x, event.y); 32115 } else { 32116 remove$1(dragGroup); 32117 } 32118 }); 32119 32120 eventBus.on('create.cleanup', function(event) { 32121 var context = event.context, 32122 dragGroup = context.dragGroup; 32123 32124 if (dragGroup) { 32125 remove$1(dragGroup); 32126 } 32127 }); 32128 } 32129 32130 CreatePreview.$inject = [ 32131 'canvas', 32132 'eventBus', 32133 'graphicsFactory', 32134 'previewSupport', 32135 'styles' 32136 ]; 32137 32138 var CreateModule = { 32139 __depends__: [ 32140 DraggingModule, 32141 PreviewSupportModule, 32142 RulesModule$1, 32143 SelectionModule 32144 ], 32145 __init__: [ 32146 'create', 32147 'createPreview' 32148 ], 32149 create: [ 'type', Create ], 32150 createPreview: [ 'type', CreatePreview ] 32151 }; 32152 32153 var DATA_REF = 'data-id'; 32154 32155 var CLOSE_EVENTS = [ 32156 'contextPad.close', 32157 'canvas.viewbox.changing', 32158 'commandStack.changed' 32159 ]; 32160 32161 var DEFAULT_PRIORITY$1 = 1000; 32162 32163 32164 /** 32165 * A popup menu that can be used to display a list of actions anywhere in the canvas. 32166 * 32167 * @param {Object} config 32168 * @param {boolean|Object} [config.scale={ min: 1.0, max: 1.5 }] 32169 * @param {number} [config.scale.min] 32170 * @param {number} [config.scale.max] 32171 * @param {EventBus} eventBus 32172 * @param {Canvas} canvas 32173 * 32174 * @class 32175 * @constructor 32176 */ 32177 function PopupMenu(config, eventBus, canvas) { 32178 32179 var scale = isDefined(config && config.scale) ? config.scale : { 32180 min: 1, 32181 max: 1.5 32182 }; 32183 32184 this._config = { 32185 scale: scale 32186 }; 32187 32188 this._eventBus = eventBus; 32189 this._canvas = canvas; 32190 this._providers = {}; 32191 this._current = {}; 32192 } 32193 32194 PopupMenu.$inject = [ 32195 'config.popupMenu', 32196 'eventBus', 32197 'canvas' 32198 ]; 32199 32200 /** 32201 * Registers a popup menu provider 32202 * 32203 * @param {string} id 32204 * @param {number} [priority=1000] 32205 * @param {Object} provider 32206 * 32207 * @example 32208 * const popupMenuProvider = { 32209 * getPopupMenuEntries: function(element) { 32210 * return { 32211 * 'entry-1': { 32212 * label: 'My Entry', 32213 * action: function() { alert("I have been clicked!"); } 32214 * } 32215 * } 32216 * } 32217 * }; 32218 * 32219 * popupMenu.registerProvider('myMenuID', popupMenuProvider); 32220 */ 32221 PopupMenu.prototype.registerProvider = function(id, priority, provider) { 32222 if (!provider) { 32223 provider = priority; 32224 priority = DEFAULT_PRIORITY$1; 32225 } 32226 32227 this._eventBus.on('popupMenu.getProviders.' + id, priority, function(event) { 32228 event.providers.push(provider); 32229 }); 32230 }; 32231 32232 /** 32233 * Determine if the popup menu has entries. 32234 * 32235 * @return {boolean} true if empty 32236 */ 32237 PopupMenu.prototype.isEmpty = function(element, providerId) { 32238 if (!element) { 32239 throw new Error('element parameter is missing'); 32240 } 32241 32242 if (!providerId) { 32243 throw new Error('providerId parameter is missing'); 32244 } 32245 32246 var providers = this._getProviders(providerId); 32247 32248 if (!providers) { 32249 return true; 32250 } 32251 32252 var entries = this._getEntries(element, providers), 32253 headerEntries = this._getHeaderEntries(element, providers); 32254 32255 var hasEntries = size(entries) > 0, 32256 hasHeaderEntries = headerEntries && size(headerEntries) > 0; 32257 32258 return !hasEntries && !hasHeaderEntries; 32259 }; 32260 32261 32262 /** 32263 * Create entries and open popup menu at given position 32264 * 32265 * @param {Object} element 32266 * @param {string} id provider id 32267 * @param {Object} position 32268 * 32269 * @return {Object} popup menu instance 32270 */ 32271 PopupMenu.prototype.open = function(element, id, position) { 32272 32273 var providers = this._getProviders(id); 32274 32275 if (!element) { 32276 throw new Error('Element is missing'); 32277 } 32278 32279 if (!providers || !providers.length) { 32280 throw new Error('No registered providers for: ' + id); 32281 } 32282 32283 if (!position) { 32284 throw new Error('the position argument is missing'); 32285 } 32286 32287 if (this.isOpen()) { 32288 this.close(); 32289 } 32290 32291 this._emit('open'); 32292 32293 var current = this._current = { 32294 className: id, 32295 element: element, 32296 position: position 32297 }; 32298 32299 var entries = this._getEntries(element, providers), 32300 headerEntries = this._getHeaderEntries(element, providers); 32301 32302 current.entries = assign({}, entries, headerEntries); 32303 32304 current.container = this._createContainer(); 32305 32306 if (size(headerEntries)) { 32307 current.container.appendChild( 32308 this._createEntries(headerEntries, 'djs-popup-header') 32309 ); 32310 } 32311 32312 if (size(entries)) { 32313 current.container.appendChild( 32314 this._createEntries(entries, 'djs-popup-body') 32315 ); 32316 } 32317 32318 var canvas = this._canvas, 32319 parent = canvas.getContainer(); 32320 32321 this._attachContainer(current.container, parent, position.cursor); 32322 this._bindAutoClose(); 32323 }; 32324 32325 32326 /** 32327 * Removes the popup menu and unbinds the event handlers. 32328 */ 32329 PopupMenu.prototype.close = function() { 32330 32331 if (!this.isOpen()) { 32332 return; 32333 } 32334 32335 this._emit('close'); 32336 32337 this._unbindAutoClose(); 32338 remove$2(this._current.container); 32339 this._current.container = null; 32340 }; 32341 32342 32343 /** 32344 * Determine if an open popup menu exist. 32345 * 32346 * @return {boolean} true if open 32347 */ 32348 PopupMenu.prototype.isOpen = function() { 32349 return !!this._current.container; 32350 }; 32351 32352 32353 /** 32354 * Trigger an action associated with an entry. 32355 * 32356 * @param {Object} event 32357 * 32358 * @return the result of the action callback, if any 32359 */ 32360 PopupMenu.prototype.trigger = function(event) { 32361 32362 // silence other actions 32363 event.preventDefault(); 32364 32365 var element = event.delegateTarget || event.target, 32366 entryId = attr$1(element, DATA_REF); 32367 32368 var entry = this._getEntry(entryId); 32369 32370 if (entry.action) { 32371 return entry.action.call(null, event, entry); 32372 } 32373 }; 32374 32375 PopupMenu.prototype._getProviders = function(id) { 32376 32377 var event = this._eventBus.createEvent({ 32378 type: 'popupMenu.getProviders.' + id, 32379 providers: [] 32380 }); 32381 32382 this._eventBus.fire(event); 32383 32384 return event.providers; 32385 }; 32386 32387 PopupMenu.prototype._getEntries = function(element, providers) { 32388 32389 var entries = {}; 32390 32391 forEach(providers, function(provider) { 32392 32393 // handle legacy method 32394 if (!provider.getPopupMenuEntries) { 32395 forEach(provider.getEntries(element), function(entry) { 32396 var id = entry.id; 32397 32398 if (!id) { 32399 throw new Error('every entry must have the id property set'); 32400 } 32401 32402 entries[id] = omit(entry, [ 'id' ]); 32403 }); 32404 32405 return; 32406 } 32407 32408 var entriesOrUpdater = provider.getPopupMenuEntries(element); 32409 32410 if (isFunction(entriesOrUpdater)) { 32411 entries = entriesOrUpdater(entries); 32412 } else { 32413 forEach(entriesOrUpdater, function(entry, id) { 32414 entries[id] = entry; 32415 }); 32416 } 32417 }); 32418 32419 return entries; 32420 }; 32421 32422 PopupMenu.prototype._getHeaderEntries = function(element, providers) { 32423 32424 var entries = {}; 32425 32426 forEach(providers, function(provider) { 32427 32428 // handle legacy method 32429 if (!provider.getPopupMenuHeaderEntries) { 32430 if (!provider.getHeaderEntries) { 32431 return; 32432 } 32433 32434 forEach(provider.getHeaderEntries(element), function(entry) { 32435 var id = entry.id; 32436 32437 if (!id) { 32438 throw new Error('every entry must have the id property set'); 32439 } 32440 32441 entries[id] = omit(entry, [ 'id' ]); 32442 }); 32443 32444 return; 32445 } 32446 32447 var entriesOrUpdater = provider.getPopupMenuHeaderEntries(element); 32448 32449 if (isFunction(entriesOrUpdater)) { 32450 entries = entriesOrUpdater(entries); 32451 } else { 32452 forEach(entriesOrUpdater, function(entry, id) { 32453 entries[id] = entry; 32454 }); 32455 } 32456 }); 32457 32458 return entries; 32459 32460 32461 }; 32462 32463 /** 32464 * Gets an entry instance (either entry or headerEntry) by id. 32465 * 32466 * @param {string} entryId 32467 * 32468 * @return {Object} entry instance 32469 */ 32470 PopupMenu.prototype._getEntry = function(entryId) { 32471 32472 var entry = this._current.entries[entryId]; 32473 32474 if (!entry) { 32475 throw new Error('entry not found'); 32476 } 32477 32478 return entry; 32479 }; 32480 32481 PopupMenu.prototype._emit = function(eventName) { 32482 this._eventBus.fire('popupMenu.' + eventName); 32483 }; 32484 32485 /** 32486 * Creates the popup menu container. 32487 * 32488 * @return {Object} a DOM container 32489 */ 32490 PopupMenu.prototype._createContainer = function() { 32491 var container = domify('<div class="djs-popup">'), 32492 position = this._current.position, 32493 className = this._current.className; 32494 32495 assign(container.style, { 32496 position: 'absolute', 32497 left: position.x + 'px', 32498 top: position.y + 'px', 32499 visibility: 'hidden' 32500 }); 32501 32502 classes$1(container).add(className); 32503 32504 return container; 32505 }; 32506 32507 32508 /** 32509 * Attaches the container to the DOM. 32510 * 32511 * @param {Object} container 32512 * @param {Object} parent 32513 */ 32514 PopupMenu.prototype._attachContainer = function(container, parent, cursor) { 32515 var self = this; 32516 32517 // Event handler 32518 delegate.bind(container, '.entry' ,'click', function(event) { 32519 self.trigger(event); 32520 }); 32521 32522 this._updateScale(container); 32523 32524 // Attach to DOM 32525 parent.appendChild(container); 32526 32527 if (cursor) { 32528 this._assureIsInbounds(container, cursor); 32529 } 32530 }; 32531 32532 32533 /** 32534 * Updates popup style.transform with respect to the config and zoom level. 32535 * 32536 * @method _updateScale 32537 * 32538 * @param {Object} container 32539 */ 32540 PopupMenu.prototype._updateScale = function(container) { 32541 var zoom = this._canvas.zoom(); 32542 32543 var scaleConfig = this._config.scale, 32544 minScale, 32545 maxScale, 32546 scale = zoom; 32547 32548 if (scaleConfig !== true) { 32549 32550 if (scaleConfig === false) { 32551 minScale = 1; 32552 maxScale = 1; 32553 } else { 32554 minScale = scaleConfig.min; 32555 maxScale = scaleConfig.max; 32556 } 32557 32558 if (isDefined(minScale) && zoom < minScale) { 32559 scale = minScale; 32560 } 32561 32562 if (isDefined(maxScale) && zoom > maxScale) { 32563 scale = maxScale; 32564 } 32565 32566 } 32567 32568 setTransform(container, 'scale(' + scale + ')'); 32569 }; 32570 32571 32572 /** 32573 * Make sure that the menu is always fully shown 32574 * 32575 * @method function 32576 * 32577 * @param {Object} container 32578 * @param {Position} cursor {x, y} 32579 */ 32580 PopupMenu.prototype._assureIsInbounds = function(container, cursor) { 32581 var canvas = this._canvas, 32582 clientRect = canvas._container.getBoundingClientRect(); 32583 32584 var containerX = container.offsetLeft, 32585 containerY = container.offsetTop, 32586 containerWidth = container.scrollWidth, 32587 containerHeight = container.scrollHeight, 32588 overAxis = {}, 32589 left, top; 32590 32591 var cursorPosition = { 32592 x: cursor.x - clientRect.left, 32593 y: cursor.y - clientRect.top 32594 }; 32595 32596 if (containerX + containerWidth > clientRect.width) { 32597 overAxis.x = true; 32598 } 32599 32600 if (containerY + containerHeight > clientRect.height) { 32601 overAxis.y = true; 32602 } 32603 32604 if (overAxis.x && overAxis.y) { 32605 left = cursorPosition.x - containerWidth + 'px'; 32606 top = cursorPosition.y - containerHeight + 'px'; 32607 } else if (overAxis.x) { 32608 left = cursorPosition.x - containerWidth + 'px'; 32609 top = cursorPosition.y + 'px'; 32610 } else if (overAxis.y && cursorPosition.y < containerHeight) { 32611 left = cursorPosition.x + 'px'; 32612 top = 10 + 'px'; 32613 } else if (overAxis.y) { 32614 left = cursorPosition.x + 'px'; 32615 top = cursorPosition.y - containerHeight + 'px'; 32616 } 32617 32618 assign(container.style, { left: left, top: top }, { visibility: 'visible', 'z-index': 1000 }); 32619 }; 32620 32621 32622 /** 32623 * Creates a list of entries and returns them as a DOM container. 32624 * 32625 * @param {Array<Object>} entries an array of entry objects 32626 * @param {string} className the class name of the entry container 32627 * 32628 * @return {Object} a DOM container 32629 */ 32630 PopupMenu.prototype._createEntries = function(entries, className) { 32631 32632 var entriesContainer = domify('<div>'), 32633 self = this; 32634 32635 classes$1(entriesContainer).add(className); 32636 32637 forEach(entries, function(entry, id) { 32638 var entryContainer = self._createEntry(entry, id); 32639 entriesContainer.appendChild(entryContainer); 32640 }); 32641 32642 return entriesContainer; 32643 }; 32644 32645 32646 /** 32647 * Creates a single entry and returns it as a DOM container. 32648 * 32649 * @param {Object} entry 32650 * 32651 * @return {Object} a DOM container 32652 */ 32653 PopupMenu.prototype._createEntry = function(entry, id) { 32654 32655 var entryContainer = domify('<div>'), 32656 entryClasses = classes$1(entryContainer); 32657 32658 entryClasses.add('entry'); 32659 32660 if (entry.className) { 32661 entry.className.split(' ').forEach(function(className) { 32662 entryClasses.add(className); 32663 }); 32664 } 32665 32666 attr$1(entryContainer, DATA_REF, id); 32667 32668 if (entry.label) { 32669 var label = domify('<span>'); 32670 label.textContent = entry.label; 32671 entryContainer.appendChild(label); 32672 } 32673 32674 if (entry.imageUrl) { 32675 entryContainer.appendChild(domify('<img src="' + entry.imageUrl + '" />')); 32676 } 32677 32678 if (entry.active === true) { 32679 entryClasses.add('active'); 32680 } 32681 32682 if (entry.disabled === true) { 32683 entryClasses.add('disabled'); 32684 } 32685 32686 if (entry.title) { 32687 entryContainer.title = entry.title; 32688 } 32689 32690 return entryContainer; 32691 }; 32692 32693 32694 /** 32695 * Set up listener to close popup automatically on certain events. 32696 */ 32697 PopupMenu.prototype._bindAutoClose = function() { 32698 this._eventBus.once(CLOSE_EVENTS, this.close, this); 32699 }; 32700 32701 32702 /** 32703 * Remove the auto-closing listener. 32704 */ 32705 PopupMenu.prototype._unbindAutoClose = function() { 32706 this._eventBus.off(CLOSE_EVENTS, this.close, this); 32707 }; 32708 32709 32710 32711 // helpers ///////////////////////////// 32712 32713 function setTransform(element, transform) { 32714 element.style['transform-origin'] = 'top left'; 32715 32716 [ '', '-ms-', '-webkit-' ].forEach(function(prefix) { 32717 element.style[prefix + 'transform'] = transform; 32718 }); 32719 } 32720 32721 var PopupMenuModule$1 = { 32722 __init__: [ 'popupMenu' ], 32723 popupMenu: [ 'type', PopupMenu ] 32724 }; 32725 32726 /** 32727 * A clip board stub 32728 */ 32729 function Clipboard() {} 32730 32731 32732 Clipboard.prototype.get = function() { 32733 return this._data; 32734 }; 32735 32736 Clipboard.prototype.set = function(data) { 32737 this._data = data; 32738 }; 32739 32740 Clipboard.prototype.clear = function() { 32741 var data = this._data; 32742 32743 delete this._data; 32744 32745 return data; 32746 }; 32747 32748 Clipboard.prototype.isEmpty = function() { 32749 return !this._data; 32750 }; 32751 32752 var ClipboardModule = { 32753 clipboard: [ 'type', Clipboard ] 32754 }; 32755 32756 function Mouse(eventBus) { 32757 var self = this; 32758 32759 this._lastMoveEvent = null; 32760 32761 function setLastMoveEvent(mousemoveEvent) { 32762 self._lastMoveEvent = mousemoveEvent; 32763 } 32764 32765 eventBus.on('canvas.init', function(context) { 32766 var svg = self._svg = context.svg; 32767 32768 svg.addEventListener('mousemove', setLastMoveEvent); 32769 }); 32770 32771 eventBus.on('canvas.destroy', function() { 32772 self._lastMouseEvent = null; 32773 32774 self._svg.removeEventListener('mousemove', setLastMoveEvent); 32775 }); 32776 } 32777 32778 Mouse.$inject = [ 'eventBus' ]; 32779 32780 Mouse.prototype.getLastMoveEvent = function() { 32781 return this._lastMoveEvent || createMoveEvent(0, 0); 32782 }; 32783 32784 // helpers ////////// 32785 32786 function createMoveEvent(x, y) { 32787 var event = document.createEvent('MouseEvent'); 32788 32789 var screenX = x, 32790 screenY = y, 32791 clientX = x, 32792 clientY = y; 32793 32794 if (event.initMouseEvent) { 32795 event.initMouseEvent( 32796 'mousemove', 32797 true, 32798 true, 32799 window, 32800 0, 32801 screenX, 32802 screenY, 32803 clientX, 32804 clientY, 32805 false, 32806 false, 32807 false, 32808 false, 32809 0, 32810 null 32811 ); 32812 } 32813 32814 return event; 32815 } 32816 32817 var MouseModule = { 32818 __init__: [ 'mouse' ], 32819 mouse: [ 'type', Mouse ] 32820 }; 32821 32822 /** 32823 * @typedef {Function} <copyPaste.canCopyElements> listener 32824 * 32825 * @param {Object} context 32826 * @param {Array<djs.model.Base>} context.elements 32827 * 32828 * @returns {Array<djs.model.Base>|boolean} - Return elements to be copied or false to disallow 32829 * copying. 32830 */ 32831 32832 /** 32833 * @typedef {Function} <copyPaste.copyElement> listener 32834 * 32835 * @param {Object} context 32836 * @param {Object} context.descriptor 32837 * @param {djs.model.Base} context.element 32838 * @param {Array<djs.model.Base>} context.elements 32839 */ 32840 32841 /** 32842 * @typedef {Function} <copyPaste.elementsCopied> listener 32843 * 32844 * @param {Object} context 32845 * @param {Object} context.elements 32846 * @param {Object} context.tree 32847 */ 32848 32849 /** 32850 * @typedef {Function} <copyPaste.pasteElement> listener 32851 * 32852 * @param {Object} context 32853 * @param {Object} context.cache - Already created elements. 32854 * @param {Object} context.descriptor 32855 */ 32856 32857 /** 32858 * @typedef {Function} <copyPaste.pasteElements> listener 32859 * 32860 * @param {Object} context 32861 * @param {Object} context.hints - Add hints before pasting. 32862 */ 32863 32864 /** 32865 * Copy and paste elements. 32866 * 32867 * @param {Canvas} canvas 32868 * @param {Create} create 32869 * @param {Clipboard} clipboard 32870 * @param {ElementFactory} elementFactory 32871 * @param {EventBus} eventBus 32872 * @param {Modeling} modeling 32873 * @param {Mouse} mouse 32874 * @param {Rules} rules 32875 */ 32876 function CopyPaste( 32877 canvas, 32878 create, 32879 clipboard, 32880 elementFactory, 32881 eventBus, 32882 modeling, 32883 mouse, 32884 rules 32885 ) { 32886 32887 this._canvas = canvas; 32888 this._create = create; 32889 this._clipboard = clipboard; 32890 this._elementFactory = elementFactory; 32891 this._eventBus = eventBus; 32892 this._modeling = modeling; 32893 this._mouse = mouse; 32894 this._rules = rules; 32895 32896 eventBus.on('copyPaste.copyElement', function(context) { 32897 var descriptor = context.descriptor, 32898 element = context.element, 32899 elements = context.elements; 32900 32901 // default priority (priority = 1) 32902 descriptor.priority = 1; 32903 32904 descriptor.id = element.id; 32905 32906 var parentCopied = find(elements, function(e) { 32907 return e === element.parent; 32908 }); 32909 32910 // do NOT reference parent if parent wasn't copied 32911 if (parentCopied) { 32912 descriptor.parent = element.parent.id; 32913 } 32914 32915 // attachers (priority = 2) 32916 if (isAttacher$1(element)) { 32917 descriptor.priority = 2; 32918 32919 descriptor.host = element.host.id; 32920 } 32921 32922 // connections (priority = 3) 32923 if (isConnection$a(element)) { 32924 descriptor.priority = 3; 32925 32926 descriptor.source = element.source.id; 32927 descriptor.target = element.target.id; 32928 32929 descriptor.waypoints = copyWaypoints$1(element); 32930 } 32931 32932 // labels (priority = 4) 32933 if (isLabel$4(element)) { 32934 descriptor.priority = 4; 32935 32936 descriptor.labelTarget = element.labelTarget.id; 32937 } 32938 32939 forEach([ 'x', 'y', 'width', 'height' ], function(property) { 32940 if (isNumber(element[ property ])) { 32941 descriptor[ property ] = element[ property ]; 32942 } 32943 }); 32944 32945 descriptor.hidden = element.hidden; 32946 descriptor.collapsed = element.collapsed; 32947 32948 }); 32949 32950 eventBus.on('copyPaste.pasteElements', function(context) { 32951 var hints = context.hints; 32952 32953 assign(hints, { 32954 createElementsBehavior: false 32955 }); 32956 }); 32957 } 32958 32959 CopyPaste.$inject = [ 32960 'canvas', 32961 'create', 32962 'clipboard', 32963 'elementFactory', 32964 'eventBus', 32965 'modeling', 32966 'mouse', 32967 'rules' 32968 ]; 32969 32970 32971 /** 32972 * Copy elements. 32973 * 32974 * @param {Array<djs.model.Base>} elements 32975 * 32976 * @returns {Object} 32977 */ 32978 CopyPaste.prototype.copy = function(elements) { 32979 var allowed, 32980 tree; 32981 32982 if (!isArray$2(elements)) { 32983 elements = elements ? [ elements ] : []; 32984 } 32985 32986 allowed = this._eventBus.fire('copyPaste.canCopyElements', { 32987 elements: elements 32988 }); 32989 32990 if (allowed === false) { 32991 tree = {}; 32992 } else { 32993 tree = this.createTree(isArray$2(allowed) ? allowed : elements); 32994 } 32995 32996 // we set an empty tree, selection of elements 32997 // to copy was empty. 32998 this._clipboard.set(tree); 32999 33000 this._eventBus.fire('copyPaste.elementsCopied', { 33001 elements: elements, 33002 tree: tree 33003 }); 33004 33005 return tree; 33006 }; 33007 33008 /** 33009 * Paste elements. 33010 * 33011 * @param {Object} [context] 33012 * @param {djs.model.base} [context.element] - Parent. 33013 * @param {Point} [context.point] - Position. 33014 * @param {Object} [context.hints] - Hints. 33015 */ 33016 CopyPaste.prototype.paste = function(context) { 33017 var tree = this._clipboard.get(); 33018 33019 if (this._clipboard.isEmpty()) { 33020 return; 33021 } 33022 33023 var hints = context && context.hints || {}; 33024 33025 this._eventBus.fire('copyPaste.pasteElements', { 33026 hints: hints 33027 }); 33028 33029 var elements = this._createElements(tree); 33030 33031 // paste directly 33032 if (context && context.element && context.point) { 33033 return this._paste(elements, context.element, context.point, hints); 33034 } 33035 33036 this._create.start(this._mouse.getLastMoveEvent(), elements, { 33037 hints: hints || {} 33038 }); 33039 }; 33040 33041 /** 33042 * Paste elements directly. 33043 * 33044 * @param {Array<djs.model.Base>} elements 33045 * @param {djs.model.base} target 33046 * @param {Point} position 33047 * @param {Object} [hints] 33048 */ 33049 CopyPaste.prototype._paste = function(elements, target, position, hints) { 33050 33051 // make sure each element has x and y 33052 forEach(elements, function(element) { 33053 if (!isNumber(element.x)) { 33054 element.x = 0; 33055 } 33056 33057 if (!isNumber(element.y)) { 33058 element.y = 0; 33059 } 33060 }); 33061 33062 var bbox = getBBox(elements); 33063 33064 // center elements around cursor 33065 forEach(elements, function(element) { 33066 if (isConnection$a(element)) { 33067 element.waypoints = map$1(element.waypoints, function(waypoint) { 33068 return { 33069 x: waypoint.x - bbox.x - bbox.width / 2, 33070 y: waypoint.y - bbox.y - bbox.height / 2 33071 }; 33072 }); 33073 } 33074 33075 assign(element, { 33076 x: element.x - bbox.x - bbox.width / 2, 33077 y: element.y - bbox.y - bbox.height / 2 33078 }); 33079 }); 33080 33081 return this._modeling.createElements(elements, position, target, assign({}, hints)); 33082 }; 33083 33084 /** 33085 * Create elements from tree. 33086 */ 33087 CopyPaste.prototype._createElements = function(tree) { 33088 var self = this; 33089 33090 var eventBus = this._eventBus; 33091 33092 var cache = {}; 33093 33094 var elements = []; 33095 33096 forEach(tree, function(branch, depth) { 33097 33098 // sort by priority 33099 branch = sortBy(branch, 'priority'); 33100 33101 forEach(branch, function(descriptor) { 33102 33103 // remove priority 33104 var attrs = assign({}, omit(descriptor, [ 'priority' ])); 33105 33106 if (cache[ descriptor.parent ]) { 33107 attrs.parent = cache[ descriptor.parent ]; 33108 } else { 33109 delete attrs.parent; 33110 } 33111 33112 eventBus.fire('copyPaste.pasteElement', { 33113 cache: cache, 33114 descriptor: attrs 33115 }); 33116 33117 var element; 33118 33119 if (isConnection$a(attrs)) { 33120 attrs.source = cache[ descriptor.source ]; 33121 attrs.target = cache[ descriptor.target ]; 33122 33123 element = cache[ descriptor.id ] = self.createConnection(attrs); 33124 33125 elements.push(element); 33126 33127 return; 33128 } 33129 33130 if (isLabel$4(attrs)) { 33131 attrs.labelTarget = cache[ attrs.labelTarget ]; 33132 33133 element = cache[ descriptor.id ] = self.createLabel(attrs); 33134 33135 elements.push(element); 33136 33137 return; 33138 } 33139 33140 if (attrs.host) { 33141 attrs.host = cache[ attrs.host ]; 33142 } 33143 33144 element = cache[ descriptor.id ] = self.createShape(attrs); 33145 33146 elements.push(element); 33147 }); 33148 33149 }); 33150 33151 return elements; 33152 }; 33153 33154 CopyPaste.prototype.createConnection = function(attrs) { 33155 var connection = this._elementFactory.createConnection(omit(attrs, [ 'id' ])); 33156 33157 return connection; 33158 }; 33159 33160 CopyPaste.prototype.createLabel = function(attrs) { 33161 var label = this._elementFactory.createLabel(omit(attrs, [ 'id' ])); 33162 33163 return label; 33164 }; 33165 33166 CopyPaste.prototype.createShape = function(attrs) { 33167 var shape = this._elementFactory.createShape(omit(attrs, [ 'id' ])); 33168 33169 return shape; 33170 }; 33171 33172 /** 33173 * Check wether element has relations to other elements e.g. attachers, labels and connections. 33174 * 33175 * @param {Object} element 33176 * @param {Array<djs.model.Base>} elements 33177 * 33178 * @returns {boolean} 33179 */ 33180 CopyPaste.prototype.hasRelations = function(element, elements) { 33181 var labelTarget, 33182 source, 33183 target; 33184 33185 if (isConnection$a(element)) { 33186 source = find(elements, matchPattern({ id: element.source.id })); 33187 target = find(elements, matchPattern({ id: element.target.id })); 33188 33189 if (!source || !target) { 33190 return false; 33191 } 33192 } 33193 33194 if (isLabel$4(element)) { 33195 labelTarget = find(elements, matchPattern({ id: element.labelTarget.id })); 33196 33197 if (!labelTarget) { 33198 return false; 33199 } 33200 } 33201 33202 return true; 33203 }; 33204 33205 /** 33206 * Create a tree-like structure from elements. 33207 * 33208 * @example 33209 * tree: { 33210 * 0: [ 33211 * { id: 'Shape_1', priority: 1, ... }, 33212 * { id: 'Shape_2', priority: 1, ... }, 33213 * { id: 'Connection_1', source: 'Shape_1', target: 'Shape_2', priority: 3, ... }, 33214 * ... 33215 * ], 33216 * 1: [ 33217 * { id: 'Shape_3', parent: 'Shape1', priority: 1, ... }, 33218 * ... 33219 * ] 33220 * }; 33221 * 33222 * @param {Array<djs.model.base>} elements 33223 * 33224 * @return {Object} 33225 */ 33226 CopyPaste.prototype.createTree = function(elements) { 33227 var rules = this._rules, 33228 self = this; 33229 33230 var tree = {}, 33231 elementsData = []; 33232 33233 var parents = getParents$1(elements); 33234 33235 function canCopy(element, elements) { 33236 return rules.allowed('element.copy', { 33237 element: element, 33238 elements: elements 33239 }); 33240 } 33241 33242 function addElementData(element, depth) { 33243 33244 // (1) check wether element has already been added 33245 var foundElementData = find(elementsData, function(elementsData) { 33246 return element === elementsData.element; 33247 }); 33248 33249 // (2) add element if not already added 33250 if (!foundElementData) { 33251 elementsData.push({ 33252 element: element, 33253 depth: depth 33254 }); 33255 33256 return; 33257 } 33258 33259 // (3) update depth 33260 if (foundElementData.depth < depth) { 33261 elementsData = removeElementData(foundElementData, elementsData); 33262 33263 elementsData.push({ 33264 element: foundElementData.element, 33265 depth: depth 33266 }); 33267 } 33268 } 33269 33270 function removeElementData(elementData, elementsData) { 33271 var index = elementsData.indexOf(elementData); 33272 33273 if (index !== -1) { 33274 elementsData.splice(index, 1); 33275 } 33276 33277 return elementsData; 33278 } 33279 33280 // (1) add elements 33281 eachElement(parents, function(element, _index, depth) { 33282 33283 // do NOT add external labels directly 33284 if (isLabel$4(element)) { 33285 return; 33286 } 33287 33288 // always copy external labels 33289 forEach(element.labels, function(label) { 33290 addElementData(label, depth); 33291 }); 33292 33293 function addRelatedElements(elements) { 33294 elements && elements.length && forEach(elements, function(element) { 33295 33296 // add external labels 33297 forEach(element.labels, function(label) { 33298 addElementData(label, depth); 33299 }); 33300 33301 addElementData(element, depth); 33302 }); 33303 } 33304 33305 forEach([ element.attachers, element.incoming, element.outgoing ], addRelatedElements); 33306 33307 addElementData(element, depth); 33308 33309 return element.children; 33310 }); 33311 33312 elements = map$1(elementsData, function(elementData) { 33313 return elementData.element; 33314 }); 33315 33316 // (2) copy elements 33317 elementsData = map$1(elementsData, function(elementData) { 33318 elementData.descriptor = {}; 33319 33320 self._eventBus.fire('copyPaste.copyElement', { 33321 descriptor: elementData.descriptor, 33322 element: elementData.element, 33323 elements: elements 33324 }); 33325 33326 return elementData; 33327 }); 33328 33329 // (3) sort elements by priority 33330 elementsData = sortBy(elementsData, function(elementData) { 33331 return elementData.descriptor.priority; 33332 }); 33333 33334 elements = map$1(elementsData, function(elementData) { 33335 return elementData.element; 33336 }); 33337 33338 // (4) create tree 33339 forEach(elementsData, function(elementData) { 33340 var depth = elementData.depth; 33341 33342 if (!self.hasRelations(elementData.element, elements)) { 33343 removeElement(elementData.element, elements); 33344 33345 return; 33346 } 33347 33348 if (!canCopy(elementData.element, elements)) { 33349 removeElement(elementData.element, elements); 33350 33351 return; 33352 } 33353 33354 if (!tree[depth]) { 33355 tree[depth] = []; 33356 } 33357 33358 tree[depth].push(elementData.descriptor); 33359 }); 33360 33361 return tree; 33362 }; 33363 33364 // helpers ////////// 33365 33366 function isAttacher$1(element) { 33367 return !!element.host; 33368 } 33369 33370 function isConnection$a(element) { 33371 return !!element.waypoints; 33372 } 33373 33374 function isLabel$4(element) { 33375 return !!element.labelTarget; 33376 } 33377 33378 function copyWaypoints$1(element) { 33379 return map$1(element.waypoints, function(waypoint) { 33380 33381 waypoint = copyWaypoint$1(waypoint); 33382 33383 if (waypoint.original) { 33384 waypoint.original = copyWaypoint$1(waypoint.original); 33385 } 33386 33387 return waypoint; 33388 }); 33389 } 33390 33391 function copyWaypoint$1(waypoint) { 33392 return assign({}, waypoint); 33393 } 33394 33395 function removeElement(element, elements) { 33396 var index = elements.indexOf(element); 33397 33398 if (index === -1) { 33399 return elements; 33400 } 33401 33402 return elements.splice(index, 1); 33403 } 33404 33405 var CopyPasteModule$1 = { 33406 __depends__: [ 33407 ClipboardModule, 33408 CreateModule, 33409 MouseModule, 33410 RulesModule$1 33411 ], 33412 __init__: [ 'copyPaste' ], 33413 copyPaste: [ 'type', CopyPaste ] 33414 }; 33415 33416 function copyProperties$1(source, target, properties) { 33417 if (!isArray$2(properties)) { 33418 properties = [ properties ]; 33419 } 33420 33421 forEach(properties, function(property) { 33422 if (!isUndefined$1(source[property])) { 33423 target[property] = source[property]; 33424 } 33425 }); 33426 } 33427 33428 function removeProperties(element, properties) { 33429 if (!isArray$2(properties)) { 33430 properties = [ properties ]; 33431 } 33432 33433 forEach(properties, function(property) { 33434 if (element[property]) { 33435 delete element[property]; 33436 } 33437 }); 33438 } 33439 33440 var LOW_PRIORITY$f = 750; 33441 33442 33443 function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) { 33444 33445 eventBus.on('copyPaste.copyElement', LOW_PRIORITY$f, function(context) { 33446 var descriptor = context.descriptor, 33447 element = context.element; 33448 33449 var businessObject = descriptor.oldBusinessObject = getBusinessObject(element); 33450 33451 descriptor.type = element.type; 33452 33453 copyProperties$1(businessObject, descriptor, 'name'); 33454 33455 descriptor.di = {}; 33456 33457 // colors will be set to DI 33458 copyProperties$1(businessObject.di, descriptor.di, [ 33459 'fill', 33460 'stroke', 33461 'background-color', 33462 'border-color', 33463 'color' 33464 ]); 33465 33466 copyProperties$1(businessObject.di, descriptor, 'isExpanded'); 33467 33468 if (isLabel$3(descriptor)) { 33469 return descriptor; 33470 } 33471 33472 // default sequence flow 33473 if (businessObject.default) { 33474 descriptor.default = businessObject.default.id; 33475 } 33476 }); 33477 33478 eventBus.on('moddleCopy.canCopyProperty', function(context) { 33479 var parent = context.parent, 33480 property = context.property, 33481 propertyName = context.propertyName, 33482 bpmnProcess; 33483 33484 if ( 33485 propertyName === 'processRef' && 33486 is$1(parent, 'bpmn:Participant') && 33487 is$1(property, 'bpmn:Process') 33488 ) { 33489 bpmnProcess = bpmnFactory.create('bpmn:Process'); 33490 33491 // return copy of process 33492 return moddleCopy.copyElement(property, bpmnProcess); 33493 } 33494 }); 33495 33496 var references; 33497 33498 function resolveReferences(descriptor, cache) { 33499 var businessObject = getBusinessObject(descriptor); 33500 33501 // default sequence flows 33502 if (descriptor.default) { 33503 33504 // relationship cannot be resolved immediately 33505 references[ descriptor.default ] = { 33506 element: businessObject, 33507 property: 'default' 33508 }; 33509 } 33510 33511 // boundary events 33512 if (descriptor.host) { 33513 33514 // relationship can be resolved immediately 33515 getBusinessObject(descriptor).attachedToRef = getBusinessObject(cache[ descriptor.host ]); 33516 } 33517 33518 references = omit(references, reduce(references, function(array, reference, key) { 33519 var element = reference.element, 33520 property = reference.property; 33521 33522 if (key === descriptor.id) { 33523 element[ property ] = businessObject; 33524 33525 array.push(descriptor.id); 33526 } 33527 33528 return array; 33529 }, [])); 33530 } 33531 33532 eventBus.on('copyPaste.pasteElements', function() { 33533 references = {}; 33534 }); 33535 33536 eventBus.on('copyPaste.pasteElement', function(context) { 33537 var cache = context.cache, 33538 descriptor = context.descriptor, 33539 oldBusinessObject = descriptor.oldBusinessObject, 33540 newBusinessObject; 33541 33542 // do NOT copy business object if external label 33543 if (isLabel$3(descriptor)) { 33544 descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]); 33545 33546 return; 33547 } 33548 33549 newBusinessObject = bpmnFactory.create(oldBusinessObject.$type); 33550 33551 descriptor.businessObject = moddleCopy.copyElement( 33552 oldBusinessObject, 33553 newBusinessObject 33554 ); 33555 33556 // resolve references e.g. default sequence flow 33557 resolveReferences(descriptor, cache); 33558 33559 copyProperties$1(descriptor, newBusinessObject, [ 33560 'isExpanded', 33561 'name' 33562 ]); 33563 33564 removeProperties(descriptor, 'oldBusinessObject'); 33565 }); 33566 33567 } 33568 33569 33570 BpmnCopyPaste.$inject = [ 33571 'bpmnFactory', 33572 'eventBus', 33573 'moddleCopy' 33574 ]; 33575 33576 // helpers ////////// 33577 33578 function isLabel$3(element) { 33579 return !!element.labelTarget; 33580 } 33581 33582 var DISALLOWED_PROPERTIES = [ 33583 'artifacts', 33584 'dataInputAssociations', 33585 'dataOutputAssociations', 33586 'default', 33587 'flowElements', 33588 'lanes', 33589 'incoming', 33590 'outgoing' 33591 ]; 33592 33593 /** 33594 * @typedef {Function} <moddleCopy.canCopyProperties> listener 33595 * 33596 * @param {Object} context 33597 * @param {Array<string>} context.propertyNames 33598 * @param {ModdleElement} context.sourceElement 33599 * @param {ModdleElement} context.targetElement 33600 * 33601 * @returns {Array<string>|boolean} - Return properties to be copied or false to disallow 33602 * copying. 33603 */ 33604 33605 /** 33606 * @typedef {Function} <moddleCopy.canCopyProperty> listener 33607 * 33608 * @param {Object} context 33609 * @param {ModdleElement} context.parent 33610 * @param {*} context.property 33611 * @param {string} context.propertyName 33612 * 33613 * @returns {*|boolean} - Return copied property or false to disallow 33614 * copying. 33615 */ 33616 33617 /** 33618 * @typedef {Function} <moddleCopy.canSetCopiedProperty> listener 33619 * 33620 * @param {Object} context 33621 * @param {ModdleElement} context.parent 33622 * @param {*} context.property 33623 * @param {string} context.propertyName 33624 * 33625 * @returns {boolean} - Return false to disallow 33626 * setting copied property. 33627 */ 33628 33629 /** 33630 * Utility for copying model properties from source element to target element. 33631 * 33632 * @param {EventBus} eventBus 33633 * @param {BpmnFactory} bpmnFactory 33634 * @param {BpmnModdle} moddle 33635 */ 33636 function ModdleCopy(eventBus, bpmnFactory, moddle) { 33637 this._bpmnFactory = bpmnFactory; 33638 this._eventBus = eventBus; 33639 this._moddle = moddle; 33640 33641 // copy extension elements last 33642 eventBus.on('moddleCopy.canCopyProperties', function(context) { 33643 var propertyNames = context.propertyNames; 33644 33645 if (!propertyNames || !propertyNames.length) { 33646 return; 33647 } 33648 33649 return sortBy(propertyNames, function(propertyName) { 33650 return propertyName === 'extensionElements'; 33651 }); 33652 }); 33653 33654 // default check whether property can be copied 33655 eventBus.on('moddleCopy.canCopyProperty', function(context) { 33656 var parent = context.parent, 33657 parentDescriptor = isObject(parent) && parent.$descriptor, 33658 propertyName = context.propertyName; 33659 33660 if (propertyName && DISALLOWED_PROPERTIES.indexOf(propertyName) !== -1) { 33661 33662 // disallow copying property 33663 return false; 33664 } 33665 33666 if (propertyName && 33667 parentDescriptor && 33668 !find(parentDescriptor.properties, matchPattern({ name: propertyName }))) { 33669 33670 // disallow copying property 33671 return false; 33672 } 33673 }); 33674 33675 // do NOT allow to copy empty extension elements 33676 eventBus.on('moddleCopy.canSetCopiedProperty', function(context) { 33677 var property = context.property; 33678 33679 if (is(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) { 33680 33681 // disallow setting copied property 33682 return false; 33683 } 33684 }); 33685 } 33686 33687 ModdleCopy.$inject = [ 33688 'eventBus', 33689 'bpmnFactory', 33690 'moddle' 33691 ]; 33692 33693 /** 33694 * Copy model properties of source element to target element. 33695 * 33696 * @param {ModdleElement} sourceElement 33697 * @param {ModdleElement} targetElement 33698 * @param {Array<string>} [propertyNames] 33699 * 33700 * @param {ModdleElement} 33701 */ 33702 ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames) { 33703 var self = this; 33704 33705 if (propertyNames && !isArray$2(propertyNames)) { 33706 propertyNames = [ propertyNames ]; 33707 } 33708 33709 propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor); 33710 33711 var canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', { 33712 propertyNames: propertyNames, 33713 sourceElement: sourceElement, 33714 targetElement: targetElement 33715 }); 33716 33717 if (canCopyProperties === false) { 33718 return targetElement; 33719 } 33720 33721 if (isArray$2(canCopyProperties)) { 33722 propertyNames = canCopyProperties; 33723 } 33724 33725 // copy properties 33726 forEach(propertyNames, function(propertyName) { 33727 var sourceProperty; 33728 33729 if (has(sourceElement, propertyName)) { 33730 sourceProperty = sourceElement.get(propertyName); 33731 } 33732 33733 var copiedProperty = self.copyProperty(sourceProperty, targetElement, propertyName); 33734 33735 var canSetProperty = self._eventBus.fire('moddleCopy.canSetCopiedProperty', { 33736 parent: targetElement, 33737 property: copiedProperty, 33738 propertyName: propertyName 33739 }); 33740 33741 if (canSetProperty === false) { 33742 return; 33743 } 33744 33745 if (isDefined(copiedProperty)) { 33746 targetElement.set(propertyName, copiedProperty); 33747 } 33748 }); 33749 33750 return targetElement; 33751 }; 33752 33753 /** 33754 * Copy model property. 33755 * 33756 * @param {*} property 33757 * @param {ModdleElement} parent 33758 * @param {string} propertyName 33759 * 33760 * @returns {*} 33761 */ 33762 ModdleCopy.prototype.copyProperty = function(property, parent, propertyName) { 33763 var self = this; 33764 33765 // allow others to copy property 33766 var copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', { 33767 parent: parent, 33768 property: property, 33769 propertyName: propertyName 33770 }); 33771 33772 // return if copying is NOT allowed 33773 if (copiedProperty === false) { 33774 return; 33775 } 33776 33777 if (copiedProperty) { 33778 if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) { 33779 copiedProperty.$parent = parent; 33780 } 33781 33782 return copiedProperty; 33783 } 33784 33785 var propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName); 33786 33787 // do NOT copy references 33788 if (propertyDescriptor.isReference) { 33789 return; 33790 } 33791 33792 // copy id 33793 if (propertyDescriptor.isId) { 33794 return this._copyId(property, parent); 33795 } 33796 33797 // copy arrays 33798 if (isArray$2(property)) { 33799 return reduce(property, function(childProperties, childProperty) { 33800 33801 // recursion 33802 copiedProperty = self.copyProperty(childProperty, parent, propertyName); 33803 33804 // copying might NOT be allowed 33805 if (copiedProperty) { 33806 copiedProperty.$parent = parent; 33807 33808 return childProperties.concat(copiedProperty); 33809 } 33810 33811 return childProperties; 33812 }, []); 33813 } 33814 33815 // copy model elements 33816 if (isObject(property) && property.$type) { 33817 if (this._moddle.getElementDescriptor(property).isGeneric) { 33818 return; 33819 } 33820 33821 copiedProperty = self._bpmnFactory.create(property.$type); 33822 33823 copiedProperty.$parent = parent; 33824 33825 // recursion 33826 copiedProperty = self.copyElement(property, copiedProperty); 33827 33828 return copiedProperty; 33829 } 33830 33831 // copy primitive properties 33832 return property; 33833 }; 33834 33835 ModdleCopy.prototype._copyId = function(id, element) { 33836 33837 // disallow if already taken 33838 if (this._moddle.ids.assigned(id)) { 33839 return; 33840 } else { 33841 33842 this._moddle.ids.claim(id, element); 33843 return id; 33844 } 33845 }; 33846 33847 // helpers ////////// 33848 33849 function getPropertyNames(descriptor, keepDefaultProperties) { 33850 return reduce(descriptor.properties, function(properties, property) { 33851 33852 if (keepDefaultProperties && property.default) { 33853 return properties; 33854 } 33855 33856 return properties.concat(property.name); 33857 }, []); 33858 } 33859 33860 function is(element, type) { 33861 return element && (typeof element.$instanceOf === 'function') && element.$instanceOf(type); 33862 } 33863 33864 var CopyPasteModule = { 33865 __depends__: [ 33866 CopyPasteModule$1 33867 ], 33868 __init__: [ 'bpmnCopyPaste', 'moddleCopy' ], 33869 bpmnCopyPaste: [ 'type', BpmnCopyPaste ], 33870 moddleCopy: [ 'type', ModdleCopy ] 33871 }; 33872 33873 var round$5 = Math.round; 33874 33875 /** 33876 * Service that allow replacing of elements. 33877 */ 33878 function Replace(modeling) { 33879 33880 this._modeling = modeling; 33881 } 33882 33883 Replace.$inject = [ 'modeling' ]; 33884 33885 /** 33886 * @param {Element} oldElement - Element to be replaced 33887 * @param {Object} newElementData - Containing information about the new element, 33888 * for example the new bounds and type. 33889 * @param {Object} options - Custom options that will be attached to the context. It can be used to inject data 33890 * that is needed in the command chain. For example it could be used in 33891 * eventbus.on('commandStack.shape.replace.postExecute') to change shape attributes after 33892 * shape creation. 33893 */ 33894 Replace.prototype.replaceElement = function(oldElement, newElementData, options) { 33895 33896 if (oldElement.waypoints) { 33897 33898 // TODO(nikku): we do not replace connections, yet 33899 return null; 33900 } 33901 33902 var modeling = this._modeling; 33903 33904 var width = newElementData.width || oldElement.width, 33905 height = newElementData.height || oldElement.height, 33906 x = newElementData.x || oldElement.x, 33907 y = newElementData.y || oldElement.y, 33908 centerX = round$5(x + width / 2), 33909 centerY = round$5(y + height / 2); 33910 33911 // modeling API requires center coordinates, 33912 // account for that when handling shape bounds 33913 33914 return modeling.replaceShape( 33915 oldElement, 33916 assign( 33917 {}, 33918 newElementData, 33919 { 33920 x: centerX, 33921 y: centerY, 33922 width: width, 33923 height: height 33924 } 33925 ), 33926 options 33927 ); 33928 }; 33929 33930 var ReplaceModule$1 = { 33931 __init__: [ 'replace' ], 33932 replace: [ 'type', Replace ] 33933 }; 33934 33935 function copyProperties(source, target, properties) { 33936 if (!isArray$2(properties)) { 33937 properties = [ properties ]; 33938 } 33939 33940 forEach(properties, function(property) { 33941 if (!isUndefined$1(source[property])) { 33942 target[property] = source[property]; 33943 } 33944 }); 33945 } 33946 33947 var CUSTOM_PROPERTIES = [ 33948 'cancelActivity', 33949 'instantiate', 33950 'eventGatewayType', 33951 'triggeredByEvent', 33952 'isInterrupting' 33953 ]; 33954 33955 33956 function toggeling(element, target) { 33957 33958 var oldCollapsed = ( 33959 element && has(element, 'collapsed') ? element.collapsed : !isExpanded(element) 33960 ); 33961 33962 var targetCollapsed; 33963 33964 if (target && (has(target, 'collapsed') || has(target, 'isExpanded'))) { 33965 33966 // property is explicitly set so use it 33967 targetCollapsed = ( 33968 has(target, 'collapsed') ? target.collapsed : !target.isExpanded 33969 ); 33970 } else { 33971 33972 // keep old state 33973 targetCollapsed = oldCollapsed; 33974 } 33975 33976 if (oldCollapsed !== targetCollapsed) { 33977 element.collapsed = oldCollapsed; 33978 return true; 33979 } 33980 33981 return false; 33982 } 33983 33984 33985 33986 /** 33987 * This module takes care of replacing BPMN elements 33988 */ 33989 function BpmnReplace( 33990 bpmnFactory, 33991 elementFactory, 33992 moddleCopy, 33993 modeling, 33994 replace, 33995 rules, 33996 selection 33997 ) { 33998 33999 /** 34000 * Prepares a new business object for the replacement element 34001 * and triggers the replace operation. 34002 * 34003 * @param {djs.model.Base} element 34004 * @param {Object} target 34005 * @param {Object} [hints] 34006 * 34007 * @return {djs.model.Base} the newly created element 34008 */ 34009 function replaceElement(element, target, hints) { 34010 34011 hints = hints || {}; 34012 34013 var type = target.type, 34014 oldBusinessObject = element.businessObject; 34015 34016 if (isSubProcess(oldBusinessObject)) { 34017 if (type === 'bpmn:SubProcess') { 34018 if (toggeling(element, target)) { 34019 34020 // expanding or collapsing process 34021 modeling.toggleCollapse(element); 34022 34023 return element; 34024 } 34025 } 34026 } 34027 34028 var newBusinessObject = bpmnFactory.create(type); 34029 34030 var newElement = { 34031 type: type, 34032 businessObject: newBusinessObject 34033 }; 34034 34035 var elementProps = getPropertyNames(oldBusinessObject.$descriptor), 34036 newElementProps = getPropertyNames(newBusinessObject.$descriptor, true), 34037 copyProps = intersection(elementProps, newElementProps); 34038 34039 // initialize special properties defined in target definition 34040 assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES)); 34041 34042 var properties = filter(copyProps, function(propertyName) { 34043 34044 // copying event definitions, unless we replace 34045 if (propertyName === 'eventDefinitions') { 34046 return hasEventDefinition$1(element, target.eventDefinitionType); 34047 } 34048 34049 // retain loop characteristics if the target element 34050 // is not an event sub process 34051 if (propertyName === 'loopCharacteristics') { 34052 return !isEventSubProcess(newBusinessObject); 34053 } 34054 34055 // so the applied properties from 'target' don't get lost 34056 if (has(newBusinessObject, propertyName)) { 34057 return false; 34058 } 34059 34060 if (propertyName === 'processRef' && target.isExpanded === false) { 34061 return false; 34062 } 34063 34064 if (propertyName === 'triggeredByEvent') { 34065 return false; 34066 } 34067 34068 return true; 34069 }); 34070 34071 newBusinessObject = moddleCopy.copyElement( 34072 oldBusinessObject, 34073 newBusinessObject, 34074 properties 34075 ); 34076 34077 // initialize custom BPMN extensions 34078 if (target.eventDefinitionType) { 34079 34080 // only initialize with new eventDefinition 34081 // if we did not set an event definition yet, 34082 // i.e. because we copied it 34083 if (!hasEventDefinition$1(newBusinessObject, target.eventDefinitionType)) { 34084 newElement.eventDefinitionType = target.eventDefinitionType; 34085 newElement.eventDefinitionAttrs = target.eventDefinitionAttrs; 34086 } 34087 } 34088 34089 if (is$1(oldBusinessObject, 'bpmn:Activity')) { 34090 34091 if (isSubProcess(oldBusinessObject)) { 34092 34093 // no toggeling, so keep old state 34094 newElement.isExpanded = isExpanded(oldBusinessObject); 34095 } 34096 34097 // else if property is explicitly set, use it 34098 else if (target && has(target, 'isExpanded')) { 34099 newElement.isExpanded = target.isExpanded; 34100 } 34101 34102 // TODO: need also to respect min/max Size 34103 // copy size, from an expanded subprocess to an expanded alternative subprocess 34104 // except bpmn:Task, because Task is always expanded 34105 if ((isExpanded(oldBusinessObject) && !is$1(oldBusinessObject, 'bpmn:Task')) && newElement.isExpanded) { 34106 newElement.width = element.width; 34107 newElement.height = element.height; 34108 } 34109 } 34110 34111 // remove children if not expanding sub process 34112 if (isSubProcess(oldBusinessObject) && !isSubProcess(newBusinessObject)) { 34113 hints.moveChildren = false; 34114 } 34115 34116 // transform collapsed/expanded pools 34117 if (is$1(oldBusinessObject, 'bpmn:Participant')) { 34118 34119 // create expanded pool 34120 if (target.isExpanded === true) { 34121 newBusinessObject.processRef = bpmnFactory.create('bpmn:Process'); 34122 } else { 34123 34124 // remove children when transforming to collapsed pool 34125 hints.moveChildren = false; 34126 } 34127 34128 // apply same width and default height 34129 newElement.width = element.width; 34130 newElement.height = elementFactory._getDefaultSize(newBusinessObject).height; 34131 } 34132 34133 if (!rules.allowed('shape.resize', { shape: newBusinessObject })) { 34134 newElement.height = elementFactory._getDefaultSize(newBusinessObject).height; 34135 newElement.width = elementFactory._getDefaultSize(newBusinessObject).width; 34136 } 34137 34138 newBusinessObject.name = oldBusinessObject.name; 34139 34140 // retain default flow's reference between inclusive <-> exclusive gateways and activities 34141 if ( 34142 isAny(oldBusinessObject, [ 34143 'bpmn:ExclusiveGateway', 34144 'bpmn:InclusiveGateway', 34145 'bpmn:Activity' 34146 ]) && 34147 isAny(newBusinessObject, [ 34148 'bpmn:ExclusiveGateway', 34149 'bpmn:InclusiveGateway', 34150 'bpmn:Activity' 34151 ]) 34152 ) { 34153 newBusinessObject.default = oldBusinessObject.default; 34154 } 34155 34156 if ( 34157 target.host && 34158 !is$1(oldBusinessObject, 'bpmn:BoundaryEvent') && 34159 is$1(newBusinessObject, 'bpmn:BoundaryEvent') 34160 ) { 34161 newElement.host = target.host; 34162 } 34163 34164 // The DataStoreReference element is 14px wider than the DataObjectReference element 34165 // This ensures that they stay centered on the x axis when replaced 34166 if ( 34167 newElement.type === 'bpmn:DataStoreReference' || 34168 newElement.type === 'bpmn:DataObjectReference' 34169 ) { 34170 newElement.x = element.x + (element.width - newElement.width) / 2; 34171 } 34172 34173 newElement.di = {}; 34174 34175 // colors will be set to DI 34176 copyProperties(oldBusinessObject.di, newElement.di, [ 34177 'fill', 34178 'stroke', 34179 'background-color', 34180 'border-color', 34181 'color' 34182 ]); 34183 34184 newElement = replace.replaceElement(element, newElement, hints); 34185 34186 if (hints.select !== false) { 34187 selection.select(newElement); 34188 } 34189 34190 return newElement; 34191 } 34192 34193 this.replaceElement = replaceElement; 34194 } 34195 34196 BpmnReplace.$inject = [ 34197 'bpmnFactory', 34198 'elementFactory', 34199 'moddleCopy', 34200 'modeling', 34201 'replace', 34202 'rules', 34203 'selection' 34204 ]; 34205 34206 34207 function isSubProcess(bo) { 34208 return is$1(bo, 'bpmn:SubProcess'); 34209 } 34210 34211 function hasEventDefinition$1(element, type) { 34212 34213 var bo = getBusinessObject(element); 34214 34215 return type && bo.get('eventDefinitions').some(function(definition) { 34216 return is$1(definition, type); 34217 }); 34218 } 34219 34220 /** 34221 * Compute intersection between two arrays. 34222 */ 34223 function intersection(a1, a2) { 34224 return a1.filter(function(el) { 34225 return a2.indexOf(el) !== -1; 34226 }); 34227 } 34228 34229 var ReplaceModule = { 34230 __depends__: [ 34231 CopyPasteModule, 34232 ReplaceModule$1, 34233 SelectionModule 34234 ], 34235 bpmnReplace: [ 'type', BpmnReplace ] 34236 }; 34237 34238 /** 34239 * Returns true, if an element is from a different type 34240 * than a target definition. Takes into account the type, 34241 * event definition type and triggeredByEvent property. 34242 * 34243 * @param {djs.model.Base} element 34244 * 34245 * @return {boolean} 34246 */ 34247 function isDifferentType(element) { 34248 34249 return function(entry) { 34250 var target = entry.target; 34251 34252 var businessObject = getBusinessObject(element), 34253 eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0]; 34254 34255 var isTypeEqual = businessObject.$type === target.type; 34256 34257 var isEventDefinitionEqual = ( 34258 (eventDefinition && eventDefinition.$type) === target.eventDefinitionType 34259 ); 34260 34261 var isTriggeredByEventEqual = ( 34262 businessObject.triggeredByEvent === target.triggeredByEvent 34263 ); 34264 34265 var isExpandedEqual = ( 34266 target.isExpanded === undefined || 34267 target.isExpanded === isExpanded(businessObject) 34268 ); 34269 34270 return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual || !isExpandedEqual; 34271 }; 34272 } 34273 34274 var START_EVENT = [ 34275 { 34276 label: 'Start Event', 34277 actionName: 'replace-with-none-start', 34278 className: 'bpmn-icon-start-event-none', 34279 target: { 34280 type: 'bpmn:StartEvent' 34281 } 34282 }, 34283 { 34284 label: 'Intermediate Throw Event', 34285 actionName: 'replace-with-none-intermediate-throwing', 34286 className: 'bpmn-icon-intermediate-event-none', 34287 target: { 34288 type: 'bpmn:IntermediateThrowEvent' 34289 } 34290 }, 34291 { 34292 label: 'End Event', 34293 actionName: 'replace-with-none-end', 34294 className: 'bpmn-icon-end-event-none', 34295 target: { 34296 type: 'bpmn:EndEvent' 34297 } 34298 }, 34299 { 34300 label: 'Message Start Event', 34301 actionName: 'replace-with-message-start', 34302 className: 'bpmn-icon-start-event-message', 34303 target: { 34304 type: 'bpmn:StartEvent', 34305 eventDefinitionType: 'bpmn:MessageEventDefinition' 34306 } 34307 }, 34308 { 34309 label: 'Timer Start Event', 34310 actionName: 'replace-with-timer-start', 34311 className: 'bpmn-icon-start-event-timer', 34312 target: { 34313 type: 'bpmn:StartEvent', 34314 eventDefinitionType: 'bpmn:TimerEventDefinition' 34315 } 34316 }, 34317 { 34318 label: 'Conditional Start Event', 34319 actionName: 'replace-with-conditional-start', 34320 className: 'bpmn-icon-start-event-condition', 34321 target: { 34322 type: 'bpmn:StartEvent', 34323 eventDefinitionType: 'bpmn:ConditionalEventDefinition' 34324 } 34325 }, 34326 { 34327 label: 'Signal Start Event', 34328 actionName: 'replace-with-signal-start', 34329 className: 'bpmn-icon-start-event-signal', 34330 target: { 34331 type: 'bpmn:StartEvent', 34332 eventDefinitionType: 'bpmn:SignalEventDefinition' 34333 } 34334 } 34335 ]; 34336 34337 var START_EVENT_SUB_PROCESS = [ 34338 { 34339 label: 'Start Event', 34340 actionName: 'replace-with-none-start', 34341 className: 'bpmn-icon-start-event-none', 34342 target: { 34343 type: 'bpmn:StartEvent' 34344 } 34345 }, 34346 { 34347 label: 'Intermediate Throw Event', 34348 actionName: 'replace-with-none-intermediate-throwing', 34349 className: 'bpmn-icon-intermediate-event-none', 34350 target: { 34351 type: 'bpmn:IntermediateThrowEvent' 34352 } 34353 }, 34354 { 34355 label: 'End Event', 34356 actionName: 'replace-with-none-end', 34357 className: 'bpmn-icon-end-event-none', 34358 target: { 34359 type: 'bpmn:EndEvent' 34360 } 34361 } 34362 ]; 34363 34364 var INTERMEDIATE_EVENT = [ 34365 { 34366 label: 'Start Event', 34367 actionName: 'replace-with-none-start', 34368 className: 'bpmn-icon-start-event-none', 34369 target: { 34370 type: 'bpmn:StartEvent' 34371 } 34372 }, 34373 { 34374 label: 'Intermediate Throw Event', 34375 actionName: 'replace-with-none-intermediate-throw', 34376 className: 'bpmn-icon-intermediate-event-none', 34377 target: { 34378 type: 'bpmn:IntermediateThrowEvent' 34379 } 34380 }, 34381 { 34382 label: 'End Event', 34383 actionName: 'replace-with-none-end', 34384 className: 'bpmn-icon-end-event-none', 34385 target: { 34386 type: 'bpmn:EndEvent' 34387 } 34388 }, 34389 { 34390 label: 'Message Intermediate Catch Event', 34391 actionName: 'replace-with-message-intermediate-catch', 34392 className: 'bpmn-icon-intermediate-event-catch-message', 34393 target: { 34394 type: 'bpmn:IntermediateCatchEvent', 34395 eventDefinitionType: 'bpmn:MessageEventDefinition' 34396 } 34397 }, 34398 { 34399 label: 'Message Intermediate Throw Event', 34400 actionName: 'replace-with-message-intermediate-throw', 34401 className: 'bpmn-icon-intermediate-event-throw-message', 34402 target: { 34403 type: 'bpmn:IntermediateThrowEvent', 34404 eventDefinitionType: 'bpmn:MessageEventDefinition' 34405 } 34406 }, 34407 { 34408 label: 'Timer Intermediate Catch Event', 34409 actionName: 'replace-with-timer-intermediate-catch', 34410 className: 'bpmn-icon-intermediate-event-catch-timer', 34411 target: { 34412 type: 'bpmn:IntermediateCatchEvent', 34413 eventDefinitionType: 'bpmn:TimerEventDefinition' 34414 } 34415 }, 34416 { 34417 label: 'Escalation Intermediate Throw Event', 34418 actionName: 'replace-with-escalation-intermediate-throw', 34419 className: 'bpmn-icon-intermediate-event-throw-escalation', 34420 target: { 34421 type: 'bpmn:IntermediateThrowEvent', 34422 eventDefinitionType: 'bpmn:EscalationEventDefinition' 34423 } 34424 }, 34425 { 34426 label: 'Conditional Intermediate Catch Event', 34427 actionName: 'replace-with-conditional-intermediate-catch', 34428 className: 'bpmn-icon-intermediate-event-catch-condition', 34429 target: { 34430 type: 'bpmn:IntermediateCatchEvent', 34431 eventDefinitionType: 'bpmn:ConditionalEventDefinition' 34432 } 34433 }, 34434 { 34435 label: 'Link Intermediate Catch Event', 34436 actionName: 'replace-with-link-intermediate-catch', 34437 className: 'bpmn-icon-intermediate-event-catch-link', 34438 target: { 34439 type: 'bpmn:IntermediateCatchEvent', 34440 eventDefinitionType: 'bpmn:LinkEventDefinition', 34441 eventDefinitionAttrs: { 34442 name: '' 34443 } 34444 } 34445 }, 34446 { 34447 label: 'Link Intermediate Throw Event', 34448 actionName: 'replace-with-link-intermediate-throw', 34449 className: 'bpmn-icon-intermediate-event-throw-link', 34450 target: { 34451 type: 'bpmn:IntermediateThrowEvent', 34452 eventDefinitionType: 'bpmn:LinkEventDefinition', 34453 eventDefinitionAttrs: { 34454 name: '' 34455 } 34456 } 34457 }, 34458 { 34459 label: 'Compensation Intermediate Throw Event', 34460 actionName: 'replace-with-compensation-intermediate-throw', 34461 className: 'bpmn-icon-intermediate-event-throw-compensation', 34462 target: { 34463 type: 'bpmn:IntermediateThrowEvent', 34464 eventDefinitionType: 'bpmn:CompensateEventDefinition' 34465 } 34466 }, 34467 { 34468 label: 'Signal Intermediate Catch Event', 34469 actionName: 'replace-with-signal-intermediate-catch', 34470 className: 'bpmn-icon-intermediate-event-catch-signal', 34471 target: { 34472 type: 'bpmn:IntermediateCatchEvent', 34473 eventDefinitionType: 'bpmn:SignalEventDefinition' 34474 } 34475 }, 34476 { 34477 label: 'Signal Intermediate Throw Event', 34478 actionName: 'replace-with-signal-intermediate-throw', 34479 className: 'bpmn-icon-intermediate-event-throw-signal', 34480 target: { 34481 type: 'bpmn:IntermediateThrowEvent', 34482 eventDefinitionType: 'bpmn:SignalEventDefinition' 34483 } 34484 } 34485 ]; 34486 34487 var END_EVENT = [ 34488 { 34489 label: 'Start Event', 34490 actionName: 'replace-with-none-start', 34491 className: 'bpmn-icon-start-event-none', 34492 target: { 34493 type: 'bpmn:StartEvent' 34494 } 34495 }, 34496 { 34497 label: 'Intermediate Throw Event', 34498 actionName: 'replace-with-none-intermediate-throw', 34499 className: 'bpmn-icon-intermediate-event-none', 34500 target: { 34501 type: 'bpmn:IntermediateThrowEvent' 34502 } 34503 }, 34504 { 34505 label: 'End Event', 34506 actionName: 'replace-with-none-end', 34507 className: 'bpmn-icon-end-event-none', 34508 target: { 34509 type: 'bpmn:EndEvent' 34510 } 34511 }, 34512 { 34513 label: 'Message End Event', 34514 actionName: 'replace-with-message-end', 34515 className: 'bpmn-icon-end-event-message', 34516 target: { 34517 type: 'bpmn:EndEvent', 34518 eventDefinitionType: 'bpmn:MessageEventDefinition' 34519 } 34520 }, 34521 { 34522 label: 'Escalation End Event', 34523 actionName: 'replace-with-escalation-end', 34524 className: 'bpmn-icon-end-event-escalation', 34525 target: { 34526 type: 'bpmn:EndEvent', 34527 eventDefinitionType: 'bpmn:EscalationEventDefinition' 34528 } 34529 }, 34530 { 34531 label: 'Error End Event', 34532 actionName: 'replace-with-error-end', 34533 className: 'bpmn-icon-end-event-error', 34534 target: { 34535 type: 'bpmn:EndEvent', 34536 eventDefinitionType: 'bpmn:ErrorEventDefinition' 34537 } 34538 }, 34539 { 34540 label: 'Cancel End Event', 34541 actionName: 'replace-with-cancel-end', 34542 className: 'bpmn-icon-end-event-cancel', 34543 target: { 34544 type: 'bpmn:EndEvent', 34545 eventDefinitionType: 'bpmn:CancelEventDefinition' 34546 } 34547 }, 34548 { 34549 label: 'Compensation End Event', 34550 actionName: 'replace-with-compensation-end', 34551 className: 'bpmn-icon-end-event-compensation', 34552 target: { 34553 type: 'bpmn:EndEvent', 34554 eventDefinitionType: 'bpmn:CompensateEventDefinition' 34555 } 34556 }, 34557 { 34558 label: 'Signal End Event', 34559 actionName: 'replace-with-signal-end', 34560 className: 'bpmn-icon-end-event-signal', 34561 target: { 34562 type: 'bpmn:EndEvent', 34563 eventDefinitionType: 'bpmn:SignalEventDefinition' 34564 } 34565 }, 34566 { 34567 label: 'Terminate End Event', 34568 actionName: 'replace-with-terminate-end', 34569 className: 'bpmn-icon-end-event-terminate', 34570 target: { 34571 type: 'bpmn:EndEvent', 34572 eventDefinitionType: 'bpmn:TerminateEventDefinition' 34573 } 34574 } 34575 ]; 34576 34577 var GATEWAY = [ 34578 { 34579 label: 'Exclusive Gateway', 34580 actionName: 'replace-with-exclusive-gateway', 34581 className: 'bpmn-icon-gateway-xor', 34582 target: { 34583 type: 'bpmn:ExclusiveGateway' 34584 } 34585 }, 34586 { 34587 label: 'Parallel Gateway', 34588 actionName: 'replace-with-parallel-gateway', 34589 className: 'bpmn-icon-gateway-parallel', 34590 target: { 34591 type: 'bpmn:ParallelGateway' 34592 } 34593 }, 34594 { 34595 label: 'Inclusive Gateway', 34596 actionName: 'replace-with-inclusive-gateway', 34597 className: 'bpmn-icon-gateway-or', 34598 target: { 34599 type: 'bpmn:InclusiveGateway' 34600 } 34601 }, 34602 { 34603 label: 'Complex Gateway', 34604 actionName: 'replace-with-complex-gateway', 34605 className: 'bpmn-icon-gateway-complex', 34606 target: { 34607 type: 'bpmn:ComplexGateway' 34608 } 34609 }, 34610 { 34611 label: 'Event based Gateway', 34612 actionName: 'replace-with-event-based-gateway', 34613 className: 'bpmn-icon-gateway-eventbased', 34614 target: { 34615 type: 'bpmn:EventBasedGateway', 34616 instantiate: false, 34617 eventGatewayType: 'Exclusive' 34618 } 34619 } 34620 34621 // Gateways deactivated until https://github.com/bpmn-io/bpmn-js/issues/194 34622 // { 34623 // label: 'Event based instantiating Gateway', 34624 // actionName: 'replace-with-exclusive-event-based-gateway', 34625 // className: 'bpmn-icon-exclusive-event-based', 34626 // target: { 34627 // type: 'bpmn:EventBasedGateway' 34628 // }, 34629 // options: { 34630 // businessObject: { instantiate: true, eventGatewayType: 'Exclusive' } 34631 // } 34632 // }, 34633 // { 34634 // label: 'Parallel Event based instantiating Gateway', 34635 // actionName: 'replace-with-parallel-event-based-instantiate-gateway', 34636 // className: 'bpmn-icon-parallel-event-based-instantiate-gateway', 34637 // target: { 34638 // type: 'bpmn:EventBasedGateway' 34639 // }, 34640 // options: { 34641 // businessObject: { instantiate: true, eventGatewayType: 'Parallel' } 34642 // } 34643 // } 34644 ]; 34645 34646 var SUBPROCESS_EXPANDED = [ 34647 { 34648 label: 'Transaction', 34649 actionName: 'replace-with-transaction', 34650 className: 'bpmn-icon-transaction', 34651 target: { 34652 type: 'bpmn:Transaction', 34653 isExpanded: true 34654 } 34655 }, 34656 { 34657 label: 'Event Sub Process', 34658 actionName: 'replace-with-event-subprocess', 34659 className: 'bpmn-icon-event-subprocess-expanded', 34660 target: { 34661 type: 'bpmn:SubProcess', 34662 triggeredByEvent: true, 34663 isExpanded: true 34664 } 34665 }, 34666 { 34667 label: 'Sub Process (collapsed)', 34668 actionName: 'replace-with-collapsed-subprocess', 34669 className: 'bpmn-icon-subprocess-collapsed', 34670 target: { 34671 type: 'bpmn:SubProcess', 34672 isExpanded: false 34673 } 34674 } 34675 ]; 34676 34677 var TRANSACTION = [ 34678 { 34679 label: 'Sub Process', 34680 actionName: 'replace-with-subprocess', 34681 className: 'bpmn-icon-subprocess-expanded', 34682 target: { 34683 type: 'bpmn:SubProcess', 34684 isExpanded: true 34685 } 34686 }, 34687 { 34688 label: 'Event Sub Process', 34689 actionName: 'replace-with-event-subprocess', 34690 className: 'bpmn-icon-event-subprocess-expanded', 34691 target: { 34692 type: 'bpmn:SubProcess', 34693 triggeredByEvent: true, 34694 isExpanded: true 34695 } 34696 } 34697 ]; 34698 34699 var EVENT_SUB_PROCESS = [ 34700 { 34701 label: 'Sub Process', 34702 actionName: 'replace-with-subprocess', 34703 className: 'bpmn-icon-subprocess-expanded', 34704 target: { 34705 type: 'bpmn:SubProcess', 34706 isExpanded: true 34707 } 34708 }, 34709 { 34710 label: 'Transaction', 34711 actionName: 'replace-with-transaction', 34712 className: 'bpmn-icon-transaction', 34713 target: { 34714 type: 'bpmn:Transaction', 34715 isExpanded: true 34716 } 34717 } 34718 ]; 34719 34720 var TASK = [ 34721 { 34722 label: 'Task', 34723 actionName: 'replace-with-task', 34724 className: 'bpmn-icon-task', 34725 target: { 34726 type: 'bpmn:Task' 34727 } 34728 }, 34729 { 34730 label: 'Send Task', 34731 actionName: 'replace-with-send-task', 34732 className: 'bpmn-icon-send', 34733 target: { 34734 type: 'bpmn:SendTask' 34735 } 34736 }, 34737 { 34738 label: 'Receive Task', 34739 actionName: 'replace-with-receive-task', 34740 className: 'bpmn-icon-receive', 34741 target: { 34742 type: 'bpmn:ReceiveTask' 34743 } 34744 }, 34745 { 34746 label: 'User Task', 34747 actionName: 'replace-with-user-task', 34748 className: 'bpmn-icon-user', 34749 target: { 34750 type: 'bpmn:UserTask' 34751 } 34752 }, 34753 { 34754 label: 'Manual Task', 34755 actionName: 'replace-with-manual-task', 34756 className: 'bpmn-icon-manual', 34757 target: { 34758 type: 'bpmn:ManualTask' 34759 } 34760 }, 34761 { 34762 label: 'Business Rule Task', 34763 actionName: 'replace-with-rule-task', 34764 className: 'bpmn-icon-business-rule', 34765 target: { 34766 type: 'bpmn:BusinessRuleTask' 34767 } 34768 }, 34769 { 34770 label: 'Service Task', 34771 actionName: 'replace-with-service-task', 34772 className: 'bpmn-icon-service', 34773 target: { 34774 type: 'bpmn:ServiceTask' 34775 } 34776 }, 34777 { 34778 label: 'Script Task', 34779 actionName: 'replace-with-script-task', 34780 className: 'bpmn-icon-script', 34781 target: { 34782 type: 'bpmn:ScriptTask' 34783 } 34784 }, 34785 { 34786 label: 'Call Activity', 34787 actionName: 'replace-with-call-activity', 34788 className: 'bpmn-icon-call-activity', 34789 target: { 34790 type: 'bpmn:CallActivity' 34791 } 34792 }, 34793 { 34794 label: 'Sub Process (collapsed)', 34795 actionName: 'replace-with-collapsed-subprocess', 34796 className: 'bpmn-icon-subprocess-collapsed', 34797 target: { 34798 type: 'bpmn:SubProcess', 34799 isExpanded: false 34800 } 34801 }, 34802 { 34803 label: 'Sub Process (expanded)', 34804 actionName: 'replace-with-expanded-subprocess', 34805 className: 'bpmn-icon-subprocess-expanded', 34806 target: { 34807 type: 'bpmn:SubProcess', 34808 isExpanded: true 34809 } 34810 } 34811 ]; 34812 34813 var DATA_OBJECT_REFERENCE = [ 34814 { 34815 label: 'Data Store Reference', 34816 actionName: 'replace-with-data-store-reference', 34817 className: 'bpmn-icon-data-store', 34818 target: { 34819 type: 'bpmn:DataStoreReference' 34820 } 34821 } 34822 ]; 34823 34824 var DATA_STORE_REFERENCE = [ 34825 { 34826 label: 'Data Object Reference', 34827 actionName: 'replace-with-data-object-reference', 34828 className: 'bpmn-icon-data-object', 34829 target: { 34830 type: 'bpmn:DataObjectReference' 34831 } 34832 } 34833 ]; 34834 34835 var BOUNDARY_EVENT = [ 34836 { 34837 label: 'Message Boundary Event', 34838 actionName: 'replace-with-message-boundary', 34839 className: 'bpmn-icon-intermediate-event-catch-message', 34840 target: { 34841 type: 'bpmn:BoundaryEvent', 34842 eventDefinitionType: 'bpmn:MessageEventDefinition' 34843 } 34844 }, 34845 { 34846 label: 'Timer Boundary Event', 34847 actionName: 'replace-with-timer-boundary', 34848 className: 'bpmn-icon-intermediate-event-catch-timer', 34849 target: { 34850 type: 'bpmn:BoundaryEvent', 34851 eventDefinitionType: 'bpmn:TimerEventDefinition' 34852 } 34853 }, 34854 { 34855 label: 'Escalation Boundary Event', 34856 actionName: 'replace-with-escalation-boundary', 34857 className: 'bpmn-icon-intermediate-event-catch-escalation', 34858 target: { 34859 type: 'bpmn:BoundaryEvent', 34860 eventDefinitionType: 'bpmn:EscalationEventDefinition' 34861 } 34862 }, 34863 { 34864 label: 'Conditional Boundary Event', 34865 actionName: 'replace-with-conditional-boundary', 34866 className: 'bpmn-icon-intermediate-event-catch-condition', 34867 target: { 34868 type: 'bpmn:BoundaryEvent', 34869 eventDefinitionType: 'bpmn:ConditionalEventDefinition' 34870 } 34871 }, 34872 { 34873 label: 'Error Boundary Event', 34874 actionName: 'replace-with-error-boundary', 34875 className: 'bpmn-icon-intermediate-event-catch-error', 34876 target: { 34877 type: 'bpmn:BoundaryEvent', 34878 eventDefinitionType: 'bpmn:ErrorEventDefinition' 34879 } 34880 }, 34881 { 34882 label: 'Cancel Boundary Event', 34883 actionName: 'replace-with-cancel-boundary', 34884 className: 'bpmn-icon-intermediate-event-catch-cancel', 34885 target: { 34886 type: 'bpmn:BoundaryEvent', 34887 eventDefinitionType: 'bpmn:CancelEventDefinition' 34888 } 34889 }, 34890 { 34891 label: 'Signal Boundary Event', 34892 actionName: 'replace-with-signal-boundary', 34893 className: 'bpmn-icon-intermediate-event-catch-signal', 34894 target: { 34895 type: 'bpmn:BoundaryEvent', 34896 eventDefinitionType: 'bpmn:SignalEventDefinition' 34897 } 34898 }, 34899 { 34900 label: 'Compensation Boundary Event', 34901 actionName: 'replace-with-compensation-boundary', 34902 className: 'bpmn-icon-intermediate-event-catch-compensation', 34903 target: { 34904 type: 'bpmn:BoundaryEvent', 34905 eventDefinitionType: 'bpmn:CompensateEventDefinition' 34906 } 34907 }, 34908 { 34909 label: 'Message Boundary Event (non-interrupting)', 34910 actionName: 'replace-with-non-interrupting-message-boundary', 34911 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-message', 34912 target: { 34913 type: 'bpmn:BoundaryEvent', 34914 eventDefinitionType: 'bpmn:MessageEventDefinition', 34915 cancelActivity: false 34916 } 34917 }, 34918 { 34919 label: 'Timer Boundary Event (non-interrupting)', 34920 actionName: 'replace-with-non-interrupting-timer-boundary', 34921 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-timer', 34922 target: { 34923 type: 'bpmn:BoundaryEvent', 34924 eventDefinitionType: 'bpmn:TimerEventDefinition', 34925 cancelActivity: false 34926 } 34927 }, 34928 { 34929 label: 'Escalation Boundary Event (non-interrupting)', 34930 actionName: 'replace-with-non-interrupting-escalation-boundary', 34931 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-escalation', 34932 target: { 34933 type: 'bpmn:BoundaryEvent', 34934 eventDefinitionType: 'bpmn:EscalationEventDefinition', 34935 cancelActivity: false 34936 } 34937 }, 34938 { 34939 label: 'Conditional Boundary Event (non-interrupting)', 34940 actionName: 'replace-with-non-interrupting-conditional-boundary', 34941 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-condition', 34942 target: { 34943 type: 'bpmn:BoundaryEvent', 34944 eventDefinitionType: 'bpmn:ConditionalEventDefinition', 34945 cancelActivity: false 34946 } 34947 }, 34948 { 34949 label: 'Signal Boundary Event (non-interrupting)', 34950 actionName: 'replace-with-non-interrupting-signal-boundary', 34951 className: 'bpmn-icon-intermediate-event-catch-non-interrupting-signal', 34952 target: { 34953 type: 'bpmn:BoundaryEvent', 34954 eventDefinitionType: 'bpmn:SignalEventDefinition', 34955 cancelActivity: false 34956 } 34957 } 34958 ]; 34959 34960 var EVENT_SUB_PROCESS_START_EVENT = [ 34961 { 34962 label: 'Message Start Event', 34963 actionName: 'replace-with-message-start', 34964 className: 'bpmn-icon-start-event-message', 34965 target: { 34966 type: 'bpmn:StartEvent', 34967 eventDefinitionType: 'bpmn:MessageEventDefinition' 34968 } 34969 }, 34970 { 34971 label: 'Timer Start Event', 34972 actionName: 'replace-with-timer-start', 34973 className: 'bpmn-icon-start-event-timer', 34974 target: { 34975 type: 'bpmn:StartEvent', 34976 eventDefinitionType: 'bpmn:TimerEventDefinition' 34977 } 34978 }, 34979 { 34980 label: 'Conditional Start Event', 34981 actionName: 'replace-with-conditional-start', 34982 className: 'bpmn-icon-start-event-condition', 34983 target: { 34984 type: 'bpmn:StartEvent', 34985 eventDefinitionType: 'bpmn:ConditionalEventDefinition' 34986 } 34987 }, 34988 { 34989 label: 'Signal Start Event', 34990 actionName: 'replace-with-signal-start', 34991 className: 'bpmn-icon-start-event-signal', 34992 target: { 34993 type: 'bpmn:StartEvent', 34994 eventDefinitionType: 'bpmn:SignalEventDefinition' 34995 } 34996 }, 34997 { 34998 label: 'Error Start Event', 34999 actionName: 'replace-with-error-start', 35000 className: 'bpmn-icon-start-event-error', 35001 target: { 35002 type: 'bpmn:StartEvent', 35003 eventDefinitionType: 'bpmn:ErrorEventDefinition' 35004 } 35005 }, 35006 { 35007 label: 'Escalation Start Event', 35008 actionName: 'replace-with-escalation-start', 35009 className: 'bpmn-icon-start-event-escalation', 35010 target: { 35011 type: 'bpmn:StartEvent', 35012 eventDefinitionType: 'bpmn:EscalationEventDefinition' 35013 } 35014 }, 35015 { 35016 label: 'Compensation Start Event', 35017 actionName: 'replace-with-compensation-start', 35018 className: 'bpmn-icon-start-event-compensation', 35019 target: { 35020 type: 'bpmn:StartEvent', 35021 eventDefinitionType: 'bpmn:CompensateEventDefinition' 35022 } 35023 }, 35024 { 35025 label: 'Message Start Event (non-interrupting)', 35026 actionName: 'replace-with-non-interrupting-message-start', 35027 className: 'bpmn-icon-start-event-non-interrupting-message', 35028 target: { 35029 type: 'bpmn:StartEvent', 35030 eventDefinitionType: 'bpmn:MessageEventDefinition', 35031 isInterrupting: false 35032 } 35033 }, 35034 { 35035 label: 'Timer Start Event (non-interrupting)', 35036 actionName: 'replace-with-non-interrupting-timer-start', 35037 className: 'bpmn-icon-start-event-non-interrupting-timer', 35038 target: { 35039 type: 'bpmn:StartEvent', 35040 eventDefinitionType: 'bpmn:TimerEventDefinition', 35041 isInterrupting: false 35042 } 35043 }, 35044 { 35045 label: 'Conditional Start Event (non-interrupting)', 35046 actionName: 'replace-with-non-interrupting-conditional-start', 35047 className: 'bpmn-icon-start-event-non-interrupting-condition', 35048 target: { 35049 type: 'bpmn:StartEvent', 35050 eventDefinitionType: 'bpmn:ConditionalEventDefinition', 35051 isInterrupting: false 35052 } 35053 }, 35054 { 35055 label: 'Signal Start Event (non-interrupting)', 35056 actionName: 'replace-with-non-interrupting-signal-start', 35057 className: 'bpmn-icon-start-event-non-interrupting-signal', 35058 target: { 35059 type: 'bpmn:StartEvent', 35060 eventDefinitionType: 'bpmn:SignalEventDefinition', 35061 isInterrupting: false 35062 } 35063 }, 35064 { 35065 label: 'Escalation Start Event (non-interrupting)', 35066 actionName: 'replace-with-non-interrupting-escalation-start', 35067 className: 'bpmn-icon-start-event-non-interrupting-escalation', 35068 target: { 35069 type: 'bpmn:StartEvent', 35070 eventDefinitionType: 'bpmn:EscalationEventDefinition', 35071 isInterrupting: false 35072 } 35073 } 35074 ]; 35075 35076 var SEQUENCE_FLOW = [ 35077 { 35078 label: 'Sequence Flow', 35079 actionName: 'replace-with-sequence-flow', 35080 className: 'bpmn-icon-connection' 35081 }, 35082 { 35083 label: 'Default Flow', 35084 actionName: 'replace-with-default-flow', 35085 className: 'bpmn-icon-default-flow' 35086 }, 35087 { 35088 label: 'Conditional Flow', 35089 actionName: 'replace-with-conditional-flow', 35090 className: 'bpmn-icon-conditional-flow' 35091 } 35092 ]; 35093 35094 var PARTICIPANT = [ 35095 { 35096 label: 'Expanded Pool', 35097 actionName: 'replace-with-expanded-pool', 35098 className: 'bpmn-icon-participant', 35099 target: { 35100 type: 'bpmn:Participant', 35101 isExpanded: true 35102 } 35103 }, 35104 { 35105 label: function(element) { 35106 var label = 'Empty Pool'; 35107 35108 if (element.children && element.children.length) { 35109 label += ' (removes content)'; 35110 } 35111 35112 return label; 35113 }, 35114 actionName: 'replace-with-collapsed-pool', 35115 35116 // TODO(@janstuemmel): maybe design new icon 35117 className: 'bpmn-icon-lane', 35118 target: { 35119 type: 'bpmn:Participant', 35120 isExpanded: false 35121 } 35122 } 35123 ]; 35124 35125 /** 35126 * This module is an element agnostic replace menu provider for the popup menu. 35127 */ 35128 function ReplaceMenuProvider( 35129 bpmnFactory, popupMenu, modeling, moddle, 35130 bpmnReplace, rules, translate) { 35131 35132 this._bpmnFactory = bpmnFactory; 35133 this._popupMenu = popupMenu; 35134 this._modeling = modeling; 35135 this._moddle = moddle; 35136 this._bpmnReplace = bpmnReplace; 35137 this._rules = rules; 35138 this._translate = translate; 35139 35140 this.register(); 35141 } 35142 35143 ReplaceMenuProvider.$inject = [ 35144 'bpmnFactory', 35145 'popupMenu', 35146 'modeling', 35147 'moddle', 35148 'bpmnReplace', 35149 'rules', 35150 'translate' 35151 ]; 35152 35153 35154 /** 35155 * Register replace menu provider in the popup menu 35156 */ 35157 ReplaceMenuProvider.prototype.register = function() { 35158 this._popupMenu.registerProvider('bpmn-replace', this); 35159 }; 35160 35161 35162 /** 35163 * Get all entries from replaceOptions for the given element and apply filters 35164 * on them. Get for example only elements, which are different from the current one. 35165 * 35166 * @param {djs.model.Base} element 35167 * 35168 * @return {Array<Object>} a list of menu entry items 35169 */ 35170 ReplaceMenuProvider.prototype.getEntries = function(element) { 35171 35172 var businessObject = element.businessObject; 35173 35174 var rules = this._rules; 35175 35176 var entries; 35177 35178 if (!rules.allowed('shape.replace', { element: element })) { 35179 return []; 35180 } 35181 35182 var differentType = isDifferentType(element); 35183 35184 if (is$1(businessObject, 'bpmn:DataObjectReference')) { 35185 return this._createEntries(element, DATA_OBJECT_REFERENCE); 35186 } 35187 35188 if (is$1(businessObject, 'bpmn:DataStoreReference')) { 35189 return this._createEntries(element, DATA_STORE_REFERENCE); 35190 } 35191 35192 // start events outside sub processes 35193 if (is$1(businessObject, 'bpmn:StartEvent') && !is$1(businessObject.$parent, 'bpmn:SubProcess')) { 35194 35195 entries = filter(START_EVENT, differentType); 35196 35197 return this._createEntries(element, entries); 35198 } 35199 35200 // expanded/collapsed pools 35201 if (is$1(businessObject, 'bpmn:Participant')) { 35202 35203 entries = filter(PARTICIPANT, function(entry) { 35204 return isExpanded(businessObject) !== entry.target.isExpanded; 35205 }); 35206 35207 return this._createEntries(element, entries); 35208 } 35209 35210 // start events inside event sub processes 35211 if (is$1(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) { 35212 entries = filter(EVENT_SUB_PROCESS_START_EVENT, function(entry) { 35213 35214 var target = entry.target; 35215 35216 var isInterrupting = target.isInterrupting !== false; 35217 35218 var isInterruptingEqual = getBusinessObject(element).isInterrupting === isInterrupting; 35219 35220 // filters elements which types and event definition are equal but have have different interrupting types 35221 return differentType(entry) || !differentType(entry) && !isInterruptingEqual; 35222 35223 }); 35224 35225 return this._createEntries(element, entries); 35226 } 35227 35228 // start events inside sub processes 35229 if (is$1(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent) 35230 && is$1(businessObject.$parent, 'bpmn:SubProcess')) { 35231 entries = filter(START_EVENT_SUB_PROCESS, differentType); 35232 35233 return this._createEntries(element, entries); 35234 } 35235 35236 // end events 35237 if (is$1(businessObject, 'bpmn:EndEvent')) { 35238 35239 entries = filter(END_EVENT, function(entry) { 35240 var target = entry.target; 35241 35242 // hide cancel end events outside transactions 35243 if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is$1(businessObject.$parent, 'bpmn:Transaction')) { 35244 return false; 35245 } 35246 35247 return differentType(entry); 35248 }); 35249 35250 return this._createEntries(element, entries); 35251 } 35252 35253 // boundary events 35254 if (is$1(businessObject, 'bpmn:BoundaryEvent')) { 35255 35256 entries = filter(BOUNDARY_EVENT, function(entry) { 35257 35258 var target = entry.target; 35259 35260 if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && 35261 !is$1(businessObject.attachedToRef, 'bpmn:Transaction')) { 35262 return false; 35263 } 35264 var cancelActivity = target.cancelActivity !== false; 35265 35266 var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity; 35267 35268 return differentType(entry) || !differentType(entry) && !isCancelActivityEqual; 35269 }); 35270 35271 return this._createEntries(element, entries); 35272 } 35273 35274 // intermediate events 35275 if (is$1(businessObject, 'bpmn:IntermediateCatchEvent') || 35276 is$1(businessObject, 'bpmn:IntermediateThrowEvent')) { 35277 35278 entries = filter(INTERMEDIATE_EVENT, differentType); 35279 35280 return this._createEntries(element, entries); 35281 } 35282 35283 // gateways 35284 if (is$1(businessObject, 'bpmn:Gateway')) { 35285 35286 entries = filter(GATEWAY, differentType); 35287 35288 return this._createEntries(element, entries); 35289 } 35290 35291 // transactions 35292 if (is$1(businessObject, 'bpmn:Transaction')) { 35293 35294 entries = filter(TRANSACTION, differentType); 35295 35296 return this._createEntries(element, entries); 35297 } 35298 35299 // expanded event sub processes 35300 if (isEventSubProcess(businessObject) && isExpanded(businessObject)) { 35301 35302 entries = filter(EVENT_SUB_PROCESS, differentType); 35303 35304 return this._createEntries(element, entries); 35305 } 35306 35307 // expanded sub processes 35308 if (is$1(businessObject, 'bpmn:SubProcess') && isExpanded(businessObject)) { 35309 35310 entries = filter(SUBPROCESS_EXPANDED, differentType); 35311 35312 return this._createEntries(element, entries); 35313 } 35314 35315 // collapsed ad hoc sub processes 35316 if (is$1(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(businessObject)) { 35317 35318 entries = filter(TASK, function(entry) { 35319 35320 var target = entry.target; 35321 35322 var isTargetSubProcess = target.type === 'bpmn:SubProcess'; 35323 35324 var isTargetExpanded = target.isExpanded === true; 35325 35326 return isDifferentType(element) && (!isTargetSubProcess || isTargetExpanded); 35327 }); 35328 35329 return this._createEntries(element, entries); 35330 } 35331 35332 // sequence flows 35333 if (is$1(businessObject, 'bpmn:SequenceFlow')) { 35334 return this._createSequenceFlowEntries(element, SEQUENCE_FLOW); 35335 } 35336 35337 // flow nodes 35338 if (is$1(businessObject, 'bpmn:FlowNode')) { 35339 entries = filter(TASK, differentType); 35340 35341 // collapsed SubProcess can not be replaced with itself 35342 if (is$1(businessObject, 'bpmn:SubProcess') && !isExpanded(businessObject)) { 35343 entries = filter(entries, function(entry) { 35344 return entry.label !== 'Sub Process (collapsed)'; 35345 }); 35346 } 35347 35348 return this._createEntries(element, entries); 35349 } 35350 35351 return []; 35352 }; 35353 35354 35355 /** 35356 * Get a list of header items for the given element. This includes buttons 35357 * for multi instance markers and for the ad hoc marker. 35358 * 35359 * @param {djs.model.Base} element 35360 * 35361 * @return {Array<Object>} a list of menu entry items 35362 */ 35363 ReplaceMenuProvider.prototype.getHeaderEntries = function(element) { 35364 35365 var headerEntries = []; 35366 35367 if (is$1(element, 'bpmn:Activity') && !isEventSubProcess(element)) { 35368 headerEntries = headerEntries.concat(this._getLoopEntries(element)); 35369 } 35370 35371 if (is$1(element, 'bpmn:DataObjectReference')) { 35372 headerEntries = headerEntries.concat(this._getDataObjectIsCollection(element)); 35373 } 35374 35375 if (is$1(element, 'bpmn:Participant')) { 35376 headerEntries = headerEntries.concat(this._getParticipantMultiplicity(element)); 35377 } 35378 35379 if (is$1(element, 'bpmn:SubProcess') && 35380 !is$1(element, 'bpmn:Transaction') && 35381 !isEventSubProcess(element)) { 35382 headerEntries.push(this._getAdHocEntry(element)); 35383 } 35384 35385 return headerEntries; 35386 }; 35387 35388 35389 /** 35390 * Creates an array of menu entry objects for a given element and filters the replaceOptions 35391 * according to a filter function. 35392 * 35393 * @param {djs.model.Base} element 35394 * @param {Object} replaceOptions 35395 * 35396 * @return {Array<Object>} a list of menu items 35397 */ 35398 ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) { 35399 var menuEntries = []; 35400 35401 var self = this; 35402 35403 forEach(replaceOptions, function(definition) { 35404 var entry = self._createMenuEntry(definition, element); 35405 35406 menuEntries.push(entry); 35407 }); 35408 35409 return menuEntries; 35410 }; 35411 35412 /** 35413 * Creates an array of menu entry objects for a given sequence flow. 35414 * 35415 * @param {djs.model.Base} element 35416 * @param {Object} replaceOptions 35417 35418 * @return {Array<Object>} a list of menu items 35419 */ 35420 ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(element, replaceOptions) { 35421 35422 var businessObject = getBusinessObject(element); 35423 35424 var menuEntries = []; 35425 35426 var modeling = this._modeling, 35427 moddle = this._moddle; 35428 35429 var self = this; 35430 35431 forEach(replaceOptions, function(entry) { 35432 35433 switch (entry.actionName) { 35434 case 'replace-with-default-flow': 35435 if (businessObject.sourceRef.default !== businessObject && 35436 (is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') || 35437 is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') || 35438 is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') || 35439 is$1(businessObject.sourceRef, 'bpmn:Activity'))) { 35440 35441 menuEntries.push(self._createMenuEntry(entry, element, function() { 35442 modeling.updateProperties(element.source, { default: businessObject }); 35443 })); 35444 } 35445 break; 35446 case 'replace-with-conditional-flow': 35447 if (!businessObject.conditionExpression && is$1(businessObject.sourceRef, 'bpmn:Activity')) { 35448 35449 menuEntries.push(self._createMenuEntry(entry, element, function() { 35450 var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' }); 35451 35452 modeling.updateProperties(element, { conditionExpression: conditionExpression }); 35453 })); 35454 } 35455 break; 35456 default: 35457 35458 // default flows 35459 if (is$1(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) { 35460 return menuEntries.push(self._createMenuEntry(entry, element, function() { 35461 modeling.updateProperties(element, { conditionExpression: undefined }); 35462 })); 35463 } 35464 35465 // conditional flows 35466 if ((is$1(businessObject.sourceRef, 'bpmn:ExclusiveGateway') || 35467 is$1(businessObject.sourceRef, 'bpmn:InclusiveGateway') || 35468 is$1(businessObject.sourceRef, 'bpmn:ComplexGateway') || 35469 is$1(businessObject.sourceRef, 'bpmn:Activity')) && 35470 businessObject.sourceRef.default === businessObject) { 35471 35472 return menuEntries.push(self._createMenuEntry(entry, element, function() { 35473 modeling.updateProperties(element.source, { default: undefined }); 35474 })); 35475 } 35476 } 35477 }); 35478 35479 return menuEntries; 35480 }; 35481 35482 35483 /** 35484 * Creates and returns a single menu entry item. 35485 * 35486 * @param {Object} definition a single replace options definition object 35487 * @param {djs.model.Base} element 35488 * @param {Function} [action] an action callback function which gets called when 35489 * the menu entry is being triggered. 35490 * 35491 * @return {Object} menu entry item 35492 */ 35493 ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) { 35494 var translate = this._translate; 35495 var replaceElement = this._bpmnReplace.replaceElement; 35496 35497 var replaceAction = function() { 35498 return replaceElement(element, definition.target); 35499 }; 35500 35501 var label = definition.label; 35502 if (label && typeof label === 'function') { 35503 label = label(element); 35504 } 35505 35506 action = action || replaceAction; 35507 35508 var menuEntry = { 35509 label: translate(label), 35510 className: definition.className, 35511 id: definition.actionName, 35512 action: action 35513 }; 35514 35515 return menuEntry; 35516 }; 35517 35518 /** 35519 * Get a list of menu items containing buttons for multi instance markers 35520 * 35521 * @param {djs.model.Base} element 35522 * 35523 * @return {Array<Object>} a list of menu items 35524 */ 35525 ReplaceMenuProvider.prototype._getLoopEntries = function(element) { 35526 35527 var self = this; 35528 var translate = this._translate; 35529 35530 function toggleLoopEntry(event, entry) { 35531 var loopCharacteristics; 35532 35533 if (entry.active) { 35534 loopCharacteristics = undefined; 35535 } else { 35536 loopCharacteristics = self._moddle.create(entry.options.loopCharacteristics); 35537 35538 if (entry.options.isSequential) { 35539 loopCharacteristics.isSequential = entry.options.isSequential; 35540 } 35541 } 35542 self._modeling.updateProperties(element, { loopCharacteristics: loopCharacteristics }); 35543 } 35544 35545 var businessObject = getBusinessObject(element), 35546 loopCharacteristics = businessObject.loopCharacteristics; 35547 35548 var isSequential, 35549 isLoop, 35550 isParallel; 35551 35552 if (loopCharacteristics) { 35553 isSequential = loopCharacteristics.isSequential; 35554 isLoop = loopCharacteristics.isSequential === undefined; 35555 isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential; 35556 } 35557 35558 35559 var loopEntries = [ 35560 { 35561 id: 'toggle-parallel-mi', 35562 className: 'bpmn-icon-parallel-mi-marker', 35563 title: translate('Parallel Multi Instance'), 35564 active: isParallel, 35565 action: toggleLoopEntry, 35566 options: { 35567 loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics', 35568 isSequential: false 35569 } 35570 }, 35571 { 35572 id: 'toggle-sequential-mi', 35573 className: 'bpmn-icon-sequential-mi-marker', 35574 title: translate('Sequential Multi Instance'), 35575 active: isSequential, 35576 action: toggleLoopEntry, 35577 options: { 35578 loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics', 35579 isSequential: true 35580 } 35581 }, 35582 { 35583 id: 'toggle-loop', 35584 className: 'bpmn-icon-loop-marker', 35585 title: translate('Loop'), 35586 active: isLoop, 35587 action: toggleLoopEntry, 35588 options: { 35589 loopCharacteristics: 'bpmn:StandardLoopCharacteristics' 35590 } 35591 } 35592 ]; 35593 return loopEntries; 35594 }; 35595 35596 /** 35597 * Get a list of menu items containing a button for the collection marker 35598 * 35599 * @param {djs.model.Base} element 35600 * 35601 * @return {Array<Object>} a list of menu items 35602 */ 35603 ReplaceMenuProvider.prototype._getDataObjectIsCollection = function(element) { 35604 35605 var self = this; 35606 var translate = this._translate; 35607 35608 function toggleIsCollection(event, entry) { 35609 self._modeling.updateModdleProperties( 35610 element, 35611 dataObject, 35612 { isCollection: !entry.active }); 35613 } 35614 35615 var dataObject = element.businessObject.dataObjectRef, 35616 isCollection = dataObject.isCollection; 35617 35618 var dataObjectEntries = [ 35619 { 35620 id: 'toggle-is-collection', 35621 className: 'bpmn-icon-parallel-mi-marker', 35622 title: translate('Collection'), 35623 active: isCollection, 35624 action: toggleIsCollection, 35625 } 35626 ]; 35627 return dataObjectEntries; 35628 }; 35629 35630 /** 35631 * Get a list of menu items containing a button for the participant multiplicity marker 35632 * 35633 * @param {djs.model.Base} element 35634 * 35635 * @return {Array<Object>} a list of menu items 35636 */ 35637 ReplaceMenuProvider.prototype._getParticipantMultiplicity = function(element) { 35638 35639 var self = this; 35640 var bpmnFactory = this._bpmnFactory; 35641 var translate = this._translate; 35642 35643 function toggleParticipantMultiplicity(event, entry) { 35644 var isActive = entry.active; 35645 var participantMultiplicity; 35646 35647 if (!isActive) { 35648 participantMultiplicity = bpmnFactory.create('bpmn:ParticipantMultiplicity'); 35649 } 35650 35651 self._modeling.updateProperties( 35652 element, 35653 { participantMultiplicity: participantMultiplicity }); 35654 } 35655 35656 var participantMultiplicity = element.businessObject.participantMultiplicity; 35657 35658 var participantEntries = [ 35659 { 35660 id: 'toggle-participant-multiplicity', 35661 className: 'bpmn-icon-parallel-mi-marker', 35662 title: translate('Participant Multiplicity'), 35663 active: !!participantMultiplicity, 35664 action: toggleParticipantMultiplicity, 35665 } 35666 ]; 35667 return participantEntries; 35668 }; 35669 35670 35671 /** 35672 * Get the menu items containing a button for the ad hoc marker 35673 * 35674 * @param {djs.model.Base} element 35675 * 35676 * @return {Object} a menu item 35677 */ 35678 ReplaceMenuProvider.prototype._getAdHocEntry = function(element) { 35679 var translate = this._translate; 35680 var businessObject = getBusinessObject(element); 35681 35682 var isAdHoc = is$1(businessObject, 'bpmn:AdHocSubProcess'); 35683 35684 var replaceElement = this._bpmnReplace.replaceElement; 35685 35686 var adHocEntry = { 35687 id: 'toggle-adhoc', 35688 className: 'bpmn-icon-ad-hoc-marker', 35689 title: translate('Ad-hoc'), 35690 active: isAdHoc, 35691 action: function(event, entry) { 35692 if (isAdHoc) { 35693 return replaceElement(element, { type: 'bpmn:SubProcess' }, { 35694 autoResize: false, 35695 layoutConnection: false 35696 }); 35697 } else { 35698 return replaceElement(element, { type: 'bpmn:AdHocSubProcess' }, { 35699 autoResize: false, 35700 layoutConnection: false 35701 }); 35702 } 35703 } 35704 }; 35705 35706 return adHocEntry; 35707 }; 35708 35709 var PopupMenuModule = { 35710 __depends__: [ 35711 PopupMenuModule$1, 35712 ReplaceModule 35713 ], 35714 __init__: [ 'replaceMenuProvider' ], 35715 replaceMenuProvider: [ 'type', ReplaceMenuProvider ] 35716 }; 35717 35718 var max$4 = Math.max, 35719 min$2 = Math.min; 35720 35721 var DEFAULT_CHILD_BOX_PADDING = 20; 35722 35723 35724 /** 35725 * Substract a TRBL from another 35726 * 35727 * @param {TRBL} trblA 35728 * @param {TRBL} trblB 35729 * 35730 * @return {TRBL} 35731 */ 35732 function substractTRBL(trblA, trblB) { 35733 return { 35734 top: trblA.top - trblB.top, 35735 right: trblA.right - trblB.right, 35736 bottom: trblA.bottom - trblB.bottom, 35737 left: trblA.left - trblB.left 35738 }; 35739 } 35740 35741 /** 35742 * Resize the given bounds by the specified delta from a given anchor point. 35743 * 35744 * @param {Bounds} bounds the bounding box that should be resized 35745 * @param {string} direction in which the element is resized (nw, ne, se, sw) 35746 * @param {Point} delta of the resize operation 35747 * 35748 * @return {Bounds} resized bounding box 35749 */ 35750 function resizeBounds$1(bounds, direction, delta) { 35751 var dx = delta.x, 35752 dy = delta.y; 35753 35754 var newBounds = { 35755 x: bounds.x, 35756 y: bounds.y, 35757 width: bounds.width, 35758 height: bounds.height 35759 }; 35760 35761 if (direction.indexOf('n') !== -1) { 35762 newBounds.y = bounds.y + dy; 35763 newBounds.height = bounds.height - dy; 35764 } else if (direction.indexOf('s') !== -1) { 35765 newBounds.height = bounds.height + dy; 35766 } 35767 35768 if (direction.indexOf('e') !== -1) { 35769 newBounds.width = bounds.width + dx; 35770 } else if (direction.indexOf('w') !== -1) { 35771 newBounds.x = bounds.x + dx; 35772 newBounds.width = bounds.width - dx; 35773 } 35774 35775 return newBounds; 35776 } 35777 35778 35779 /** 35780 * Resize the given bounds by applying the passed 35781 * { top, right, bottom, left } delta. 35782 * 35783 * @param {Bounds} bounds 35784 * @param {TRBL} trblResize 35785 * 35786 * @return {Bounds} 35787 */ 35788 function resizeTRBL(bounds, resize) { 35789 return { 35790 x: bounds.x + (resize.left || 0), 35791 y: bounds.y + (resize.top || 0), 35792 width: bounds.width - (resize.left || 0) + (resize.right || 0), 35793 height: bounds.height - (resize.top || 0) + (resize.bottom || 0) 35794 }; 35795 } 35796 35797 35798 function applyConstraints(attr, trbl, resizeConstraints) { 35799 35800 var value = trbl[attr], 35801 minValue = resizeConstraints.min && resizeConstraints.min[attr], 35802 maxValue = resizeConstraints.max && resizeConstraints.max[attr]; 35803 35804 if (isNumber(minValue)) { 35805 value = (/top|left/.test(attr) ? min$2 : max$4)(value, minValue); 35806 } 35807 35808 if (isNumber(maxValue)) { 35809 value = (/top|left/.test(attr) ? max$4 : min$2)(value, maxValue); 35810 } 35811 35812 return value; 35813 } 35814 35815 function ensureConstraints$1(currentBounds, resizeConstraints) { 35816 35817 if (!resizeConstraints) { 35818 return currentBounds; 35819 } 35820 35821 var currentTrbl = asTRBL(currentBounds); 35822 35823 return asBounds({ 35824 top: applyConstraints('top', currentTrbl, resizeConstraints), 35825 right: applyConstraints('right', currentTrbl, resizeConstraints), 35826 bottom: applyConstraints('bottom', currentTrbl, resizeConstraints), 35827 left: applyConstraints('left', currentTrbl, resizeConstraints) 35828 }); 35829 } 35830 35831 35832 function getMinResizeBounds(direction, currentBounds, minDimensions, childrenBounds) { 35833 35834 var currentBox = asTRBL(currentBounds); 35835 35836 var minBox = { 35837 top: /n/.test(direction) ? currentBox.bottom - minDimensions.height : currentBox.top, 35838 left: /w/.test(direction) ? currentBox.right - minDimensions.width : currentBox.left, 35839 bottom: /s/.test(direction) ? currentBox.top + minDimensions.height : currentBox.bottom, 35840 right: /e/.test(direction) ? currentBox.left + minDimensions.width : currentBox.right 35841 }; 35842 35843 var childrenBox = childrenBounds ? asTRBL(childrenBounds) : minBox; 35844 35845 var combinedBox = { 35846 top: min$2(minBox.top, childrenBox.top), 35847 left: min$2(minBox.left, childrenBox.left), 35848 bottom: max$4(minBox.bottom, childrenBox.bottom), 35849 right: max$4(minBox.right, childrenBox.right) 35850 }; 35851 35852 return asBounds(combinedBox); 35853 } 35854 35855 function asPadding(mayBePadding, defaultValue) { 35856 if (typeof mayBePadding !== 'undefined') { 35857 return mayBePadding; 35858 } else { 35859 return DEFAULT_CHILD_BOX_PADDING; 35860 } 35861 } 35862 35863 function addPadding$1(bbox, padding) { 35864 var left, right, top, bottom; 35865 35866 if (typeof padding === 'object') { 35867 left = asPadding(padding.left); 35868 right = asPadding(padding.right); 35869 top = asPadding(padding.top); 35870 bottom = asPadding(padding.bottom); 35871 } else { 35872 left = right = top = bottom = asPadding(padding); 35873 } 35874 35875 return { 35876 x: bbox.x - left, 35877 y: bbox.y - top, 35878 width: bbox.width + left + right, 35879 height: bbox.height + top + bottom 35880 }; 35881 } 35882 35883 35884 /** 35885 * Is the given element part of the resize 35886 * targets min boundary box? 35887 * 35888 * This is the default implementation which excludes 35889 * connections and labels. 35890 * 35891 * @param {djs.model.Base} element 35892 */ 35893 function isBBoxChild(element) { 35894 35895 // exclude connections 35896 if (element.waypoints) { 35897 return false; 35898 } 35899 35900 // exclude labels 35901 if (element.type === 'label') { 35902 return false; 35903 } 35904 35905 return true; 35906 } 35907 35908 /** 35909 * Return children bounding computed from a shapes children 35910 * or a list of prefiltered children. 35911 * 35912 * @param {djs.model.Shape|Array<djs.model.Shape>} shapeOrChildren 35913 * @param {number|Object} padding 35914 * 35915 * @return {Bounds} 35916 */ 35917 function computeChildrenBBox(shapeOrChildren, padding) { 35918 35919 var elements; 35920 35921 // compute based on shape 35922 if (shapeOrChildren.length === undefined) { 35923 35924 // grab all the children that are part of the 35925 // parents children box 35926 elements = filter(shapeOrChildren.children, isBBoxChild); 35927 35928 } else { 35929 elements = shapeOrChildren; 35930 } 35931 35932 if (elements.length) { 35933 return addPadding$1(getBBox(elements), padding); 35934 } 35935 } 35936 35937 var abs$4 = Math.abs; 35938 35939 35940 function getTRBLResize(oldBounds, newBounds) { 35941 return substractTRBL(asTRBL(newBounds), asTRBL(oldBounds)); 35942 } 35943 35944 35945 var LANE_PARENTS = [ 35946 'bpmn:Participant', 35947 'bpmn:Process', 35948 'bpmn:SubProcess' 35949 ]; 35950 35951 var LANE_INDENTATION = 30; 35952 35953 35954 /** 35955 * Collect all lane shapes in the given paren 35956 * 35957 * @param {djs.model.Shape} shape 35958 * @param {Array<djs.model.Base>} [collectedShapes] 35959 * 35960 * @return {Array<djs.model.Base>} 35961 */ 35962 function collectLanes(shape, collectedShapes) { 35963 35964 collectedShapes = collectedShapes || []; 35965 35966 shape.children.filter(function(s) { 35967 if (is$1(s, 'bpmn:Lane')) { 35968 collectLanes(s, collectedShapes); 35969 35970 collectedShapes.push(s); 35971 } 35972 }); 35973 35974 return collectedShapes; 35975 } 35976 35977 35978 /** 35979 * Return the lane children of the given element. 35980 * 35981 * @param {djs.model.Shape} shape 35982 * 35983 * @return {Array<djs.model.Shape>} 35984 */ 35985 function getChildLanes(shape) { 35986 return shape.children.filter(function(c) { 35987 return is$1(c, 'bpmn:Lane'); 35988 }); 35989 } 35990 35991 35992 /** 35993 * Return the root element containing the given lane shape 35994 * 35995 * @param {djs.model.Shape} shape 35996 * 35997 * @return {djs.model.Shape} 35998 */ 35999 function getLanesRoot(shape) { 36000 return getParent(shape, LANE_PARENTS) || shape; 36001 } 36002 36003 36004 /** 36005 * Compute the required resize operations for lanes 36006 * adjacent to the given shape, assuming it will be 36007 * resized to the given new bounds. 36008 * 36009 * @param {djs.model.Shape} shape 36010 * @param {Bounds} newBounds 36011 * 36012 * @return {Array<Object>} 36013 */ 36014 function computeLanesResize(shape, newBounds) { 36015 36016 var rootElement = getLanesRoot(shape); 36017 36018 var initialShapes = is$1(rootElement, 'bpmn:Process') ? [] : [ rootElement ]; 36019 36020 var allLanes = collectLanes(rootElement, initialShapes), 36021 shapeTrbl = asTRBL(shape), 36022 shapeNewTrbl = asTRBL(newBounds), 36023 trblResize = getTRBLResize(shape, newBounds), 36024 resizeNeeded = []; 36025 36026 allLanes.forEach(function(other) { 36027 36028 if (other === shape) { 36029 return; 36030 } 36031 36032 var topResize = 0, 36033 rightResize = trblResize.right, 36034 bottomResize = 0, 36035 leftResize = trblResize.left; 36036 36037 var otherTrbl = asTRBL(other); 36038 36039 if (trblResize.top) { 36040 if (abs$4(otherTrbl.bottom - shapeTrbl.top) < 10) { 36041 bottomResize = shapeNewTrbl.top - otherTrbl.bottom; 36042 } 36043 36044 if (abs$4(otherTrbl.top - shapeTrbl.top) < 5) { 36045 topResize = shapeNewTrbl.top - otherTrbl.top; 36046 } 36047 } 36048 36049 if (trblResize.bottom) { 36050 if (abs$4(otherTrbl.top - shapeTrbl.bottom) < 10) { 36051 topResize = shapeNewTrbl.bottom - otherTrbl.top; 36052 } 36053 36054 if (abs$4(otherTrbl.bottom - shapeTrbl.bottom) < 5) { 36055 bottomResize = shapeNewTrbl.bottom - otherTrbl.bottom; 36056 } 36057 } 36058 36059 if (topResize || rightResize || bottomResize || leftResize) { 36060 36061 resizeNeeded.push({ 36062 shape: other, 36063 newBounds: resizeTRBL(other, { 36064 top: topResize, 36065 right: rightResize, 36066 bottom: bottomResize, 36067 left: leftResize 36068 }) 36069 }); 36070 } 36071 36072 }); 36073 36074 return resizeNeeded; 36075 } 36076 36077 /** 36078 * A provider for BPMN 2.0 elements context pad 36079 */ 36080 function ContextPadProvider( 36081 config, injector, eventBus, 36082 contextPad, modeling, elementFactory, 36083 connect, create, popupMenu, 36084 canvas, rules, translate) { 36085 36086 config = config || {}; 36087 36088 contextPad.registerProvider(this); 36089 36090 this._contextPad = contextPad; 36091 36092 this._modeling = modeling; 36093 36094 this._elementFactory = elementFactory; 36095 this._connect = connect; 36096 this._create = create; 36097 this._popupMenu = popupMenu; 36098 this._canvas = canvas; 36099 this._rules = rules; 36100 this._translate = translate; 36101 36102 if (config.autoPlace !== false) { 36103 this._autoPlace = injector.get('autoPlace', false); 36104 } 36105 36106 eventBus.on('create.end', 250, function(event) { 36107 var context = event.context, 36108 shape = context.shape; 36109 36110 if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) { 36111 return; 36112 } 36113 36114 var entries = contextPad.getEntries(shape); 36115 36116 if (entries.replace) { 36117 entries.replace.action.click(event, shape); 36118 } 36119 }); 36120 } 36121 36122 ContextPadProvider.$inject = [ 36123 'config.contextPad', 36124 'injector', 36125 'eventBus', 36126 'contextPad', 36127 'modeling', 36128 'elementFactory', 36129 'connect', 36130 'create', 36131 'popupMenu', 36132 'canvas', 36133 'rules', 36134 'translate' 36135 ]; 36136 36137 36138 ContextPadProvider.prototype.getContextPadEntries = function(element) { 36139 36140 var contextPad = this._contextPad, 36141 modeling = this._modeling, 36142 36143 elementFactory = this._elementFactory, 36144 connect = this._connect, 36145 create = this._create, 36146 popupMenu = this._popupMenu, 36147 canvas = this._canvas, 36148 rules = this._rules, 36149 autoPlace = this._autoPlace, 36150 translate = this._translate; 36151 36152 var actions = {}; 36153 36154 if (element.type === 'label') { 36155 return actions; 36156 } 36157 36158 var businessObject = element.businessObject; 36159 36160 function startConnect(event, element) { 36161 connect.start(event, element); 36162 } 36163 36164 function removeElement(e) { 36165 modeling.removeElements([ element ]); 36166 } 36167 36168 function getReplaceMenuPosition(element) { 36169 36170 var Y_OFFSET = 5; 36171 36172 var diagramContainer = canvas.getContainer(), 36173 pad = contextPad.getPad(element).html; 36174 36175 var diagramRect = diagramContainer.getBoundingClientRect(), 36176 padRect = pad.getBoundingClientRect(); 36177 36178 var top = padRect.top - diagramRect.top; 36179 var left = padRect.left - diagramRect.left; 36180 36181 var pos = { 36182 x: left, 36183 y: top + padRect.height + Y_OFFSET 36184 }; 36185 36186 return pos; 36187 } 36188 36189 36190 /** 36191 * Create an append action 36192 * 36193 * @param {string} type 36194 * @param {string} className 36195 * @param {string} [title] 36196 * @param {Object} [options] 36197 * 36198 * @return {Object} descriptor 36199 */ 36200 function appendAction(type, className, title, options) { 36201 36202 if (typeof title !== 'string') { 36203 options = title; 36204 title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') }); 36205 } 36206 36207 function appendStart(event, element) { 36208 36209 var shape = elementFactory.createShape(assign({ type: type }, options)); 36210 create.start(event, shape, { 36211 source: element 36212 }); 36213 } 36214 36215 36216 var append = autoPlace ? function(event, element) { 36217 var shape = elementFactory.createShape(assign({ type: type }, options)); 36218 36219 autoPlace.append(element, shape); 36220 } : appendStart; 36221 36222 36223 return { 36224 group: 'model', 36225 className: className, 36226 title: title, 36227 action: { 36228 dragstart: appendStart, 36229 click: append 36230 } 36231 }; 36232 } 36233 36234 function splitLaneHandler(count) { 36235 36236 return function(event, element) { 36237 36238 // actual split 36239 modeling.splitLane(element, count); 36240 36241 // refresh context pad after split to 36242 // get rid of split icons 36243 contextPad.open(element, true); 36244 }; 36245 } 36246 36247 36248 if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(businessObject)) { 36249 36250 var childLanes = getChildLanes(element); 36251 36252 assign(actions, { 36253 'lane-insert-above': { 36254 group: 'lane-insert-above', 36255 className: 'bpmn-icon-lane-insert-above', 36256 title: translate('Add Lane above'), 36257 action: { 36258 click: function(event, element) { 36259 modeling.addLane(element, 'top'); 36260 } 36261 } 36262 } 36263 }); 36264 36265 if (childLanes.length < 2) { 36266 36267 if (element.height >= 120) { 36268 assign(actions, { 36269 'lane-divide-two': { 36270 group: 'lane-divide', 36271 className: 'bpmn-icon-lane-divide-two', 36272 title: translate('Divide into two Lanes'), 36273 action: { 36274 click: splitLaneHandler(2) 36275 } 36276 } 36277 }); 36278 } 36279 36280 if (element.height >= 180) { 36281 assign(actions, { 36282 'lane-divide-three': { 36283 group: 'lane-divide', 36284 className: 'bpmn-icon-lane-divide-three', 36285 title: translate('Divide into three Lanes'), 36286 action: { 36287 click: splitLaneHandler(3) 36288 } 36289 } 36290 }); 36291 } 36292 } 36293 36294 assign(actions, { 36295 'lane-insert-below': { 36296 group: 'lane-insert-below', 36297 className: 'bpmn-icon-lane-insert-below', 36298 title: translate('Add Lane below'), 36299 action: { 36300 click: function(event, element) { 36301 modeling.addLane(element, 'bottom'); 36302 } 36303 } 36304 } 36305 }); 36306 36307 } 36308 36309 if (is$1(businessObject, 'bpmn:FlowNode')) { 36310 36311 if (is$1(businessObject, 'bpmn:EventBasedGateway')) { 36312 36313 assign(actions, { 36314 'append.receive-task': appendAction( 36315 'bpmn:ReceiveTask', 36316 'bpmn-icon-receive-task', 36317 translate('Append ReceiveTask') 36318 ), 36319 'append.message-intermediate-event': appendAction( 36320 'bpmn:IntermediateCatchEvent', 36321 'bpmn-icon-intermediate-event-catch-message', 36322 translate('Append MessageIntermediateCatchEvent'), 36323 { eventDefinitionType: 'bpmn:MessageEventDefinition' } 36324 ), 36325 'append.timer-intermediate-event': appendAction( 36326 'bpmn:IntermediateCatchEvent', 36327 'bpmn-icon-intermediate-event-catch-timer', 36328 translate('Append TimerIntermediateCatchEvent'), 36329 { eventDefinitionType: 'bpmn:TimerEventDefinition' } 36330 ), 36331 'append.condition-intermediate-event': appendAction( 36332 'bpmn:IntermediateCatchEvent', 36333 'bpmn-icon-intermediate-event-catch-condition', 36334 translate('Append ConditionIntermediateCatchEvent'), 36335 { eventDefinitionType: 'bpmn:ConditionalEventDefinition' } 36336 ), 36337 'append.signal-intermediate-event': appendAction( 36338 'bpmn:IntermediateCatchEvent', 36339 'bpmn-icon-intermediate-event-catch-signal', 36340 translate('Append SignalIntermediateCatchEvent'), 36341 { eventDefinitionType: 'bpmn:SignalEventDefinition' } 36342 ) 36343 }); 36344 } else 36345 36346 if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) { 36347 36348 assign(actions, { 36349 'append.compensation-activity': 36350 appendAction( 36351 'bpmn:Task', 36352 'bpmn-icon-task', 36353 translate('Append compensation activity'), 36354 { 36355 isForCompensation: true 36356 } 36357 ) 36358 }); 36359 } else 36360 36361 if (!is$1(businessObject, 'bpmn:EndEvent') && 36362 !businessObject.isForCompensation && 36363 !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') && 36364 !isEventSubProcess(businessObject)) { 36365 36366 assign(actions, { 36367 'append.end-event': appendAction( 36368 'bpmn:EndEvent', 36369 'bpmn-icon-end-event-none', 36370 translate('Append EndEvent') 36371 ), 36372 'append.gateway': appendAction( 36373 'bpmn:ExclusiveGateway', 36374 'bpmn-icon-gateway-none', 36375 translate('Append Gateway') 36376 ), 36377 'append.append-task': appendAction( 36378 'bpmn:Task', 36379 'bpmn-icon-task', 36380 translate('Append Task') 36381 ), 36382 'append.intermediate-event': appendAction( 36383 'bpmn:IntermediateThrowEvent', 36384 'bpmn-icon-intermediate-event-none', 36385 translate('Append Intermediate/Boundary Event') 36386 ) 36387 }); 36388 } 36389 } 36390 36391 if (!popupMenu.isEmpty(element, 'bpmn-replace')) { 36392 36393 // Replace menu entry 36394 assign(actions, { 36395 'replace': { 36396 group: 'edit', 36397 className: 'bpmn-icon-screw-wrench', 36398 title: translate('Change type'), 36399 action: { 36400 click: function(event, element) { 36401 36402 var position = assign(getReplaceMenuPosition(element), { 36403 cursor: { x: event.x, y: event.y } 36404 }); 36405 36406 popupMenu.open(element, 'bpmn-replace', position); 36407 } 36408 } 36409 } 36410 }); 36411 } 36412 36413 if ( 36414 isAny(businessObject, [ 36415 'bpmn:FlowNode', 36416 'bpmn:InteractionNode', 36417 'bpmn:DataObjectReference', 36418 'bpmn:DataStoreReference', 36419 ]) 36420 ) { 36421 assign(actions, { 36422 'append.text-annotation': appendAction( 36423 'bpmn:TextAnnotation', 36424 'bpmn-icon-text-annotation' 36425 ), 36426 36427 'connect': { 36428 group: 'connect', 36429 className: 'bpmn-icon-connection-multi', 36430 title: translate( 36431 'Connect using ' + 36432 (businessObject.isForCompensation 36433 ? '' 36434 : 'Sequence/MessageFlow or ') + 36435 'Association' 36436 ), 36437 action: { 36438 click: startConnect, 36439 dragstart: startConnect, 36440 }, 36441 }, 36442 }); 36443 } 36444 36445 if (is$1(businessObject, 'bpmn:TextAnnotation')) { 36446 assign(actions, { 36447 'connect': { 36448 group: 'connect', 36449 className: 'bpmn-icon-connection-multi', 36450 title: translate('Connect using Association'), 36451 action: { 36452 click: startConnect, 36453 dragstart: startConnect, 36454 }, 36455 }, 36456 }); 36457 } 36458 36459 if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) { 36460 assign(actions, { 36461 'connect': { 36462 group: 'connect', 36463 className: 'bpmn-icon-connection-multi', 36464 title: translate('Connect using DataInputAssociation'), 36465 action: { 36466 click: startConnect, 36467 dragstart: startConnect 36468 } 36469 } 36470 }); 36471 } 36472 36473 if (is$1(businessObject, 'bpmn:Group')) { 36474 assign(actions, { 36475 'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation') 36476 }); 36477 } 36478 36479 // delete element entry, only show if allowed by rules 36480 var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] }); 36481 36482 if (isArray$2(deleteAllowed)) { 36483 36484 // was the element returned as a deletion candidate? 36485 deleteAllowed = deleteAllowed[0] === element; 36486 } 36487 36488 if (deleteAllowed) { 36489 assign(actions, { 36490 'delete': { 36491 group: 'edit', 36492 className: 'bpmn-icon-trash', 36493 title: translate('Remove'), 36494 action: { 36495 click: removeElement 36496 } 36497 } 36498 }); 36499 } 36500 36501 return actions; 36502 }; 36503 36504 36505 // helpers ///////// 36506 36507 function isEventType(eventBo, type, definition) { 36508 36509 var isType = eventBo.$instanceOf(type); 36510 var isDefinition = false; 36511 36512 var definitions = eventBo.eventDefinitions || []; 36513 forEach(definitions, function(def) { 36514 if (def.$type === definition) { 36515 isDefinition = true; 36516 } 36517 }); 36518 36519 return isType && isDefinition; 36520 } 36521 36522 var ContextPadModule = { 36523 __depends__: [ 36524 DirectEditingModule, 36525 ContextPadModule$1, 36526 SelectionModule, 36527 ConnectModule, 36528 CreateModule, 36529 PopupMenuModule 36530 ], 36531 __init__: [ 'contextPadProvider' ], 36532 contextPadProvider: [ 'type', ContextPadProvider ] 36533 }; 36534 36535 var AXIS_DIMENSIONS = { 36536 horizontal: [ 'x', 'width' ], 36537 vertical: [ 'y', 'height' ] 36538 }; 36539 36540 var THRESHOLD = 5; 36541 36542 36543 /** 36544 * Groups and filters elements and then trigger even distribution. 36545 */ 36546 function DistributeElements$1(modeling) { 36547 this._modeling = modeling; 36548 36549 this._filters = []; 36550 36551 // register filter for filtering big elements 36552 this.registerFilter(function(elements, axis, dimension) { 36553 var elementsSize = 0, 36554 numOfShapes = 0, 36555 avgDimension; 36556 36557 forEach(elements, function(element) { 36558 if (element.waypoints || element.labelTarget) { 36559 return; 36560 } 36561 36562 elementsSize += element[dimension]; 36563 36564 numOfShapes += 1; 36565 }); 36566 36567 avgDimension = Math.round(elementsSize / numOfShapes); 36568 36569 return filter(elements, function(element) { 36570 return element[dimension] < (avgDimension + 50); 36571 }); 36572 }); 36573 36574 } 36575 36576 DistributeElements$1.$inject = [ 'modeling' ]; 36577 36578 36579 /** 36580 * Registers filter functions that allow external parties to filter 36581 * out certain elements. 36582 * 36583 * @param {Function} filterFn 36584 */ 36585 DistributeElements$1.prototype.registerFilter = function(filterFn) { 36586 if (typeof filterFn !== 'function') { 36587 throw new Error('the filter has to be a function'); 36588 } 36589 36590 this._filters.push(filterFn); 36591 }; 36592 36593 /** 36594 * Distributes the elements with a given orientation 36595 * 36596 * @param {Array} elements 36597 * @param {string} orientation 36598 */ 36599 DistributeElements$1.prototype.trigger = function(elements, orientation) { 36600 var modeling = this._modeling; 36601 36602 var groups, 36603 distributableElements; 36604 36605 if (elements.length < 3) { 36606 return; 36607 } 36608 36609 this._setOrientation(orientation); 36610 36611 distributableElements = this._filterElements(elements); 36612 36613 groups = this._createGroups(distributableElements); 36614 36615 // nothing to distribute 36616 if (groups.length <= 2) { 36617 return; 36618 } 36619 36620 modeling.distributeElements(groups, this._axis, this._dimension); 36621 36622 return groups; 36623 }; 36624 36625 /** 36626 * Filters the elements with provided filters by external parties 36627 * 36628 * @param {Array[Elements]} elements 36629 * 36630 * @return {Array[Elements]} 36631 */ 36632 DistributeElements$1.prototype._filterElements = function(elements) { 36633 var filters = this._filters, 36634 axis = this._axis, 36635 dimension = this._dimension, 36636 distributableElements = [].concat(elements); 36637 36638 if (!filters.length) { 36639 return elements; 36640 } 36641 36642 forEach(filters, function(filterFn) { 36643 distributableElements = filterFn(distributableElements, axis, dimension); 36644 }); 36645 36646 return distributableElements; 36647 }; 36648 36649 36650 /** 36651 * Create range (min, max) groups. Also tries to group elements 36652 * together that share the same range. 36653 * 36654 * @example 36655 * var distributableElements = [ 36656 * { 36657 * range: { 36658 * min: 100, 36659 * max: 200 36660 * }, 36661 * elements: [ { id: 'shape1', .. }] 36662 * } 36663 * ] 36664 * 36665 * @param {Array} elements 36666 * 36667 * @return {Array[Objects]} 36668 */ 36669 DistributeElements$1.prototype._createGroups = function(elements) { 36670 var rangeGroups = [], 36671 self = this, 36672 axis = this._axis, 36673 dimension = this._dimension; 36674 36675 if (!axis) { 36676 throw new Error('must have a defined "axis" and "dimension"'); 36677 } 36678 36679 // sort by 'left->right' or 'top->bottom' 36680 var sortedElements = sortBy(elements, axis); 36681 36682 forEach(sortedElements, function(element, idx) { 36683 var elementRange = self._findRange(element, axis, dimension), 36684 range; 36685 36686 var previous = rangeGroups[rangeGroups.length - 1]; 36687 36688 if (previous && self._hasIntersection(previous.range, elementRange)) { 36689 rangeGroups[rangeGroups.length - 1].elements.push(element); 36690 } else { 36691 range = { range: elementRange, elements: [ element ] }; 36692 36693 rangeGroups.push(range); 36694 } 36695 }); 36696 36697 return rangeGroups; 36698 }; 36699 36700 36701 /** 36702 * Maps a direction to the according axis and dimension 36703 * 36704 * @param {string} direction 'horizontal' or 'vertical' 36705 */ 36706 DistributeElements$1.prototype._setOrientation = function(direction) { 36707 var orientation = AXIS_DIMENSIONS[direction]; 36708 36709 this._axis = orientation[0]; 36710 this._dimension = orientation[1]; 36711 }; 36712 36713 36714 /** 36715 * Checks if the two ranges intercept each other 36716 * 36717 * @param {Object} rangeA {min, max} 36718 * @param {Object} rangeB {min, max} 36719 * 36720 * @return {boolean} 36721 */ 36722 DistributeElements$1.prototype._hasIntersection = function(rangeA, rangeB) { 36723 return Math.max(rangeA.min, rangeA.max) >= Math.min(rangeB.min, rangeB.max) && 36724 Math.min(rangeA.min, rangeA.max) <= Math.max(rangeB.min, rangeB.max); 36725 }; 36726 36727 36728 /** 36729 * Returns the min and max values for an element 36730 * 36731 * @param {Bounds} element 36732 * @param {string} axis 36733 * @param {string} dimension 36734 * 36735 * @return {{ min: number, max: number }} 36736 */ 36737 DistributeElements$1.prototype._findRange = function(element) { 36738 var axis = element[this._axis], 36739 dimension = element[this._dimension]; 36740 36741 return { 36742 min: axis + THRESHOLD, 36743 max: axis + dimension - THRESHOLD 36744 }; 36745 }; 36746 36747 var DistributeElementsModule$1 = { 36748 __init__: [ 'distributeElements' ], 36749 distributeElements: [ 'type', DistributeElements$1 ] 36750 }; 36751 36752 /** 36753 * Registers element exclude filters for elements that 36754 * currently do not support distribution. 36755 */ 36756 function BpmnDistributeElements(distributeElements) { 36757 36758 distributeElements.registerFilter(function(elements) { 36759 return filter(elements, function(element) { 36760 var cannotDistribute = isAny(element, [ 36761 'bpmn:Association', 36762 'bpmn:BoundaryEvent', 36763 'bpmn:DataInputAssociation', 36764 'bpmn:DataOutputAssociation', 36765 'bpmn:Lane', 36766 'bpmn:MessageFlow', 36767 'bpmn:Participant', 36768 'bpmn:SequenceFlow', 36769 'bpmn:TextAnnotation' 36770 ]); 36771 36772 return !(element.labelTarget || cannotDistribute); 36773 }); 36774 }); 36775 } 36776 36777 BpmnDistributeElements.$inject = [ 'distributeElements' ]; 36778 36779 var DistributeElementsModule = { 36780 __depends__: [ 36781 DistributeElementsModule$1 36782 ], 36783 __init__: [ 'bpmnDistributeElements' ], 36784 bpmnDistributeElements: [ 'type', BpmnDistributeElements ] 36785 }; 36786 36787 var NOT_REGISTERED_ERROR = 'is not a registered action', 36788 IS_REGISTERED_ERROR = 'is already registered'; 36789 36790 36791 /** 36792 * An interface that provides access to modeling actions by decoupling 36793 * the one who requests the action to be triggered and the trigger itself. 36794 * 36795 * It's possible to add new actions by registering them with ´registerAction´ 36796 * and likewise unregister existing ones with ´unregisterAction´. 36797 * 36798 * 36799 * ## Life-Cycle and configuration 36800 * 36801 * The editor actions will wait for diagram initialization before 36802 * registering default actions _and_ firing an `editorActions.init` event. 36803 * 36804 * Interested parties may listen to the `editorActions.init` event with 36805 * low priority to check, which actions got registered. Other components 36806 * may use the event to register their own actions via `registerAction`. 36807 * 36808 * @param {EventBus} eventBus 36809 * @param {Injector} injector 36810 */ 36811 function EditorActions(eventBus, injector) { 36812 36813 // initialize actions 36814 this._actions = {}; 36815 36816 var self = this; 36817 36818 eventBus.on('diagram.init', function() { 36819 36820 // all diagram modules got loaded; check which ones 36821 // are available and register the respective default actions 36822 self._registerDefaultActions(injector); 36823 36824 // ask interested parties to register available editor 36825 // actions on diagram initialization 36826 eventBus.fire('editorActions.init', { 36827 editorActions: self 36828 }); 36829 }); 36830 36831 } 36832 36833 EditorActions.$inject = [ 36834 'eventBus', 36835 'injector' 36836 ]; 36837 36838 /** 36839 * Register default actions. 36840 * 36841 * @param {Injector} injector 36842 */ 36843 EditorActions.prototype._registerDefaultActions = function(injector) { 36844 36845 // (1) retrieve optional components to integrate with 36846 36847 var commandStack = injector.get('commandStack', false); 36848 var modeling = injector.get('modeling', false); 36849 var selection = injector.get('selection', false); 36850 var zoomScroll = injector.get('zoomScroll', false); 36851 var copyPaste = injector.get('copyPaste', false); 36852 var canvas = injector.get('canvas', false); 36853 var rules = injector.get('rules', false); 36854 var keyboardMove = injector.get('keyboardMove', false); 36855 var keyboardMoveSelection = injector.get('keyboardMoveSelection', false); 36856 36857 // (2) check components and register actions 36858 36859 if (commandStack) { 36860 this.register('undo', function() { 36861 commandStack.undo(); 36862 }); 36863 36864 this.register('redo', function() { 36865 commandStack.redo(); 36866 }); 36867 } 36868 36869 if (copyPaste && selection) { 36870 this.register('copy', function() { 36871 var selectedElements = selection.get(); 36872 36873 copyPaste.copy(selectedElements); 36874 }); 36875 } 36876 36877 if (copyPaste) { 36878 this.register('paste', function() { 36879 copyPaste.paste(); 36880 }); 36881 } 36882 36883 if (zoomScroll) { 36884 this.register('stepZoom', function(opts) { 36885 zoomScroll.stepZoom(opts.value); 36886 }); 36887 } 36888 36889 if (canvas) { 36890 this.register('zoom', function(opts) { 36891 canvas.zoom(opts.value); 36892 }); 36893 } 36894 36895 if (modeling && selection && rules) { 36896 this.register('removeSelection', function() { 36897 36898 var selectedElements = selection.get(); 36899 36900 if (!selectedElements.length) { 36901 return; 36902 } 36903 36904 var allowed = rules.allowed('elements.delete', { elements: selectedElements }), 36905 removableElements; 36906 36907 if (allowed === false) { 36908 return; 36909 } 36910 else if (isArray$2(allowed)) { 36911 removableElements = allowed; 36912 } 36913 else { 36914 removableElements = selectedElements; 36915 } 36916 36917 if (removableElements.length) { 36918 modeling.removeElements(removableElements.slice()); 36919 } 36920 }); 36921 } 36922 36923 if (keyboardMove) { 36924 this.register('moveCanvas', function(opts) { 36925 keyboardMove.moveCanvas(opts); 36926 }); 36927 } 36928 36929 if (keyboardMoveSelection) { 36930 this.register('moveSelection', function(opts) { 36931 keyboardMoveSelection.moveSelection(opts.direction, opts.accelerated); 36932 }); 36933 } 36934 36935 }; 36936 36937 36938 /** 36939 * Triggers a registered action 36940 * 36941 * @param {string} action 36942 * @param {Object} opts 36943 * 36944 * @return {Unknown} Returns what the registered listener returns 36945 */ 36946 EditorActions.prototype.trigger = function(action, opts) { 36947 if (!this._actions[action]) { 36948 throw error(action, NOT_REGISTERED_ERROR); 36949 } 36950 36951 return this._actions[action](opts); 36952 }; 36953 36954 36955 /** 36956 * Registers a collections of actions. 36957 * The key of the object will be the name of the action. 36958 * 36959 * @example 36960 * ´´´ 36961 * var actions = { 36962 * spaceTool: function() { 36963 * spaceTool.activateSelection(); 36964 * }, 36965 * lassoTool: function() { 36966 * lassoTool.activateSelection(); 36967 * } 36968 * ]; 36969 * 36970 * editorActions.register(actions); 36971 * 36972 * editorActions.isRegistered('spaceTool'); // true 36973 * ´´´ 36974 * 36975 * @param {Object} actions 36976 */ 36977 EditorActions.prototype.register = function(actions, listener) { 36978 var self = this; 36979 36980 if (typeof actions === 'string') { 36981 return this._registerAction(actions, listener); 36982 } 36983 36984 forEach(actions, function(listener, action) { 36985 self._registerAction(action, listener); 36986 }); 36987 }; 36988 36989 /** 36990 * Registers a listener to an action key 36991 * 36992 * @param {string} action 36993 * @param {Function} listener 36994 */ 36995 EditorActions.prototype._registerAction = function(action, listener) { 36996 if (this.isRegistered(action)) { 36997 throw error(action, IS_REGISTERED_ERROR); 36998 } 36999 37000 this._actions[action] = listener; 37001 }; 37002 37003 /** 37004 * Unregister an existing action 37005 * 37006 * @param {string} action 37007 */ 37008 EditorActions.prototype.unregister = function(action) { 37009 if (!this.isRegistered(action)) { 37010 throw error(action, NOT_REGISTERED_ERROR); 37011 } 37012 37013 this._actions[action] = undefined; 37014 }; 37015 37016 /** 37017 * Returns the number of actions that are currently registered 37018 * 37019 * @return {number} 37020 */ 37021 EditorActions.prototype.getActions = function() { 37022 return Object.keys(this._actions); 37023 }; 37024 37025 /** 37026 * Checks wether the given action is registered 37027 * 37028 * @param {string} action 37029 * 37030 * @return {boolean} 37031 */ 37032 EditorActions.prototype.isRegistered = function(action) { 37033 return !!this._actions[action]; 37034 }; 37035 37036 37037 function error(action, message) { 37038 return new Error(action + ' ' + message); 37039 } 37040 37041 var EditorActionsModule$1 = { 37042 __init__: [ 'editorActions' ], 37043 editorActions: [ 'type', EditorActions ] 37044 }; 37045 37046 /** 37047 * Registers and executes BPMN specific editor actions. 37048 * 37049 * @param {Injector} injector 37050 */ 37051 function BpmnEditorActions(injector) { 37052 injector.invoke(EditorActions, this); 37053 } 37054 37055 inherits$1(BpmnEditorActions, EditorActions); 37056 37057 BpmnEditorActions.$inject = [ 37058 'injector' 37059 ]; 37060 37061 /** 37062 * Register default actions. 37063 * 37064 * @param {Injector} injector 37065 */ 37066 BpmnEditorActions.prototype._registerDefaultActions = function(injector) { 37067 37068 // (0) invoke super method 37069 37070 EditorActions.prototype._registerDefaultActions.call(this, injector); 37071 37072 // (1) retrieve optional components to integrate with 37073 37074 var canvas = injector.get('canvas', false); 37075 var elementRegistry = injector.get('elementRegistry', false); 37076 var selection = injector.get('selection', false); 37077 var spaceTool = injector.get('spaceTool', false); 37078 var lassoTool = injector.get('lassoTool', false); 37079 var handTool = injector.get('handTool', false); 37080 var globalConnect = injector.get('globalConnect', false); 37081 var distributeElements = injector.get('distributeElements', false); 37082 var alignElements = injector.get('alignElements', false); 37083 var directEditing = injector.get('directEditing', false); 37084 var searchPad = injector.get('searchPad', false); 37085 var modeling = injector.get('modeling', false); 37086 37087 // (2) check components and register actions 37088 37089 if (canvas && elementRegistry && selection) { 37090 this._registerAction('selectElements', function() { 37091 37092 // select all elements except for the invisible 37093 // root element 37094 var rootElement = canvas.getRootElement(); 37095 37096 var elements = elementRegistry.filter(function(element) { 37097 return element !== rootElement; 37098 }); 37099 37100 selection.select(elements); 37101 37102 return elements; 37103 }); 37104 } 37105 37106 if (spaceTool) { 37107 this._registerAction('spaceTool', function() { 37108 spaceTool.toggle(); 37109 }); 37110 } 37111 37112 if (lassoTool) { 37113 this._registerAction('lassoTool', function() { 37114 lassoTool.toggle(); 37115 }); 37116 } 37117 37118 if (handTool) { 37119 this._registerAction('handTool', function() { 37120 handTool.toggle(); 37121 }); 37122 } 37123 37124 if (globalConnect) { 37125 this._registerAction('globalConnectTool', function() { 37126 globalConnect.toggle(); 37127 }); 37128 } 37129 37130 if (selection && distributeElements) { 37131 this._registerAction('distributeElements', function(opts) { 37132 var currentSelection = selection.get(), 37133 type = opts.type; 37134 37135 if (currentSelection.length) { 37136 distributeElements.trigger(currentSelection, type); 37137 } 37138 }); 37139 } 37140 37141 if (selection && alignElements) { 37142 this._registerAction('alignElements', function(opts) { 37143 var currentSelection = selection.get(), 37144 aligneableElements = [], 37145 type = opts.type; 37146 37147 if (currentSelection.length) { 37148 aligneableElements = filter(currentSelection, function(element) { 37149 return !is$1(element, 'bpmn:Lane'); 37150 }); 37151 37152 alignElements.trigger(aligneableElements, type); 37153 } 37154 }); 37155 } 37156 37157 if (selection && modeling) { 37158 this._registerAction('setColor', function(opts) { 37159 var currentSelection = selection.get(); 37160 37161 if (currentSelection.length) { 37162 modeling.setColor(currentSelection, opts); 37163 } 37164 }); 37165 } 37166 37167 if (selection && directEditing) { 37168 this._registerAction('directEditing', function() { 37169 var currentSelection = selection.get(); 37170 37171 if (currentSelection.length) { 37172 directEditing.activate(currentSelection[0]); 37173 } 37174 }); 37175 } 37176 37177 if (searchPad) { 37178 this._registerAction('find', function() { 37179 searchPad.toggle(); 37180 }); 37181 } 37182 37183 if (canvas && modeling) { 37184 this._registerAction('moveToOrigin', function() { 37185 var rootElement = canvas.getRootElement(), 37186 boundingBox, 37187 elements; 37188 37189 if (is$1(rootElement, 'bpmn:Collaboration')) { 37190 elements = elementRegistry.filter(function(element) { 37191 return is$1(element.parent, 'bpmn:Collaboration'); 37192 }); 37193 } else { 37194 elements = elementRegistry.filter(function(element) { 37195 return element !== rootElement && !is$1(element.parent, 'bpmn:SubProcess'); 37196 }); 37197 } 37198 37199 boundingBox = getBBox(elements); 37200 37201 modeling.moveElements( 37202 elements, 37203 { x: -boundingBox.x, y: -boundingBox.y }, 37204 rootElement 37205 ); 37206 }); 37207 } 37208 37209 }; 37210 37211 var EditorActionsModule = { 37212 __depends__: [ 37213 EditorActionsModule$1 37214 ], 37215 editorActions: [ 'type', BpmnEditorActions ] 37216 }; 37217 37218 function BpmnGridSnapping(eventBus) { 37219 eventBus.on([ 37220 'create.init', 37221 'shape.move.init' 37222 ], function(event) { 37223 var context = event.context, 37224 shape = event.shape; 37225 37226 if (isAny(shape, [ 37227 'bpmn:Participant', 37228 'bpmn:SubProcess', 37229 'bpmn:TextAnnotation' 37230 ])) { 37231 if (!context.gridSnappingContext) { 37232 context.gridSnappingContext = {}; 37233 } 37234 37235 context.gridSnappingContext.snapLocation = 'top-left'; 37236 } 37237 }); 37238 } 37239 37240 BpmnGridSnapping.$inject = [ 'eventBus' ]; 37241 37242 var SPACING = 10; 37243 37244 function quantize(value, quantum, fn) { 37245 if (!fn) { 37246 fn = 'round'; 37247 } 37248 37249 return Math[ fn ](value / quantum) * quantum; 37250 } 37251 37252 var LOWER_PRIORITY = 1200; 37253 var LOW_PRIORITY$e = 800; 37254 37255 /** 37256 * Basic grid snapping that covers connecting, creating, moving, resizing shapes, moving bendpoints 37257 * and connection segments. 37258 */ 37259 function GridSnapping(elementRegistry, eventBus, config) { 37260 37261 var active = !config || config.active !== false; 37262 37263 this._eventBus = eventBus; 37264 37265 var self = this; 37266 37267 eventBus.on('diagram.init', LOW_PRIORITY$e, function() { 37268 self.setActive(active); 37269 }); 37270 37271 eventBus.on([ 37272 'create.move', 37273 'create.end', 37274 'bendpoint.move.move', 37275 'bendpoint.move.end', 37276 'connect.move', 37277 'connect.end', 37278 'connectionSegment.move.move', 37279 'connectionSegment.move.end', 37280 'resize.move', 37281 'resize.end', 37282 'shape.move.move', 37283 'shape.move.end' 37284 ], LOWER_PRIORITY, function(event) { 37285 var originalEvent = event.originalEvent; 37286 37287 if (!self.active || (originalEvent && isCmd(originalEvent))) { 37288 return; 37289 } 37290 37291 var context = event.context, 37292 gridSnappingContext = context.gridSnappingContext; 37293 37294 if (!gridSnappingContext) { 37295 gridSnappingContext = context.gridSnappingContext = {}; 37296 } 37297 37298 [ 'x', 'y' ].forEach(function(axis) { 37299 var options = {}; 37300 37301 // allow snapping with offset 37302 var snapOffset = getSnapOffset(event, axis, elementRegistry); 37303 37304 if (snapOffset) { 37305 options.offset = snapOffset; 37306 } 37307 37308 // allow snapping with min and max 37309 var snapConstraints = getSnapConstraints(event, axis); 37310 37311 if (snapConstraints) { 37312 assign(options, snapConstraints); 37313 } 37314 37315 if (!isSnapped(event, axis)) { 37316 self.snapEvent(event, axis, options); 37317 } 37318 }); 37319 }); 37320 } 37321 37322 /** 37323 * Snap an events x or y with optional min, max and offset. 37324 * 37325 * @param {Object} event 37326 * @param {string} axis 37327 * @param {number} [options.min] 37328 * @param {number} [options.max] 37329 * @param {number} [options.offset] 37330 */ 37331 GridSnapping.prototype.snapEvent = function(event, axis, options) { 37332 var snappedValue = this.snapValue(event[ axis ], options); 37333 37334 setSnapped(event, axis, snappedValue); 37335 }; 37336 37337 /** 37338 * Expose grid spacing for third parties (i.e. extensions). 37339 * 37340 * @return {number} spacing of grid dots 37341 */ 37342 GridSnapping.prototype.getGridSpacing = function() { 37343 return SPACING; 37344 }; 37345 37346 /** 37347 * Snap value with optional min, max and offset. 37348 * 37349 * @param {number} value 37350 * @param {Object} options 37351 * @param {number} [options.min] 37352 * @param {number} [options.max] 37353 * @param {number} [options.offset] 37354 */ 37355 GridSnapping.prototype.snapValue = function(value, options) { 37356 var offset = 0; 37357 37358 if (options && options.offset) { 37359 offset = options.offset; 37360 } 37361 37362 value += offset; 37363 37364 value = quantize(value, SPACING); 37365 37366 var min, max; 37367 37368 if (options && options.min) { 37369 min = options.min; 37370 37371 if (isNumber(min)) { 37372 min = quantize(min + offset, SPACING, 'ceil'); 37373 37374 value = Math.max(value, min); 37375 } 37376 } 37377 37378 if (options && options.max) { 37379 max = options.max; 37380 37381 if (isNumber(max)) { 37382 max = quantize(max + offset, SPACING, 'floor'); 37383 37384 value = Math.min(value, max); 37385 } 37386 } 37387 37388 value -= offset; 37389 37390 return value; 37391 }; 37392 37393 GridSnapping.prototype.isActive = function() { 37394 return this.active; 37395 }; 37396 37397 GridSnapping.prototype.setActive = function(active) { 37398 this.active = active; 37399 37400 this._eventBus.fire('gridSnapping.toggle', { active: active }); 37401 }; 37402 37403 GridSnapping.prototype.toggleActive = function() { 37404 this.setActive(!this.active); 37405 }; 37406 37407 GridSnapping.$inject = [ 37408 'elementRegistry', 37409 'eventBus', 37410 'config.gridSnapping' 37411 ]; 37412 37413 // helpers ////////// 37414 37415 /** 37416 * Get minimum and maximum snap constraints. 37417 * Constraints are cached. 37418 * 37419 * @param {Object} event 37420 * @param {Object} event.context 37421 * @param {string} axis 37422 * 37423 * @returns {boolean|Object} 37424 */ 37425 function getSnapConstraints(event, axis) { 37426 var context = event.context, 37427 createConstraints = context.createConstraints, 37428 resizeConstraints = context.resizeConstraints || {}, 37429 gridSnappingContext = context.gridSnappingContext, 37430 snapConstraints = gridSnappingContext.snapConstraints; 37431 37432 // cache snap constraints 37433 if (snapConstraints && snapConstraints[ axis ]) { 37434 return snapConstraints[ axis ]; 37435 } 37436 37437 if (!snapConstraints) { 37438 snapConstraints = gridSnappingContext.snapConstraints = {}; 37439 } 37440 37441 if (!snapConstraints[ axis ]) { 37442 snapConstraints[ axis ] = {}; 37443 } 37444 37445 var direction = context.direction; 37446 37447 // create 37448 if (createConstraints) { 37449 if (isHorizontal$3(axis)) { 37450 snapConstraints.x.min = createConstraints.left; 37451 snapConstraints.x.max = createConstraints.right; 37452 } else { 37453 snapConstraints.y.min = createConstraints.top; 37454 snapConstraints.y.max = createConstraints.bottom; 37455 } 37456 } 37457 37458 // resize 37459 var minResizeConstraints = resizeConstraints.min, 37460 maxResizeConstraints = resizeConstraints.max; 37461 37462 if (minResizeConstraints) { 37463 if (isHorizontal$3(axis)) { 37464 37465 if (isWest(direction)) { 37466 snapConstraints.x.max = minResizeConstraints.left; 37467 } else { 37468 snapConstraints.x.min = minResizeConstraints.right; 37469 } 37470 37471 } else { 37472 37473 if (isNorth(direction)) { 37474 snapConstraints.y.max = minResizeConstraints.top; 37475 } else { 37476 snapConstraints.y.min = minResizeConstraints.bottom; 37477 } 37478 37479 } 37480 } 37481 37482 if (maxResizeConstraints) { 37483 if (isHorizontal$3(axis)) { 37484 37485 if (isWest(direction)) { 37486 snapConstraints.x.min = maxResizeConstraints.left; 37487 } else { 37488 snapConstraints.x.max = maxResizeConstraints.right; 37489 } 37490 37491 } else { 37492 37493 if (isNorth(direction)) { 37494 snapConstraints.y.min = maxResizeConstraints.top; 37495 } else { 37496 snapConstraints.y.max = maxResizeConstraints.bottom; 37497 } 37498 37499 } 37500 } 37501 37502 return snapConstraints[ axis ]; 37503 } 37504 37505 /** 37506 * Get snap offset. 37507 * Offset is cached. 37508 * 37509 * @param {Object} event 37510 * @param {string} axis 37511 * @param {ElementRegistry} elementRegistry 37512 * 37513 * @returns {number} 37514 */ 37515 function getSnapOffset(event, axis, elementRegistry) { 37516 var context = event.context, 37517 shape = event.shape, 37518 gridSnappingContext = context.gridSnappingContext, 37519 snapLocation = gridSnappingContext.snapLocation, 37520 snapOffset = gridSnappingContext.snapOffset; 37521 37522 // cache snap offset 37523 if (snapOffset && isNumber(snapOffset[ axis ])) { 37524 return snapOffset[ axis ]; 37525 } 37526 37527 if (!snapOffset) { 37528 snapOffset = gridSnappingContext.snapOffset = {}; 37529 } 37530 37531 if (!isNumber(snapOffset[ axis ])) { 37532 snapOffset[ axis ] = 0; 37533 } 37534 37535 if (!shape) { 37536 return snapOffset[ axis ]; 37537 } 37538 37539 if (!elementRegistry.get(shape.id)) { 37540 37541 if (isHorizontal$3(axis)) { 37542 snapOffset[ axis ] += shape[ axis ] + shape.width / 2; 37543 } else { 37544 snapOffset[ axis ] += shape[ axis ] + shape.height / 2; 37545 } 37546 } 37547 37548 if (!snapLocation) { 37549 return snapOffset[ axis ]; 37550 } 37551 37552 if (axis === 'x') { 37553 if (/left/.test(snapLocation)) { 37554 snapOffset[ axis ] -= shape.width / 2; 37555 } else if (/right/.test(snapLocation)) { 37556 snapOffset[ axis ] += shape.width / 2; 37557 } 37558 } else { 37559 if (/top/.test(snapLocation)) { 37560 snapOffset[ axis ] -= shape.height / 2; 37561 } else if (/bottom/.test(snapLocation)) { 37562 snapOffset[ axis ] += shape.height / 2; 37563 } 37564 } 37565 37566 return snapOffset[ axis ]; 37567 } 37568 37569 function isHorizontal$3(axis) { 37570 return axis === 'x'; 37571 } 37572 37573 function isNorth(direction) { 37574 return direction.indexOf('n') !== -1; 37575 } 37576 37577 function isWest(direction) { 37578 return direction.indexOf('w') !== -1; 37579 } 37580 37581 /** 37582 * Integrates resizing with grid snapping. 37583 */ 37584 function ResizeBehavior$1(eventBus, gridSnapping) { 37585 CommandInterceptor.call(this, eventBus); 37586 37587 this._gridSnapping = gridSnapping; 37588 37589 var self = this; 37590 37591 this.preExecute('shape.resize', function(event) { 37592 var context = event.context, 37593 hints = context.hints || {}, 37594 autoResize = hints.autoResize; 37595 37596 if (!autoResize) { 37597 return; 37598 } 37599 37600 var shape = context.shape, 37601 newBounds = context.newBounds; 37602 37603 if (isString(autoResize)) { 37604 context.newBounds = self.snapComplex(newBounds, autoResize); 37605 } else { 37606 context.newBounds = self.snapSimple(shape, newBounds); 37607 } 37608 }); 37609 } 37610 37611 ResizeBehavior$1.$inject = [ 37612 'eventBus', 37613 'gridSnapping', 37614 'modeling' 37615 ]; 37616 37617 inherits$1(ResizeBehavior$1, CommandInterceptor); 37618 37619 /** 37620 * Snap width and height in relation to center. 37621 * 37622 * @param {djs.model.shape} shape 37623 * @param {Bounds} newBounds 37624 * 37625 * @returns {Bounds} Snapped bounds. 37626 */ 37627 ResizeBehavior$1.prototype.snapSimple = function(shape, newBounds) { 37628 var gridSnapping = this._gridSnapping; 37629 37630 newBounds.width = gridSnapping.snapValue(newBounds.width, { 37631 min: newBounds.width 37632 }); 37633 37634 newBounds.height = gridSnapping.snapValue(newBounds.height, { 37635 min: newBounds.height 37636 }); 37637 37638 newBounds.x = shape.x + (shape.width / 2) - (newBounds.width / 2); 37639 newBounds.y = shape.y + (shape.height / 2) - (newBounds.height / 2); 37640 37641 return newBounds; 37642 }; 37643 37644 /** 37645 * Snap x, y, width and height according to given directions. 37646 * 37647 * @param {Bounds} newBounds 37648 * @param {string} directions - Directions as {n|w|s|e}. 37649 * 37650 * @returns {Bounds} Snapped bounds. 37651 */ 37652 ResizeBehavior$1.prototype.snapComplex = function(newBounds, directions) { 37653 if (/w|e/.test(directions)) { 37654 newBounds = this.snapHorizontally(newBounds, directions); 37655 } 37656 37657 if (/n|s/.test(directions)) { 37658 newBounds = this.snapVertically(newBounds, directions); 37659 } 37660 37661 return newBounds; 37662 }; 37663 37664 /** 37665 * Snap in one or both directions horizontally. 37666 * 37667 * @param {Bounds} newBounds 37668 * @param {string} directions - Directions as {n|w|s|e}. 37669 * 37670 * @returns {Bounds} Snapped bounds. 37671 */ 37672 ResizeBehavior$1.prototype.snapHorizontally = function(newBounds, directions) { 37673 var gridSnapping = this._gridSnapping, 37674 west = /w/.test(directions), 37675 east = /e/.test(directions); 37676 37677 var snappedNewBounds = {}; 37678 37679 snappedNewBounds.width = gridSnapping.snapValue(newBounds.width, { 37680 min: newBounds.width 37681 }); 37682 37683 if (east) { 37684 37685 // handle <we> 37686 if (west) { 37687 snappedNewBounds.x = gridSnapping.snapValue(newBounds.x, { 37688 max: newBounds.x 37689 }); 37690 37691 snappedNewBounds.width += gridSnapping.snapValue(newBounds.x - snappedNewBounds.x, { 37692 min: newBounds.x - snappedNewBounds.x 37693 }); 37694 } 37695 37696 // handle <e> 37697 else { 37698 newBounds.x = newBounds.x + newBounds.width - snappedNewBounds.width; 37699 } 37700 } 37701 37702 // assign snapped x and width 37703 assign(newBounds, snappedNewBounds); 37704 37705 return newBounds; 37706 }; 37707 37708 /** 37709 * Snap in one or both directions vertically. 37710 * 37711 * @param {Bounds} newBounds 37712 * @param {string} directions - Directions as {n|w|s|e}. 37713 * 37714 * @returns {Bounds} Snapped bounds. 37715 */ 37716 ResizeBehavior$1.prototype.snapVertically = function(newBounds, directions) { 37717 var gridSnapping = this._gridSnapping, 37718 north = /n/.test(directions), 37719 south = /s/.test(directions); 37720 37721 var snappedNewBounds = {}; 37722 37723 snappedNewBounds.height = gridSnapping.snapValue(newBounds.height, { 37724 min: newBounds.height 37725 }); 37726 37727 if (north) { 37728 37729 // handle <ns> 37730 if (south) { 37731 snappedNewBounds.y = gridSnapping.snapValue(newBounds.y, { 37732 max: newBounds.y 37733 }); 37734 37735 snappedNewBounds.height += gridSnapping.snapValue(newBounds.y - snappedNewBounds.y, { 37736 min: newBounds.y - snappedNewBounds.y 37737 }); 37738 } 37739 37740 // handle <n> 37741 else { 37742 newBounds.y = newBounds.y + newBounds.height - snappedNewBounds.height; 37743 } 37744 } 37745 37746 // assign snapped y and height 37747 assign(newBounds, snappedNewBounds); 37748 37749 return newBounds; 37750 }; 37751 37752 var HIGH_PRIORITY$f = 2000; 37753 37754 /** 37755 * Integrates space tool with grid snapping. 37756 */ 37757 function SpaceToolBehavior$1(eventBus, gridSnapping) { 37758 eventBus.on([ 37759 'spaceTool.move', 37760 'spaceTool.end' 37761 ], HIGH_PRIORITY$f, function(event) { 37762 var context = event.context; 37763 37764 if (!context.initialized) { 37765 return; 37766 } 37767 37768 var axis = context.axis; 37769 37770 var snapped; 37771 37772 if (axis === 'x') { 37773 37774 // snap delta x to multiple of 10 37775 snapped = gridSnapping.snapValue(event.dx); 37776 37777 event.x = event.x + snapped - event.dx; 37778 event.dx = snapped; 37779 } else { 37780 37781 // snap delta y to multiple of 10 37782 snapped = gridSnapping.snapValue(event.dy); 37783 37784 event.y = event.y + snapped - event.dy; 37785 event.dy = snapped; 37786 } 37787 }); 37788 } 37789 37790 SpaceToolBehavior$1.$inject = [ 37791 'eventBus', 37792 'gridSnapping' 37793 ]; 37794 37795 var GridSnappingBehaviorModule$1 = { 37796 __init__: [ 37797 'gridSnappingResizeBehavior', 37798 'gridSnappingSpaceToolBehavior' 37799 ], 37800 gridSnappingResizeBehavior: [ 'type', ResizeBehavior$1 ], 37801 gridSnappingSpaceToolBehavior: [ 'type', SpaceToolBehavior$1 ] 37802 }; 37803 37804 var GridSnappingModule$1 = { 37805 __depends__: [ GridSnappingBehaviorModule$1 ], 37806 __init__: [ 'gridSnapping' ], 37807 gridSnapping: [ 'type', GridSnapping ] 37808 }; 37809 37810 var HIGH_PRIORITY$e = 2000; 37811 37812 37813 function AutoPlaceBehavior(eventBus, gridSnapping) { 37814 eventBus.on('autoPlace', HIGH_PRIORITY$e, function(context) { 37815 var source = context.source, 37816 sourceMid = getMid(source), 37817 shape = context.shape; 37818 37819 var position = getNewShapePosition(source, shape); 37820 37821 [ 'x', 'y' ].forEach(function(axis) { 37822 var options = {}; 37823 37824 // do not snap if x/y equal 37825 if (position[ axis ] === sourceMid[ axis ]) { 37826 return; 37827 } 37828 37829 if (position[ axis ] > sourceMid[ axis ]) { 37830 options.min = position[ axis ]; 37831 } else { 37832 options.max = position[ axis ]; 37833 } 37834 37835 if (is$1(shape, 'bpmn:TextAnnotation')) { 37836 37837 if (isHorizontal$2(axis)) { 37838 options.offset = -shape.width / 2; 37839 } else { 37840 options.offset = -shape.height / 2; 37841 } 37842 37843 } 37844 37845 position[ axis ] = gridSnapping.snapValue(position[ axis ], options); 37846 37847 }); 37848 37849 // must be returned to be considered by auto place 37850 return position; 37851 }); 37852 } 37853 37854 AutoPlaceBehavior.$inject = [ 37855 'eventBus', 37856 'gridSnapping' 37857 ]; 37858 37859 // helpers ////////// 37860 37861 function isHorizontal$2(axis) { 37862 return axis === 'x'; 37863 } 37864 37865 var HIGHER_PRIORITY$4 = 1750; 37866 37867 37868 function CreateParticipantBehavior$1(canvas, eventBus, gridSnapping) { 37869 eventBus.on([ 37870 'create.start', 37871 'shape.move.start' 37872 ], HIGHER_PRIORITY$4, function(event) { 37873 var context = event.context, 37874 shape = context.shape, 37875 rootElement = canvas.getRootElement(); 37876 37877 if (!is$1(shape, 'bpmn:Participant') || 37878 !is$1(rootElement, 'bpmn:Process') || 37879 !rootElement.children.length) { 37880 return; 37881 } 37882 37883 var createConstraints = context.createConstraints; 37884 37885 if (!createConstraints) { 37886 return; 37887 } 37888 37889 shape.width = gridSnapping.snapValue(shape.width, { min: shape.width }); 37890 shape.height = gridSnapping.snapValue(shape.height, { min: shape.height }); 37891 }); 37892 } 37893 37894 CreateParticipantBehavior$1.$inject = [ 37895 'canvas', 37896 'eventBus', 37897 'gridSnapping' 37898 ]; 37899 37900 var HIGH_PRIORITY$d = 3000; 37901 37902 37903 /** 37904 * Snaps connections with Manhattan layout. 37905 */ 37906 function LayoutConnectionBehavior(eventBus, gridSnapping, modeling) { 37907 CommandInterceptor.call(this, eventBus); 37908 37909 this._gridSnapping = gridSnapping; 37910 37911 var self = this; 37912 37913 this.postExecuted([ 37914 'connection.create', 37915 'connection.layout' 37916 ], HIGH_PRIORITY$d, function(event) { 37917 var context = event.context, 37918 connection = context.connection, 37919 hints = context.hints || {}, 37920 waypoints = connection.waypoints; 37921 37922 if (hints.connectionStart || hints.connectionEnd || hints.createElementsBehavior === false) { 37923 return; 37924 } 37925 37926 if (!hasMiddleSegments(waypoints)) { 37927 return; 37928 } 37929 37930 modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints)); 37931 }); 37932 } 37933 37934 LayoutConnectionBehavior.$inject = [ 37935 'eventBus', 37936 'gridSnapping', 37937 'modeling' 37938 ]; 37939 37940 inherits$1(LayoutConnectionBehavior, CommandInterceptor); 37941 37942 /** 37943 * Snap middle segments of a given connection. 37944 * 37945 * @param {Array<Point>} waypoints 37946 * 37947 * @returns {Array<Point>} 37948 */ 37949 LayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) { 37950 var gridSnapping = this._gridSnapping, 37951 snapped; 37952 37953 waypoints = waypoints.slice(); 37954 37955 for (var i = 1; i < waypoints.length - 2; i++) { 37956 37957 snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]); 37958 37959 waypoints[i] = snapped[0]; 37960 waypoints[i + 1] = snapped[1]; 37961 } 37962 37963 return waypoints; 37964 }; 37965 37966 37967 // helpers ////////// 37968 37969 /** 37970 * Check whether a connection has a middle segments. 37971 * 37972 * @param {Array} waypoints 37973 * 37974 * @returns {boolean} 37975 */ 37976 function hasMiddleSegments(waypoints) { 37977 return waypoints.length > 3; 37978 } 37979 37980 /** 37981 * Check whether an alignment is horizontal. 37982 * 37983 * @param {string} aligned 37984 * 37985 * @returns {boolean} 37986 */ 37987 function horizontallyAligned(aligned) { 37988 return aligned === 'h'; 37989 } 37990 37991 /** 37992 * Check whether an alignment is vertical. 37993 * 37994 * @param {string} aligned 37995 * 37996 * @returns {boolean} 37997 */ 37998 function verticallyAligned(aligned) { 37999 return aligned === 'v'; 38000 } 38001 38002 /** 38003 * Get middle segments from a given connection. 38004 * 38005 * @param {Array} waypoints 38006 * 38007 * @returns {Array} 38008 */ 38009 function snapSegment(gridSnapping, segmentStart, segmentEnd) { 38010 38011 var aligned = pointsAligned(segmentStart, segmentEnd); 38012 38013 var snapped = {}; 38014 38015 if (horizontallyAligned(aligned)) { 38016 38017 // snap horizontally 38018 snapped.y = gridSnapping.snapValue(segmentStart.y); 38019 } 38020 38021 if (verticallyAligned(aligned)) { 38022 38023 // snap vertically 38024 snapped.x = gridSnapping.snapValue(segmentStart.x); 38025 } 38026 38027 if ('x' in snapped || 'y' in snapped) { 38028 segmentStart = assign({}, segmentStart, snapped); 38029 segmentEnd = assign({}, segmentEnd, snapped); 38030 } 38031 38032 return [ segmentStart, segmentEnd ]; 38033 } 38034 38035 var GridSnappingBehaviorModule = { 38036 __init__: [ 38037 'gridSnappingAutoPlaceBehavior', 38038 'gridSnappingCreateParticipantBehavior', 38039 'gridSnappingLayoutConnectionBehavior', 38040 ], 38041 gridSnappingAutoPlaceBehavior: [ 'type', AutoPlaceBehavior ], 38042 gridSnappingCreateParticipantBehavior: [ 'type', CreateParticipantBehavior$1 ], 38043 gridSnappingLayoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ] 38044 }; 38045 38046 var GridSnappingModule = { 38047 __depends__: [ 38048 GridSnappingModule$1, 38049 GridSnappingBehaviorModule 38050 ], 38051 __init__: [ 'bpmnGridSnapping' ], 38052 bpmnGridSnapping: [ 'type', BpmnGridSnapping ] 38053 }; 38054 38055 var LABEL_WIDTH = 30, 38056 LABEL_HEIGHT = 30; 38057 38058 38059 /** 38060 * BPMN-specific hit zones and interaction fixes. 38061 * 38062 * @param {EventBus} eventBus 38063 * @param {InteractionEvents} interactionEvents 38064 */ 38065 function BpmnInteractionEvents(eventBus, interactionEvents) { 38066 38067 this._interactionEvents = interactionEvents; 38068 38069 var self = this; 38070 38071 eventBus.on([ 38072 'interactionEvents.createHit', 38073 'interactionEvents.updateHit' 38074 ], function(context) { 38075 var element = context.element, 38076 gfx = context.gfx; 38077 38078 if (is$1(element, 'bpmn:Lane')) { 38079 return self.createParticipantHit(element, gfx); 38080 } else 38081 38082 if (is$1(element, 'bpmn:Participant')) { 38083 if (isExpanded(element)) { 38084 return self.createParticipantHit(element, gfx); 38085 } else { 38086 return self.createDefaultHit(element, gfx); 38087 } 38088 } else 38089 38090 if (is$1(element, 'bpmn:SubProcess')) { 38091 if (isExpanded(element)) { 38092 return self.createSubProcessHit(element, gfx); 38093 } else { 38094 return self.createDefaultHit(element, gfx); 38095 } 38096 } 38097 }); 38098 38099 } 38100 38101 BpmnInteractionEvents.$inject = [ 38102 'eventBus', 38103 'interactionEvents' 38104 ]; 38105 38106 38107 BpmnInteractionEvents.prototype.createDefaultHit = function(element, gfx) { 38108 this._interactionEvents.removeHits(gfx); 38109 38110 this._interactionEvents.createDefaultHit(element, gfx); 38111 38112 // indicate that we created a hit 38113 return true; 38114 }; 38115 38116 BpmnInteractionEvents.prototype.createParticipantHit = function(element, gfx) { 38117 38118 // remove existing hits 38119 this._interactionEvents.removeHits(gfx); 38120 38121 // add outline hit 38122 this._interactionEvents.createBoxHit(gfx, 'click-stroke', { 38123 width: element.width, 38124 height: element.height 38125 }); 38126 38127 // add label hit 38128 this._interactionEvents.createBoxHit(gfx, 'all', { 38129 width: LABEL_WIDTH, 38130 height: element.height 38131 }); 38132 38133 // indicate that we created a hit 38134 return true; 38135 }; 38136 38137 BpmnInteractionEvents.prototype.createSubProcessHit = function(element, gfx) { 38138 38139 // remove existing hits 38140 this._interactionEvents.removeHits(gfx); 38141 38142 // add outline hit 38143 this._interactionEvents.createBoxHit(gfx, 'click-stroke', { 38144 width: element.width, 38145 height: element.height 38146 }); 38147 38148 // add label hit 38149 this._interactionEvents.createBoxHit(gfx, 'all', { 38150 width: element.width, 38151 height: LABEL_HEIGHT 38152 }); 38153 38154 // indicate that we created a hit 38155 return true; 38156 }; 38157 38158 var InteractionEventsModule = { 38159 __init__: [ 'bpmnInteractionEvents' ], 38160 bpmnInteractionEvents: [ 'type', BpmnInteractionEvents ] 38161 }; 38162 38163 /** 38164 * BPMN 2.0 specific keyboard bindings. 38165 * 38166 * @param {Injector} injector 38167 */ 38168 function BpmnKeyboardBindings(injector) { 38169 injector.invoke(KeyboardBindings, this); 38170 } 38171 38172 inherits$1(BpmnKeyboardBindings, KeyboardBindings); 38173 38174 BpmnKeyboardBindings.$inject = [ 38175 'injector' 38176 ]; 38177 38178 38179 /** 38180 * Register available keyboard bindings. 38181 * 38182 * @param {Keyboard} keyboard 38183 * @param {EditorActions} editorActions 38184 */ 38185 BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) { 38186 38187 // inherit default bindings 38188 KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions); 38189 38190 /** 38191 * Add keyboard binding if respective editor action 38192 * is registered. 38193 * 38194 * @param {string} action name 38195 * @param {Function} fn that implements the key binding 38196 */ 38197 function addListener(action, fn) { 38198 38199 if (editorActions.isRegistered(action)) { 38200 keyboard.addListener(fn); 38201 } 38202 } 38203 38204 // select all elements 38205 // CTRL + A 38206 addListener('selectElements', function(context) { 38207 38208 var event = context.keyEvent; 38209 38210 if (keyboard.isKey(['a', 'A'], event) && keyboard.isCmd(event)) { 38211 editorActions.trigger('selectElements'); 38212 38213 return true; 38214 } 38215 }); 38216 38217 // search labels 38218 // CTRL + F 38219 addListener('find', function(context) { 38220 38221 var event = context.keyEvent; 38222 38223 if (keyboard.isKey(['f', 'F'], event) && keyboard.isCmd(event)) { 38224 editorActions.trigger('find'); 38225 38226 return true; 38227 } 38228 }); 38229 38230 // activate space tool 38231 // S 38232 addListener('spaceTool', function(context) { 38233 38234 var event = context.keyEvent; 38235 38236 if (keyboard.hasModifier(event)) { 38237 return; 38238 } 38239 38240 if (keyboard.isKey(['s', 'S'], event)) { 38241 editorActions.trigger('spaceTool'); 38242 38243 return true; 38244 } 38245 }); 38246 38247 // activate lasso tool 38248 // L 38249 addListener('lassoTool', function(context) { 38250 38251 var event = context.keyEvent; 38252 38253 if (keyboard.hasModifier(event)) { 38254 return; 38255 } 38256 38257 if (keyboard.isKey(['l', 'L'], event)) { 38258 editorActions.trigger('lassoTool'); 38259 38260 return true; 38261 } 38262 }); 38263 38264 // activate hand tool 38265 // H 38266 addListener('handTool', function(context) { 38267 38268 var event = context.keyEvent; 38269 38270 if (keyboard.hasModifier(event)) { 38271 return; 38272 } 38273 38274 if (keyboard.isKey(['h', 'H'], event)) { 38275 editorActions.trigger('handTool'); 38276 38277 return true; 38278 } 38279 }); 38280 38281 // activate global connect tool 38282 // C 38283 addListener('globalConnectTool', function(context) { 38284 38285 var event = context.keyEvent; 38286 38287 if (keyboard.hasModifier(event)) { 38288 return; 38289 } 38290 38291 if (keyboard.isKey(['c', 'C'], event)) { 38292 editorActions.trigger('globalConnectTool'); 38293 38294 return true; 38295 } 38296 }); 38297 38298 // activate direct editing 38299 // E 38300 addListener('directEditing', function(context) { 38301 38302 var event = context.keyEvent; 38303 38304 if (keyboard.hasModifier(event)) { 38305 return; 38306 } 38307 38308 if (keyboard.isKey(['e', 'E'], event)) { 38309 editorActions.trigger('directEditing'); 38310 38311 return true; 38312 } 38313 }); 38314 38315 }; 38316 38317 var KeyboardModule = { 38318 __depends__: [ 38319 KeyboardModule$1 38320 ], 38321 __init__: [ 'keyboardBindings' ], 38322 keyboardBindings: [ 'type', BpmnKeyboardBindings ] 38323 }; 38324 38325 var DEFAULT_CONFIG = { 38326 moveSpeed: 1, 38327 moveSpeedAccelerated: 10 38328 }; 38329 38330 var HIGHER_PRIORITY$3 = 1500; 38331 38332 var LEFT = 'left'; 38333 var UP = 'up'; 38334 var RIGHT = 'right'; 38335 var DOWN = 'down'; 38336 38337 var KEY_TO_DIRECTION = { 38338 ArrowLeft: LEFT, 38339 Left: LEFT, 38340 ArrowUp: UP, 38341 Up: UP, 38342 ArrowRight: RIGHT, 38343 Right: RIGHT, 38344 ArrowDown: DOWN, 38345 Down: DOWN 38346 }; 38347 38348 var DIRECTIONS_DELTA = { 38349 left: function(speed) { 38350 return { 38351 x: -speed, 38352 y: 0 38353 }; 38354 }, 38355 up: function(speed) { 38356 return { 38357 x: 0, 38358 y: -speed 38359 }; 38360 }, 38361 right: function(speed) { 38362 return { 38363 x: speed, 38364 y: 0 38365 }; 38366 }, 38367 down: function(speed) { 38368 return { 38369 x: 0, 38370 y: speed 38371 }; 38372 } 38373 }; 38374 38375 38376 /** 38377 * Enables to move selection with keyboard arrows. 38378 * Use with Shift for modified speed (default=1, with Shift=10). 38379 * Pressed Cmd/Ctrl turns the feature off. 38380 * 38381 * @param {Object} config 38382 * @param {number} [config.moveSpeed=1] 38383 * @param {number} [config.moveSpeedAccelerated=10] 38384 * @param {Keyboard} keyboard 38385 * @param {Modeling} modeling 38386 * @param {Selection} selection 38387 */ 38388 function KeyboardMoveSelection( 38389 config, 38390 keyboard, 38391 modeling, 38392 rules, 38393 selection 38394 ) { 38395 38396 var self = this; 38397 38398 this._config = assign({}, DEFAULT_CONFIG, config || {}); 38399 38400 keyboard.addListener(HIGHER_PRIORITY$3, function(event) { 38401 38402 var keyEvent = event.keyEvent; 38403 38404 var direction = KEY_TO_DIRECTION[keyEvent.key]; 38405 38406 if (!direction) { 38407 return; 38408 } 38409 38410 if (keyboard.isCmd(keyEvent)) { 38411 return; 38412 } 38413 38414 var accelerated = keyboard.isShift(keyEvent); 38415 38416 self.moveSelection(direction, accelerated); 38417 38418 return true; 38419 }); 38420 38421 38422 /** 38423 * Move selected elements in the given direction, 38424 * optionally specifying accelerated movement. 38425 * 38426 * @param {string} direction 38427 * @param {boolean} [accelerated=false] 38428 */ 38429 this.moveSelection = function(direction, accelerated) { 38430 38431 var selectedElements = selection.get(); 38432 38433 if (!selectedElements.length) { 38434 return; 38435 } 38436 38437 var speed = this._config[ 38438 accelerated ? 38439 'moveSpeedAccelerated' : 38440 'moveSpeed' 38441 ]; 38442 38443 var delta = DIRECTIONS_DELTA[direction](speed); 38444 38445 var canMove = rules.allowed('elements.move', { 38446 shapes: selectedElements 38447 }); 38448 38449 if (canMove) { 38450 modeling.moveElements(selectedElements, delta); 38451 } 38452 }; 38453 38454 } 38455 38456 KeyboardMoveSelection.$inject = [ 38457 'config.keyboardMoveSelection', 38458 'keyboard', 38459 'modeling', 38460 'rules', 38461 'selection' 38462 ]; 38463 38464 var KeyboardMoveSelectionModule = { 38465 __depends__: [ 38466 KeyboardModule$1, 38467 SelectionModule 38468 ], 38469 __init__: [ 38470 'keyboardMoveSelection' 38471 ], 38472 keyboardMoveSelection: [ 'type', KeyboardMoveSelection ] 38473 }; 38474 38475 /** 38476 * Adds change support to the diagram, including 38477 * 38478 * <ul> 38479 * <li>redrawing shapes and connections on change</li> 38480 * </ul> 38481 * 38482 * @param {EventBus} eventBus 38483 * @param {Canvas} canvas 38484 * @param {ElementRegistry} elementRegistry 38485 * @param {GraphicsFactory} graphicsFactory 38486 */ 38487 function ChangeSupport( 38488 eventBus, canvas, elementRegistry, 38489 graphicsFactory) { 38490 38491 38492 // redraw shapes / connections on change 38493 38494 eventBus.on('element.changed', function(event) { 38495 38496 var element = event.element; 38497 38498 // element might have been deleted and replaced by new element with same ID 38499 // thus check for parent of element except for root element 38500 if (element.parent || element === canvas.getRootElement()) { 38501 event.gfx = elementRegistry.getGraphics(element); 38502 } 38503 38504 // shape + gfx may have been deleted 38505 if (!event.gfx) { 38506 return; 38507 } 38508 38509 eventBus.fire(getType(element) + '.changed', event); 38510 }); 38511 38512 eventBus.on('elements.changed', function(event) { 38513 38514 var elements = event.elements; 38515 38516 elements.forEach(function(e) { 38517 eventBus.fire('element.changed', { element: e }); 38518 }); 38519 38520 graphicsFactory.updateContainments(elements); 38521 }); 38522 38523 eventBus.on('shape.changed', function(event) { 38524 graphicsFactory.update('shape', event.element, event.gfx); 38525 }); 38526 38527 eventBus.on('connection.changed', function(event) { 38528 graphicsFactory.update('connection', event.element, event.gfx); 38529 }); 38530 } 38531 38532 ChangeSupport.$inject = [ 38533 'eventBus', 38534 'canvas', 38535 'elementRegistry', 38536 'graphicsFactory' 38537 ]; 38538 38539 var ChangeSupportModule = { 38540 __init__: [ 'changeSupport'], 38541 changeSupport: [ 'type', ChangeSupport ] 38542 }; 38543 38544 var DEFAULT_MIN_WIDTH = 10; 38545 38546 38547 /** 38548 * A component that provides resizing of shapes on the canvas. 38549 * 38550 * The following components are part of shape resize: 38551 * 38552 * * adding resize handles, 38553 * * creating a visual during resize 38554 * * checking resize rules 38555 * * committing a change once finished 38556 * 38557 * 38558 * ## Customizing 38559 * 38560 * It's possible to customize the resizing behaviour by intercepting 'resize.start' 38561 * and providing the following parameters through the 'context': 38562 * 38563 * * minDimensions ({ width, height }): minimum shape dimensions 38564 * 38565 * * childrenBoxPadding ({ left, top, bottom, right } || number): 38566 * gap between the minimum bounding box and the container 38567 * 38568 * f.ex: 38569 * 38570 * ```javascript 38571 * eventBus.on('resize.start', 1500, function(event) { 38572 * var context = event.context, 38573 * 38574 * context.minDimensions = { width: 140, height: 120 }; 38575 * 38576 * // Passing general padding 38577 * context.childrenBoxPadding = 30; 38578 * 38579 * // Passing padding to a specific side 38580 * context.childrenBoxPadding.left = 20; 38581 * }); 38582 * ``` 38583 */ 38584 function Resize(eventBus, rules, modeling, dragging) { 38585 38586 this._dragging = dragging; 38587 this._rules = rules; 38588 38589 var self = this; 38590 38591 38592 /** 38593 * Handle resize move by specified delta. 38594 * 38595 * @param {Object} context 38596 * @param {Point} delta 38597 */ 38598 function handleMove(context, delta) { 38599 38600 var shape = context.shape, 38601 direction = context.direction, 38602 resizeConstraints = context.resizeConstraints, 38603 newBounds; 38604 38605 context.delta = delta; 38606 38607 newBounds = resizeBounds$1(shape, direction, delta); 38608 38609 // ensure constraints during resize 38610 context.newBounds = ensureConstraints$1(newBounds, resizeConstraints); 38611 38612 // update + cache executable state 38613 context.canExecute = self.canResize(context); 38614 } 38615 38616 /** 38617 * Handle resize start. 38618 * 38619 * @param {Object} context 38620 */ 38621 function handleStart(context) { 38622 38623 var resizeConstraints = context.resizeConstraints, 38624 38625 // evaluate minBounds for backwards compatibility 38626 minBounds = context.minBounds; 38627 38628 if (resizeConstraints !== undefined) { 38629 return; 38630 } 38631 38632 if (minBounds === undefined) { 38633 minBounds = self.computeMinResizeBox(context); 38634 } 38635 38636 context.resizeConstraints = { 38637 min: asTRBL(minBounds) 38638 }; 38639 } 38640 38641 /** 38642 * Handle resize end. 38643 * 38644 * @param {Object} context 38645 */ 38646 function handleEnd(context) { 38647 var shape = context.shape, 38648 canExecute = context.canExecute, 38649 newBounds = context.newBounds; 38650 38651 if (canExecute) { 38652 38653 // ensure we have actual pixel values for new bounds 38654 // (important when zoom level was > 1 during move) 38655 newBounds = roundBounds(newBounds); 38656 38657 if (!boundsChanged(shape, newBounds)) { 38658 38659 // no resize necessary 38660 return; 38661 } 38662 38663 // perform the actual resize 38664 modeling.resizeShape(shape, newBounds); 38665 } 38666 } 38667 38668 38669 eventBus.on('resize.start', function(event) { 38670 handleStart(event.context); 38671 }); 38672 38673 eventBus.on('resize.move', function(event) { 38674 var delta = { 38675 x: event.dx, 38676 y: event.dy 38677 }; 38678 38679 handleMove(event.context, delta); 38680 }); 38681 38682 eventBus.on('resize.end', function(event) { 38683 handleEnd(event.context); 38684 }); 38685 38686 } 38687 38688 38689 Resize.prototype.canResize = function(context) { 38690 var rules = this._rules; 38691 38692 var ctx = pick(context, [ 'newBounds', 'shape', 'delta', 'direction' ]); 38693 38694 return rules.allowed('shape.resize', ctx); 38695 }; 38696 38697 /** 38698 * Activate a resize operation. 38699 * 38700 * You may specify additional contextual information and must specify a 38701 * resize direction during activation of the resize event. 38702 * 38703 * @param {MouseEvent} event 38704 * @param {djs.model.Shape} shape 38705 * @param {Object|string} contextOrDirection 38706 */ 38707 Resize.prototype.activate = function(event, shape, contextOrDirection) { 38708 var dragging = this._dragging, 38709 context, 38710 direction; 38711 38712 if (typeof contextOrDirection === 'string') { 38713 contextOrDirection = { 38714 direction: contextOrDirection 38715 }; 38716 } 38717 38718 context = assign({ shape: shape }, contextOrDirection); 38719 38720 direction = context.direction; 38721 38722 if (!direction) { 38723 throw new Error('must provide a direction (n|w|s|e|nw|se|ne|sw)'); 38724 } 38725 38726 dragging.init(event, getReferencePoint$1(shape, direction), 'resize', { 38727 autoActivate: true, 38728 cursor: getCursor(direction), 38729 data: { 38730 shape: shape, 38731 context: context 38732 } 38733 }); 38734 }; 38735 38736 Resize.prototype.computeMinResizeBox = function(context) { 38737 var shape = context.shape, 38738 direction = context.direction, 38739 minDimensions, 38740 childrenBounds; 38741 38742 minDimensions = context.minDimensions || { 38743 width: DEFAULT_MIN_WIDTH, 38744 height: DEFAULT_MIN_WIDTH 38745 }; 38746 38747 // get children bounds 38748 childrenBounds = computeChildrenBBox(shape, context.childrenBoxPadding); 38749 38750 // get correct minimum bounds from given resize direction 38751 // basically ensures that the minBounds is max(childrenBounds, minDimensions) 38752 return getMinResizeBounds(direction, shape, minDimensions, childrenBounds); 38753 }; 38754 38755 38756 Resize.$inject = [ 38757 'eventBus', 38758 'rules', 38759 'modeling', 38760 'dragging' 38761 ]; 38762 38763 // helpers ////////// 38764 38765 function boundsChanged(shape, newBounds) { 38766 return shape.x !== newBounds.x || 38767 shape.y !== newBounds.y || 38768 shape.width !== newBounds.width || 38769 shape.height !== newBounds.height; 38770 } 38771 38772 function getReferencePoint$1(shape, direction) { 38773 var mid = getMid(shape), 38774 trbl = asTRBL(shape); 38775 38776 var referencePoint = { 38777 x: mid.x, 38778 y: mid.y 38779 }; 38780 38781 if (direction.indexOf('n') !== -1) { 38782 referencePoint.y = trbl.top; 38783 } else if (direction.indexOf('s') !== -1) { 38784 referencePoint.y = trbl.bottom; 38785 } 38786 38787 if (direction.indexOf('e') !== -1) { 38788 referencePoint.x = trbl.right; 38789 } else if (direction.indexOf('w') !== -1) { 38790 referencePoint.x = trbl.left; 38791 } 38792 38793 return referencePoint; 38794 } 38795 38796 function getCursor(direction) { 38797 var prefix = 'resize-'; 38798 38799 if (direction === 'n' || direction === 's') { 38800 return prefix + 'ns'; 38801 } else if (direction === 'e' || direction === 'w') { 38802 return prefix + 'ew'; 38803 } else if (direction === 'nw' || direction === 'se') { 38804 return prefix + 'nwse'; 38805 } else { 38806 return prefix + 'nesw'; 38807 } 38808 } 38809 38810 var MARKER_RESIZING$1 = 'djs-resizing', 38811 MARKER_RESIZE_NOT_OK = 'resize-not-ok'; 38812 38813 var LOW_PRIORITY$d = 500; 38814 38815 38816 /** 38817 * Provides previews for resizing shapes when resizing. 38818 * 38819 * @param {EventBus} eventBus 38820 * @param {Canvas} canvas 38821 * @param {PreviewSupport} previewSupport 38822 */ 38823 function ResizePreview(eventBus, canvas, previewSupport) { 38824 38825 /** 38826 * Update resizer frame. 38827 * 38828 * @param {Object} context 38829 */ 38830 function updateFrame(context) { 38831 38832 var shape = context.shape, 38833 bounds = context.newBounds, 38834 frame = context.frame; 38835 38836 if (!frame) { 38837 frame = context.frame = previewSupport.addFrame(shape, canvas.getActiveLayer()); 38838 38839 canvas.addMarker(shape, MARKER_RESIZING$1); 38840 } 38841 38842 if (bounds.width > 5) { 38843 attr(frame, { x: bounds.x, width: bounds.width }); 38844 } 38845 38846 if (bounds.height > 5) { 38847 attr(frame, { y: bounds.y, height: bounds.height }); 38848 } 38849 38850 if (context.canExecute) { 38851 classes(frame).remove(MARKER_RESIZE_NOT_OK); 38852 } else { 38853 classes(frame).add(MARKER_RESIZE_NOT_OK); 38854 } 38855 } 38856 38857 /** 38858 * Remove resizer frame. 38859 * 38860 * @param {Object} context 38861 */ 38862 function removeFrame(context) { 38863 var shape = context.shape, 38864 frame = context.frame; 38865 38866 if (frame) { 38867 remove$1(context.frame); 38868 } 38869 38870 canvas.removeMarker(shape, MARKER_RESIZING$1); 38871 } 38872 38873 // add and update previews 38874 eventBus.on('resize.move', LOW_PRIORITY$d, function(event) { 38875 updateFrame(event.context); 38876 }); 38877 38878 // remove previews 38879 eventBus.on('resize.cleanup', function(event) { 38880 removeFrame(event.context); 38881 }); 38882 38883 } 38884 38885 ResizePreview.$inject = [ 38886 'eventBus', 38887 'canvas', 38888 'previewSupport' 38889 ]; 38890 38891 var HANDLE_OFFSET = -6, 38892 HANDLE_SIZE = 4, 38893 HANDLE_HIT_SIZE = 20; 38894 38895 var CLS_RESIZER = 'djs-resizer'; 38896 38897 var directions = [ 'n', 'w', 's', 'e', 'nw', 'ne', 'se', 'sw' ]; 38898 38899 38900 /** 38901 * This component is responsible for adding resize handles. 38902 * 38903 * @param {EventBus} eventBus 38904 * @param {Canvas} canvas 38905 * @param {Selection} selection 38906 * @param {Resize} resize 38907 */ 38908 function ResizeHandles(eventBus, canvas, selection, resize) { 38909 38910 this._resize = resize; 38911 this._canvas = canvas; 38912 38913 var self = this; 38914 38915 eventBus.on('selection.changed', function(e) { 38916 var newSelection = e.newSelection; 38917 38918 // remove old selection markers 38919 self.removeResizers(); 38920 38921 // add new selection markers ONLY if single selection 38922 if (newSelection.length === 1) { 38923 forEach(newSelection, bind$2(self.addResizer, self)); 38924 } 38925 }); 38926 38927 eventBus.on('shape.changed', function(e) { 38928 var shape = e.element; 38929 38930 if (selection.isSelected(shape)) { 38931 self.removeResizers(); 38932 38933 self.addResizer(shape); 38934 } 38935 }); 38936 } 38937 38938 38939 ResizeHandles.prototype.makeDraggable = function(element, gfx, direction) { 38940 var resize = this._resize; 38941 38942 function startResize(event) { 38943 38944 // only trigger on left mouse button 38945 if (isPrimaryButton(event)) { 38946 resize.activate(event, element, direction); 38947 } 38948 } 38949 38950 componentEvent.bind(gfx, 'mousedown', startResize); 38951 componentEvent.bind(gfx, 'touchstart', startResize); 38952 }; 38953 38954 38955 ResizeHandles.prototype._createResizer = function(element, x, y, direction) { 38956 var resizersParent = this._getResizersParent(); 38957 38958 var offset = getHandleOffset(direction); 38959 38960 var group = create$1('g'); 38961 38962 classes(group).add(CLS_RESIZER); 38963 classes(group).add(CLS_RESIZER + '-' + element.id); 38964 classes(group).add(CLS_RESIZER + '-' + direction); 38965 38966 append(resizersParent, group); 38967 38968 var visual = create$1('rect'); 38969 38970 attr(visual, { 38971 x: -HANDLE_SIZE / 2 + offset.x, 38972 y: -HANDLE_SIZE / 2 + offset.y, 38973 width: HANDLE_SIZE, 38974 height: HANDLE_SIZE 38975 }); 38976 38977 classes(visual).add(CLS_RESIZER + '-visual'); 38978 38979 append(group, visual); 38980 38981 var hit = create$1('rect'); 38982 38983 attr(hit, { 38984 x: -HANDLE_HIT_SIZE / 2 + offset.x, 38985 y: -HANDLE_HIT_SIZE / 2 + offset.y, 38986 width: HANDLE_HIT_SIZE, 38987 height: HANDLE_HIT_SIZE 38988 }); 38989 38990 classes(hit).add(CLS_RESIZER + '-hit'); 38991 38992 append(group, hit); 38993 38994 transform(group, x, y); 38995 38996 return group; 38997 }; 38998 38999 ResizeHandles.prototype.createResizer = function(element, direction) { 39000 var point = getReferencePoint$1(element, direction); 39001 39002 var resizer = this._createResizer(element, point.x, point.y, direction); 39003 39004 this.makeDraggable(element, resizer, direction); 39005 }; 39006 39007 // resize handles implementation /////////////////////////////// 39008 39009 /** 39010 * Add resizers for a given element. 39011 * 39012 * @param {djs.model.Shape} shape 39013 */ 39014 ResizeHandles.prototype.addResizer = function(shape) { 39015 var self = this; 39016 39017 var resize = this._resize; 39018 39019 if (!resize.canResize({ shape: shape })) { 39020 return; 39021 } 39022 39023 forEach(directions, function(direction) { 39024 self.createResizer(shape, direction); 39025 }); 39026 }; 39027 39028 /** 39029 * Remove all resizers 39030 */ 39031 ResizeHandles.prototype.removeResizers = function() { 39032 var resizersParent = this._getResizersParent(); 39033 39034 clear(resizersParent); 39035 }; 39036 39037 ResizeHandles.prototype._getResizersParent = function() { 39038 return this._canvas.getLayer('resizers'); 39039 }; 39040 39041 ResizeHandles.$inject = [ 39042 'eventBus', 39043 'canvas', 39044 'selection', 39045 'resize' 39046 ]; 39047 39048 // helpers ////////// 39049 39050 function getHandleOffset(direction) { 39051 var offset = { 39052 x: 0, 39053 y: 0 39054 }; 39055 39056 if (direction.indexOf('e') !== -1) { 39057 offset.x = -HANDLE_OFFSET; 39058 } else if (direction.indexOf('w') !== -1) { 39059 offset.x = HANDLE_OFFSET; 39060 } 39061 39062 if (direction.indexOf('s') !== -1) { 39063 offset.y = -HANDLE_OFFSET; 39064 } else if (direction.indexOf('n') !== -1) { 39065 offset.y = HANDLE_OFFSET; 39066 } 39067 39068 return offset; 39069 } 39070 39071 var ResizeModule = { 39072 __depends__: [ 39073 RulesModule$1, 39074 DraggingModule, 39075 PreviewSupportModule 39076 ], 39077 __init__: [ 39078 'resize', 39079 'resizePreview', 39080 'resizeHandles' 39081 ], 39082 resize: [ 'type', Resize ], 39083 resizePreview: [ 'type', ResizePreview ], 39084 resizeHandles: [ 'type', ResizeHandles ] 39085 }; 39086 39087 /** 39088 * Creates a new bpmn:CategoryValue inside a new bpmn:Category 39089 * 39090 * @param {ModdleElement} definitions 39091 * @param {BpmnFactory} bpmnFactory 39092 * 39093 * @return {ModdleElement} categoryValue. 39094 */ 39095 function createCategoryValue(definitions, bpmnFactory) { 39096 var categoryValue = bpmnFactory.create('bpmn:CategoryValue'), 39097 category = bpmnFactory.create('bpmn:Category', { 39098 categoryValue: [ categoryValue ] 39099 }); 39100 39101 // add to correct place 39102 add(definitions.get('rootElements'), category); 39103 getBusinessObject(category).$parent = definitions; 39104 getBusinessObject(categoryValue).$parent = category; 39105 39106 return categoryValue; 39107 39108 } 39109 39110 function LabelEditingProvider( 39111 eventBus, bpmnFactory, canvas, directEditing, 39112 modeling, resizeHandles, textRenderer) { 39113 39114 this._bpmnFactory = bpmnFactory; 39115 this._canvas = canvas; 39116 this._modeling = modeling; 39117 this._textRenderer = textRenderer; 39118 39119 directEditing.registerProvider(this); 39120 39121 // listen to dblclick on non-root elements 39122 eventBus.on('element.dblclick', function(event) { 39123 activateDirectEdit(event.element, true); 39124 }); 39125 39126 // complete on followup canvas operation 39127 eventBus.on([ 39128 'autoPlace.start', 39129 'canvas.viewbox.changing', 39130 'drag.init', 39131 'element.mousedown', 39132 'popupMenu.open' 39133 ], function(event) { 39134 39135 if (directEditing.isActive()) { 39136 directEditing.complete(); 39137 } 39138 }); 39139 39140 // cancel on command stack changes 39141 eventBus.on([ 'commandStack.changed' ], function(e) { 39142 if (directEditing.isActive()) { 39143 directEditing.cancel(); 39144 } 39145 }); 39146 39147 39148 eventBus.on('directEditing.activate', function(event) { 39149 resizeHandles.removeResizers(); 39150 }); 39151 39152 eventBus.on('create.end', 500, function(event) { 39153 39154 var context = event.context, 39155 element = context.shape, 39156 canExecute = event.context.canExecute, 39157 isTouch = event.isTouch; 39158 39159 // TODO(nikku): we need to find a way to support the 39160 // direct editing on mobile devices; right now this will 39161 // break for desworkflowediting on mobile devices 39162 // as it breaks the user interaction workflow 39163 39164 // TODO(nre): we should temporarily focus the edited element 39165 // here and release the focused viewport after the direct edit 39166 // operation is finished 39167 if (isTouch) { 39168 return; 39169 } 39170 39171 if (!canExecute) { 39172 return; 39173 } 39174 39175 if (context.hints && context.hints.createElementsBehavior === false) { 39176 return; 39177 } 39178 39179 activateDirectEdit(element); 39180 }); 39181 39182 eventBus.on('autoPlace.end', 500, function(event) { 39183 activateDirectEdit(event.shape); 39184 }); 39185 39186 39187 function activateDirectEdit(element, force) { 39188 if (force || 39189 isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation', 'bpmn:Group' ]) || 39190 isCollapsedSubProcess(element)) { 39191 39192 directEditing.activate(element); 39193 } 39194 } 39195 39196 } 39197 39198 LabelEditingProvider.$inject = [ 39199 'eventBus', 39200 'bpmnFactory', 39201 'canvas', 39202 'directEditing', 39203 'modeling', 39204 'resizeHandles', 39205 'textRenderer' 39206 ]; 39207 39208 39209 /** 39210 * Activate direct editing for activities and text annotations. 39211 * 39212 * @param {djs.model.Base} element 39213 * 39214 * @return {Object} an object with properties bounds (position and size), text and options 39215 */ 39216 LabelEditingProvider.prototype.activate = function(element) { 39217 39218 // text 39219 var text = getLabel(element); 39220 39221 if (text === undefined) { 39222 return; 39223 } 39224 39225 var context = { 39226 text: text 39227 }; 39228 39229 // bounds 39230 var bounds = this.getEditingBBox(element); 39231 39232 assign(context, bounds); 39233 39234 var options = {}; 39235 39236 // tasks 39237 if ( 39238 isAny(element, [ 39239 'bpmn:Task', 39240 'bpmn:Participant', 39241 'bpmn:Lane', 39242 'bpmn:CallActivity' 39243 ]) || 39244 isCollapsedSubProcess(element) 39245 ) { 39246 assign(options, { 39247 centerVertically: true 39248 }); 39249 } 39250 39251 // external labels 39252 if (isLabelExternal(element)) { 39253 assign(options, { 39254 autoResize: true 39255 }); 39256 } 39257 39258 // text annotations 39259 if (is$1(element, 'bpmn:TextAnnotation')) { 39260 assign(options, { 39261 resizable: true, 39262 autoResize: true 39263 }); 39264 } 39265 39266 assign(context, { 39267 options: options 39268 }); 39269 39270 return context; 39271 }; 39272 39273 39274 /** 39275 * Get the editing bounding box based on the element's size and position 39276 * 39277 * @param {djs.model.Base} element 39278 * 39279 * @return {Object} an object containing information about position 39280 * and size (fixed or minimum and/or maximum) 39281 */ 39282 LabelEditingProvider.prototype.getEditingBBox = function(element) { 39283 var canvas = this._canvas; 39284 39285 var target = element.label || element; 39286 39287 var bbox = canvas.getAbsoluteBBox(target); 39288 39289 var mid = { 39290 x: bbox.x + bbox.width / 2, 39291 y: bbox.y + bbox.height / 2 39292 }; 39293 39294 // default position 39295 var bounds = { x: bbox.x, y: bbox.y }; 39296 39297 var zoom = canvas.zoom(); 39298 39299 var defaultStyle = this._textRenderer.getDefaultStyle(), 39300 externalStyle = this._textRenderer.getExternalStyle(); 39301 39302 // take zoom into account 39303 var externalFontSize = externalStyle.fontSize * zoom, 39304 externalLineHeight = externalStyle.lineHeight, 39305 defaultFontSize = defaultStyle.fontSize * zoom, 39306 defaultLineHeight = defaultStyle.lineHeight; 39307 39308 var style = { 39309 fontFamily: this._textRenderer.getDefaultStyle().fontFamily, 39310 fontWeight: this._textRenderer.getDefaultStyle().fontWeight 39311 }; 39312 39313 // adjust for expanded pools AND lanes 39314 if (is$1(element, 'bpmn:Lane') || isExpandedPool(element)) { 39315 39316 assign(bounds, { 39317 width: bbox.height, 39318 height: 30 * zoom, 39319 x: bbox.x - bbox.height / 2 + (15 * zoom), 39320 y: mid.y - (30 * zoom) / 2 39321 }); 39322 39323 assign(style, { 39324 fontSize: defaultFontSize + 'px', 39325 lineHeight: defaultLineHeight, 39326 paddingTop: (7 * zoom) + 'px', 39327 paddingBottom: (7 * zoom) + 'px', 39328 paddingLeft: (5 * zoom) + 'px', 39329 paddingRight: (5 * zoom) + 'px', 39330 transform: 'rotate(-90deg)' 39331 }); 39332 } 39333 39334 39335 // internal labels for tasks and collapsed call activities, 39336 // sub processes and participants 39337 if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity']) || 39338 isCollapsedPool(element) || 39339 isCollapsedSubProcess(element)) { 39340 39341 assign(bounds, { 39342 width: bbox.width, 39343 height: bbox.height 39344 }); 39345 39346 assign(style, { 39347 fontSize: defaultFontSize + 'px', 39348 lineHeight: defaultLineHeight, 39349 paddingTop: (7 * zoom) + 'px', 39350 paddingBottom: (7 * zoom) + 'px', 39351 paddingLeft: (5 * zoom) + 'px', 39352 paddingRight: (5 * zoom) + 'px' 39353 }); 39354 } 39355 39356 39357 // internal labels for expanded sub processes 39358 if (isExpandedSubProcess$1(element)) { 39359 assign(bounds, { 39360 width: bbox.width, 39361 x: bbox.x 39362 }); 39363 39364 assign(style, { 39365 fontSize: defaultFontSize + 'px', 39366 lineHeight: defaultLineHeight, 39367 paddingTop: (7 * zoom) + 'px', 39368 paddingBottom: (7 * zoom) + 'px', 39369 paddingLeft: (5 * zoom) + 'px', 39370 paddingRight: (5 * zoom) + 'px' 39371 }); 39372 } 39373 39374 var width = 90 * zoom, 39375 paddingTop = 7 * zoom, 39376 paddingBottom = 4 * zoom; 39377 39378 // external labels for events, data elements, gateways, groups and connections 39379 if (target.labelTarget) { 39380 assign(bounds, { 39381 width: width, 39382 height: bbox.height + paddingTop + paddingBottom, 39383 x: mid.x - width / 2, 39384 y: bbox.y - paddingTop 39385 }); 39386 39387 assign(style, { 39388 fontSize: externalFontSize + 'px', 39389 lineHeight: externalLineHeight, 39390 paddingTop: paddingTop + 'px', 39391 paddingBottom: paddingBottom + 'px' 39392 }); 39393 } 39394 39395 // external label not yet created 39396 if (isLabelExternal(target) 39397 && !hasExternalLabel(target) 39398 && !isLabel$6(target)) { 39399 39400 var externalLabelMid = getExternalLabelMid(element); 39401 39402 var absoluteBBox = canvas.getAbsoluteBBox({ 39403 x: externalLabelMid.x, 39404 y: externalLabelMid.y, 39405 width: 0, 39406 height: 0 39407 }); 39408 39409 var height = externalFontSize + paddingTop + paddingBottom; 39410 39411 assign(bounds, { 39412 width: width, 39413 height: height, 39414 x: absoluteBBox.x - width / 2, 39415 y: absoluteBBox.y - height / 2 39416 }); 39417 39418 assign(style, { 39419 fontSize: externalFontSize + 'px', 39420 lineHeight: externalLineHeight, 39421 paddingTop: paddingTop + 'px', 39422 paddingBottom: paddingBottom + 'px' 39423 }); 39424 } 39425 39426 // text annotations 39427 if (is$1(element, 'bpmn:TextAnnotation')) { 39428 assign(bounds, { 39429 width: bbox.width, 39430 height: bbox.height, 39431 minWidth: 30 * zoom, 39432 minHeight: 10 * zoom 39433 }); 39434 39435 assign(style, { 39436 textAlign: 'left', 39437 paddingTop: (5 * zoom) + 'px', 39438 paddingBottom: (7 * zoom) + 'px', 39439 paddingLeft: (7 * zoom) + 'px', 39440 paddingRight: (5 * zoom) + 'px', 39441 fontSize: defaultFontSize + 'px', 39442 lineHeight: defaultLineHeight 39443 }); 39444 } 39445 39446 return { bounds: bounds, style: style }; 39447 }; 39448 39449 39450 LabelEditingProvider.prototype.update = function( 39451 element, newLabel, 39452 activeContextText, bounds) { 39453 39454 var newBounds, 39455 bbox; 39456 39457 if (is$1(element, 'bpmn:TextAnnotation')) { 39458 39459 bbox = this._canvas.getAbsoluteBBox(element); 39460 39461 newBounds = { 39462 x: element.x, 39463 y: element.y, 39464 width: element.width / bbox.width * bounds.width, 39465 height: element.height / bbox.height * bounds.height 39466 }; 39467 } 39468 39469 if (is$1(element, 'bpmn:Group')) { 39470 39471 var businessObject = getBusinessObject(element); 39472 39473 // initialize categoryValue if not existing 39474 if (!businessObject.categoryValueRef) { 39475 39476 var rootElement = this._canvas.getRootElement(), 39477 definitions = getBusinessObject(rootElement).$parent; 39478 39479 var categoryValue = createCategoryValue(definitions, this._bpmnFactory); 39480 39481 getBusinessObject(element).categoryValueRef = categoryValue; 39482 } 39483 39484 } 39485 39486 if (isEmptyText$1(newLabel)) { 39487 newLabel = null; 39488 } 39489 39490 this._modeling.updateLabel(element, newLabel, newBounds); 39491 }; 39492 39493 39494 39495 // helpers ////////////////////// 39496 39497 function isCollapsedSubProcess(element) { 39498 return is$1(element, 'bpmn:SubProcess') && !isExpanded(element); 39499 } 39500 39501 function isExpandedSubProcess$1(element) { 39502 return is$1(element, 'bpmn:SubProcess') && isExpanded(element); 39503 } 39504 39505 function isCollapsedPool(element) { 39506 return is$1(element, 'bpmn:Participant') && !isExpanded(element); 39507 } 39508 39509 function isExpandedPool(element) { 39510 return is$1(element, 'bpmn:Participant') && isExpanded(element); 39511 } 39512 39513 function isEmptyText$1(label) { 39514 return !label || !label.trim(); 39515 } 39516 39517 var MARKER_HIDDEN = 'djs-element-hidden', 39518 MARKER_LABEL_HIDDEN = 'djs-label-hidden'; 39519 39520 39521 function LabelEditingPreview( 39522 eventBus, canvas, elementRegistry, 39523 pathMap) { 39524 39525 var self = this; 39526 39527 var defaultLayer = canvas.getDefaultLayer(); 39528 39529 var element, absoluteElementBBox, gfx; 39530 39531 eventBus.on('directEditing.activate', function(context) { 39532 var activeProvider = context.active; 39533 39534 element = activeProvider.element.label || activeProvider.element; 39535 39536 // text annotation 39537 if (is$1(element, 'bpmn:TextAnnotation')) { 39538 absoluteElementBBox = canvas.getAbsoluteBBox(element); 39539 39540 gfx = create$1('g'); 39541 39542 var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', { 39543 xScaleFactor: 1, 39544 yScaleFactor: 1, 39545 containerWidth: element.width, 39546 containerHeight: element.height, 39547 position: { 39548 mx: 0.0, 39549 my: 0.0 39550 } 39551 }); 39552 39553 var path = self.path = create$1('path'); 39554 39555 attr(path, { 39556 d: textPathData, 39557 strokeWidth: 2, 39558 stroke: getStrokeColor(element) 39559 }); 39560 39561 append(gfx, path); 39562 39563 append(defaultLayer, gfx); 39564 39565 translate$2(gfx, element.x, element.y); 39566 } 39567 39568 if (is$1(element, 'bpmn:TextAnnotation') || 39569 element.labelTarget) { 39570 canvas.addMarker(element, MARKER_HIDDEN); 39571 } else if (is$1(element, 'bpmn:Task') || 39572 is$1(element, 'bpmn:CallActivity') || 39573 is$1(element, 'bpmn:SubProcess') || 39574 is$1(element, 'bpmn:Participant')) { 39575 canvas.addMarker(element, MARKER_LABEL_HIDDEN); 39576 } 39577 }); 39578 39579 eventBus.on('directEditing.resize', function(context) { 39580 39581 // text annotation 39582 if (is$1(element, 'bpmn:TextAnnotation')) { 39583 var height = context.height, 39584 dy = context.dy; 39585 39586 var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0); 39587 39588 var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', { 39589 xScaleFactor: 1, 39590 yScaleFactor: 1, 39591 containerWidth: element.width, 39592 containerHeight: newElementHeight, 39593 position: { 39594 mx: 0.0, 39595 my: 0.0 39596 } 39597 }); 39598 39599 attr(self.path, { 39600 d: textPathData 39601 }); 39602 } 39603 }); 39604 39605 eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) { 39606 var activeProvider = context.active; 39607 39608 if (activeProvider) { 39609 canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN); 39610 canvas.removeMarker(element, MARKER_LABEL_HIDDEN); 39611 } 39612 39613 element = undefined; 39614 absoluteElementBBox = undefined; 39615 39616 if (gfx) { 39617 remove$1(gfx); 39618 39619 gfx = undefined; 39620 } 39621 }); 39622 } 39623 39624 LabelEditingPreview.$inject = [ 39625 'eventBus', 39626 'canvas', 39627 'elementRegistry', 39628 'pathMap' 39629 ]; 39630 39631 39632 // helpers /////////////////// 39633 39634 function getStrokeColor(element, defaultColor) { 39635 var bo = getBusinessObject(element); 39636 39637 return bo.di.get('stroke') || defaultColor || 'black'; 39638 } 39639 39640 var LabelEditingModule = { 39641 __depends__: [ 39642 ChangeSupportModule, 39643 ResizeModule, 39644 DirectEditingModule 39645 ], 39646 __init__: [ 39647 'labelEditingProvider', 39648 'labelEditingPreview' 39649 ], 39650 labelEditingProvider: [ 'type', LabelEditingProvider ], 39651 labelEditingPreview: [ 'type', LabelEditingPreview ] 39652 }; 39653 39654 var ALIGNMENTS = [ 39655 'top', 39656 'bottom', 39657 'left', 39658 'right' 39659 ]; 39660 39661 var ELEMENT_LABEL_DISTANCE = 10; 39662 39663 /** 39664 * A component that makes sure that external labels are added 39665 * together with respective elements and properly updated (DI wise) 39666 * during move. 39667 * 39668 * @param {EventBus} eventBus 39669 * @param {Modeling} modeling 39670 */ 39671 function AdaptiveLabelPositioningBehavior(eventBus, modeling) { 39672 39673 CommandInterceptor.call(this, eventBus); 39674 39675 this.postExecuted([ 39676 'connection.create', 39677 'connection.layout', 39678 'connection.updateWaypoints' 39679 ], function(event) { 39680 var context = event.context, 39681 connection = context.connection, 39682 source = connection.source, 39683 target = connection.target, 39684 hints = context.hints || {}; 39685 39686 if (hints.createElementsBehavior !== false) { 39687 checkLabelAdjustment(source); 39688 checkLabelAdjustment(target); 39689 } 39690 }); 39691 39692 39693 this.postExecuted([ 39694 'label.create' 39695 ], function(event) { 39696 var context = event.context, 39697 shape = context.shape, 39698 hints = context.hints || {}; 39699 39700 if (hints.createElementsBehavior !== false) { 39701 checkLabelAdjustment(shape.labelTarget); 39702 } 39703 }); 39704 39705 39706 this.postExecuted([ 39707 'elements.create' 39708 ], function(event) { 39709 var context = event.context, 39710 elements = context.elements, 39711 hints = context.hints || {}; 39712 39713 if (hints.createElementsBehavior !== false) { 39714 elements.forEach(function(element) { 39715 checkLabelAdjustment(element); 39716 }); 39717 } 39718 }); 39719 39720 function checkLabelAdjustment(element) { 39721 39722 // skip non-existing labels 39723 if (!hasExternalLabel(element)) { 39724 return; 39725 } 39726 39727 var optimalPosition = getOptimalPosition(element); 39728 39729 // no optimal position found 39730 if (!optimalPosition) { 39731 return; 39732 } 39733 39734 adjustLabelPosition(element, optimalPosition); 39735 } 39736 39737 function adjustLabelPosition(element, orientation) { 39738 39739 var elementMid = getMid(element), 39740 label = element.label, 39741 labelMid = getMid(label); 39742 39743 // ignore labels that are being created 39744 if (!label.parent) { 39745 return; 39746 } 39747 39748 var elementTrbl = asTRBL(element); 39749 39750 var newLabelMid; 39751 39752 switch (orientation) { 39753 case 'top': 39754 newLabelMid = { 39755 x: elementMid.x, 39756 y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2 39757 }; 39758 39759 break; 39760 39761 case 'left': 39762 39763 newLabelMid = { 39764 x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2, 39765 y: elementMid.y 39766 }; 39767 39768 break; 39769 39770 case 'bottom': 39771 39772 newLabelMid = { 39773 x: elementMid.x, 39774 y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2 39775 }; 39776 39777 break; 39778 39779 case 'right': 39780 39781 newLabelMid = { 39782 x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2, 39783 y: elementMid.y 39784 }; 39785 39786 break; 39787 } 39788 39789 var delta$1 = delta(newLabelMid, labelMid); 39790 39791 modeling.moveShape(label, delta$1); 39792 } 39793 39794 } 39795 39796 inherits$1(AdaptiveLabelPositioningBehavior, CommandInterceptor); 39797 39798 AdaptiveLabelPositioningBehavior.$inject = [ 39799 'eventBus', 39800 'modeling' 39801 ]; 39802 39803 39804 // helpers ////////////////////// 39805 39806 /** 39807 * Return alignments which are taken by a boundary's host element 39808 * 39809 * @param {Shape} element 39810 * 39811 * @return {Array<string>} 39812 */ 39813 function getTakenHostAlignments(element) { 39814 39815 var hostElement = element.host, 39816 elementMid = getMid(element), 39817 hostOrientation = getOrientation(elementMid, hostElement); 39818 39819 var freeAlignments; 39820 39821 // check whether there is a multi-orientation, e.g. 'top-left' 39822 if (hostOrientation.indexOf('-') >= 0) { 39823 freeAlignments = hostOrientation.split('-'); 39824 } else { 39825 freeAlignments = [ hostOrientation ]; 39826 } 39827 39828 var takenAlignments = ALIGNMENTS.filter(function(alignment) { 39829 39830 return freeAlignments.indexOf(alignment) === -1; 39831 }); 39832 39833 return takenAlignments; 39834 39835 } 39836 39837 /** 39838 * Return alignments which are taken by related connections 39839 * 39840 * @param {Shape} element 39841 * 39842 * @return {Array<string>} 39843 */ 39844 function getTakenConnectionAlignments(element) { 39845 39846 var elementMid = getMid(element); 39847 39848 var takenAlignments = [].concat( 39849 element.incoming.map(function(c) { 39850 return c.waypoints[c.waypoints.length - 2 ]; 39851 }), 39852 element.outgoing.map(function(c) { 39853 return c.waypoints[1]; 39854 }) 39855 ).map(function(point) { 39856 return getApproximateOrientation(elementMid, point); 39857 }); 39858 39859 return takenAlignments; 39860 } 39861 39862 /** 39863 * Return the optimal label position around an element 39864 * or _undefined_, if none was found. 39865 * 39866 * @param {Shape} element 39867 * 39868 * @return {string} positioning identifier 39869 */ 39870 function getOptimalPosition(element) { 39871 39872 var labelMid = getMid(element.label); 39873 39874 var elementMid = getMid(element); 39875 39876 var labelOrientation = getApproximateOrientation(elementMid, labelMid); 39877 39878 if (!isAligned(labelOrientation)) { 39879 return; 39880 } 39881 39882 var takenAlignments = getTakenConnectionAlignments(element); 39883 39884 if (element.host) { 39885 var takenHostAlignments = getTakenHostAlignments(element); 39886 39887 takenAlignments = takenAlignments.concat(takenHostAlignments); 39888 } 39889 39890 var freeAlignments = ALIGNMENTS.filter(function(alignment) { 39891 39892 return takenAlignments.indexOf(alignment) === -1; 39893 }); 39894 39895 // NOTHING TO DO; label already aligned a.O.K. 39896 if (freeAlignments.indexOf(labelOrientation) !== -1) { 39897 return; 39898 } 39899 39900 return freeAlignments[0]; 39901 } 39902 39903 function getApproximateOrientation(p0, p1) { 39904 return getOrientation(p1, p0, 5); 39905 } 39906 39907 function isAligned(orientation) { 39908 return ALIGNMENTS.indexOf(orientation) !== -1; 39909 } 39910 39911 function AppendBehavior(eventBus, elementFactory, bpmnRules) { 39912 39913 CommandInterceptor.call(this, eventBus); 39914 39915 // assign correct shape position unless already set 39916 39917 this.preExecute('shape.append', function(context) { 39918 39919 var source = context.source, 39920 shape = context.shape; 39921 39922 if (!context.position) { 39923 39924 if (is$1(shape, 'bpmn:TextAnnotation')) { 39925 context.position = { 39926 x: source.x + source.width / 2 + 75, 39927 y: source.y - (50) - shape.height / 2 39928 }; 39929 } else { 39930 context.position = { 39931 x: source.x + source.width + 80 + shape.width / 2, 39932 y: source.y + source.height / 2 39933 }; 39934 } 39935 } 39936 }, true); 39937 } 39938 39939 inherits$1(AppendBehavior, CommandInterceptor); 39940 39941 AppendBehavior.$inject = [ 39942 'eventBus', 39943 'elementFactory', 39944 'bpmnRules' 39945 ]; 39946 39947 function AssociationBehavior(injector, modeling) { 39948 injector.invoke(CommandInterceptor, this); 39949 39950 this.postExecute('shape.move', function(context) { 39951 var newParent = context.newParent, 39952 shape = context.shape; 39953 39954 var associations = filter(shape.incoming.concat(shape.outgoing), function(connection) { 39955 return is$1(connection, 'bpmn:Association'); 39956 }); 39957 39958 forEach(associations, function(association) { 39959 modeling.moveConnection(association, { x: 0, y: 0 }, newParent); 39960 }); 39961 }, true); 39962 } 39963 39964 inherits$1(AssociationBehavior, CommandInterceptor); 39965 39966 AssociationBehavior.$inject = [ 39967 'injector', 39968 'modeling' 39969 ]; 39970 39971 var LOW_PRIORITY$c = 500; 39972 39973 39974 /** 39975 * Replace intermediate event with boundary event when creating or moving results in attached event. 39976 */ 39977 function AttachEventBehavior(bpmnReplace, injector) { 39978 injector.invoke(CommandInterceptor, this); 39979 39980 this._bpmnReplace = bpmnReplace; 39981 39982 var self = this; 39983 39984 this.postExecuted('elements.create', LOW_PRIORITY$c, function(context) { 39985 var elements = context.elements; 39986 39987 elements = elements.filter(function(shape) { 39988 var host = shape.host; 39989 39990 return shouldReplace$1(shape, host); 39991 }); 39992 39993 if (elements.length !== 1) { 39994 return; 39995 } 39996 39997 elements.map(function(element) { 39998 return elements.indexOf(element); 39999 }).forEach(function(index) { 40000 var host = elements[ index ]; 40001 40002 context.elements[ index ] = self.replaceShape(elements[ index ], host); 40003 }); 40004 }, true); 40005 40006 40007 this.preExecute('elements.move', LOW_PRIORITY$c, function(context) { 40008 var shapes = context.shapes, 40009 host = context.newHost; 40010 40011 if (shapes.length !== 1) { 40012 return; 40013 } 40014 40015 var shape = shapes[0]; 40016 40017 if (shouldReplace$1(shape, host)) { 40018 context.shapes = [ self.replaceShape(shape, host) ]; 40019 } 40020 }, true); 40021 } 40022 40023 AttachEventBehavior.$inject = [ 40024 'bpmnReplace', 40025 'injector' 40026 ]; 40027 40028 inherits$1(AttachEventBehavior, CommandInterceptor); 40029 40030 AttachEventBehavior.prototype.replaceShape = function(shape, host) { 40031 var eventDefinition = getEventDefinition$1(shape); 40032 40033 var boundaryEvent = { 40034 type: 'bpmn:BoundaryEvent', 40035 host: host 40036 }; 40037 40038 if (eventDefinition) { 40039 boundaryEvent.eventDefinitionType = eventDefinition.$type; 40040 } 40041 40042 return this._bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false }); 40043 }; 40044 40045 40046 // helpers ////////// 40047 40048 function getEventDefinition$1(element) { 40049 var businessObject = getBusinessObject(element), 40050 eventDefinitions = businessObject.eventDefinitions; 40051 40052 return eventDefinitions && eventDefinitions[0]; 40053 } 40054 40055 function shouldReplace$1(shape, host) { 40056 return !isLabel$6(shape) && 40057 isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ]) && !!host; 40058 } 40059 40060 var HIGH_PRIORITY$c = 2000; 40061 40062 40063 /** 40064 * BPMN specific boundary event behavior 40065 */ 40066 function BoundaryEventBehavior(eventBus, moddle, modeling) { 40067 40068 CommandInterceptor.call(this, eventBus); 40069 40070 function getBoundaryEvents(element) { 40071 return filter(element.attachers, function(attacher) { 40072 return is$1(attacher, 'bpmn:BoundaryEvent'); 40073 }); 40074 } 40075 40076 // remove after connecting to event-based gateway 40077 this.postExecute('connection.create', function(event) { 40078 var source = event.context.source, 40079 target = event.context.target, 40080 boundaryEvents = getBoundaryEvents(target); 40081 40082 if ( 40083 is$1(source, 'bpmn:EventBasedGateway') && 40084 is$1(target, 'bpmn:ReceiveTask') && 40085 boundaryEvents.length > 0 40086 ) { 40087 modeling.removeElements(boundaryEvents); 40088 } 40089 40090 }); 40091 40092 // remove after replacing connected gateway with event-based gateway 40093 this.postExecute('connection.reconnect', function(event) { 40094 var oldSource = event.context.oldSource, 40095 newSource = event.context.newSource; 40096 40097 if (is$1(oldSource, 'bpmn:Gateway') && 40098 is$1(newSource, 'bpmn:EventBasedGateway')) { 40099 forEach(newSource.outgoing, function(connection) { 40100 var target = connection.target, 40101 attachedboundaryEvents = getBoundaryEvents(target); 40102 40103 if (is$1(target, 'bpmn:ReceiveTask') && 40104 attachedboundaryEvents.length > 0) { 40105 modeling.removeElements(attachedboundaryEvents); 40106 } 40107 }); 40108 } 40109 }); 40110 40111 // copy reference to root element on replace 40112 eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY$c, function(context) { 40113 var parent = context.parent, 40114 property = context.property, 40115 propertyName = context.propertyName; 40116 40117 var propertyDescriptor = moddle.getPropertyDescriptor(parent, propertyName); 40118 40119 if (propertyDescriptor && propertyDescriptor.isReference && is$1(property, 'bpmn:RootElement')) { 40120 parent.set(propertyName, property); 40121 } 40122 }); 40123 } 40124 40125 BoundaryEventBehavior.$inject = [ 40126 'eventBus', 40127 'moddle', 40128 'modeling' 40129 ]; 40130 40131 inherits$1(BoundaryEventBehavior, CommandInterceptor); 40132 40133 var LOW_PRIORITY$b = 500; 40134 40135 40136 /** 40137 * Add referenced root elements (error, escalation, message, signal) if they don't exist. 40138 * Copy referenced root elements on copy & paste. 40139 */ 40140 function RootElementReferenceBehavior( 40141 bpmnjs, eventBus, injector, moddleCopy, bpmnFactory 40142 ) { 40143 injector.invoke(CommandInterceptor, this); 40144 40145 function canHaveRootElementReference(element) { 40146 return isAny(element, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ]) || 40147 hasAnyEventDefinition(element, [ 40148 'bpmn:ErrorEventDefinition', 40149 'bpmn:EscalationEventDefinition', 40150 'bpmn:MessageEventDefinition', 40151 'bpmn:SignalEventDefinition' 40152 ]); 40153 } 40154 40155 function hasRootElement(rootElement) { 40156 var definitions = bpmnjs.getDefinitions(), 40157 rootElements = definitions.get('rootElements'); 40158 40159 return !!find(rootElements, matchPattern({ id: rootElement.id })); 40160 } 40161 40162 function getRootElementReferencePropertyName(eventDefinition) { 40163 if (is$1(eventDefinition, 'bpmn:ErrorEventDefinition')) { 40164 return 'errorRef'; 40165 } else if (is$1(eventDefinition, 'bpmn:EscalationEventDefinition')) { 40166 return 'escalationRef'; 40167 } else if (is$1(eventDefinition, 'bpmn:MessageEventDefinition')) { 40168 return 'messageRef'; 40169 } else if (is$1(eventDefinition, 'bpmn:SignalEventDefinition')) { 40170 return 'signalRef'; 40171 } 40172 } 40173 40174 function getRootElement(businessObject) { 40175 if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) { 40176 return businessObject.get('messageRef'); 40177 } 40178 40179 var eventDefinitions = businessObject.get('eventDefinitions'), 40180 eventDefinition = eventDefinitions[ 0 ]; 40181 40182 return eventDefinition.get(getRootElementReferencePropertyName(eventDefinition)); 40183 } 40184 40185 function setRootElement(businessObject, rootElement) { 40186 if (isAny(businessObject, [ 'bpmn:ReceiveTask', 'bpmn:SendTask' ])) { 40187 return businessObject.set('messageRef', rootElement); 40188 } 40189 40190 var eventDefinitions = businessObject.get('eventDefinitions'), 40191 eventDefinition = eventDefinitions[ 0 ]; 40192 40193 return eventDefinition.set(getRootElementReferencePropertyName(eventDefinition), rootElement); 40194 } 40195 40196 // create shape 40197 this.executed('shape.create', function(context) { 40198 var shape = context.shape; 40199 40200 if (!canHaveRootElementReference(shape)) { 40201 return; 40202 } 40203 40204 var businessObject = getBusinessObject(shape), 40205 rootElement = getRootElement(businessObject), 40206 rootElements; 40207 40208 if (rootElement && !hasRootElement(rootElement)) { 40209 rootElements = bpmnjs.getDefinitions().get('rootElements'); 40210 40211 // add root element 40212 add(rootElements, rootElement); 40213 40214 context.addedRootElement = rootElement; 40215 } 40216 }, true); 40217 40218 this.reverted('shape.create', function(context) { 40219 var addedRootElement = context.addedRootElement; 40220 40221 if (!addedRootElement) { 40222 return; 40223 } 40224 40225 var rootElements = bpmnjs.getDefinitions().get('rootElements'); 40226 40227 // remove root element 40228 remove(rootElements, addedRootElement); 40229 }, true); 40230 40231 eventBus.on('copyPaste.copyElement', function(context) { 40232 var descriptor = context.descriptor, 40233 element = context.element; 40234 40235 if (!canHaveRootElementReference(element)) { 40236 return; 40237 } 40238 40239 var businessObject = getBusinessObject(element), 40240 rootElement = getRootElement(businessObject); 40241 40242 if (rootElement) { 40243 descriptor.referencedRootElement = rootElement; 40244 } 40245 }); 40246 40247 eventBus.on('copyPaste.pasteElement', LOW_PRIORITY$b, function(context) { 40248 var descriptor = context.descriptor, 40249 businessObject = descriptor.businessObject; 40250 40251 if (!canHaveRootElementReference(businessObject)) { 40252 return; 40253 } 40254 40255 var referencedRootElement = descriptor.referencedRootElement; 40256 40257 if (!referencedRootElement) { 40258 return; 40259 } 40260 40261 if (!hasRootElement(referencedRootElement)) { 40262 referencedRootElement = moddleCopy.copyElement( 40263 referencedRootElement, 40264 bpmnFactory.create(referencedRootElement.$type) 40265 ); 40266 } 40267 40268 setRootElement(businessObject, referencedRootElement); 40269 }); 40270 } 40271 40272 RootElementReferenceBehavior.$inject = [ 40273 'bpmnjs', 40274 'eventBus', 40275 'injector', 40276 'moddleCopy', 40277 'bpmnFactory' 40278 ]; 40279 40280 inherits$1(RootElementReferenceBehavior, CommandInterceptor); 40281 40282 // helpers ////////// 40283 40284 function hasAnyEventDefinition(element, types) { 40285 if (!isArray$2(types)) { 40286 types = [ types ]; 40287 } 40288 40289 return some(types, function(type) { 40290 return hasEventDefinition$2(element, type); 40291 }); 40292 } 40293 40294 function CreateBehavior(injector) { 40295 injector.invoke(CommandInterceptor, this); 40296 40297 this.preExecute('shape.create', 1500, function(event) { 40298 var context = event.context, 40299 parent = context.parent, 40300 shape = context.shape; 40301 40302 if (is$1(parent, 'bpmn:Lane') && !is$1(shape, 'bpmn:Lane')) { 40303 context.parent = getParent(parent, 'bpmn:Participant'); 40304 } 40305 }); 40306 40307 } 40308 40309 40310 CreateBehavior.$inject = [ 'injector' ]; 40311 40312 inherits$1(CreateBehavior, CommandInterceptor); 40313 40314 var HIGH_PRIORITY$b = 1500; 40315 var HIGHEST_PRIORITY = 2000; 40316 40317 40318 /** 40319 * Correct hover targets in certain situations to improve diagram interaction. 40320 * 40321 * @param {ElementRegistry} elementRegistry 40322 * @param {EventBus} eventBus 40323 * @param {Canvas} canvas 40324 */ 40325 function FixHoverBehavior(elementRegistry, eventBus, canvas) { 40326 40327 eventBus.on([ 40328 'create.hover', 40329 'create.move', 40330 'create.out', 40331 'create.end', 40332 'shape.move.hover', 40333 'shape.move.move', 40334 'shape.move.out', 40335 'shape.move.end' 40336 ], HIGH_PRIORITY$b, function(event) { 40337 var context = event.context, 40338 shape = context.shape || event.shape, 40339 hover = event.hover; 40340 40341 // ensure elements are not dropped onto a bpmn:Lane but onto 40342 // the underlying bpmn:Participant 40343 if (is$1(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) { 40344 event.hover = getLanesRoot(hover); 40345 event.hoverGfx = elementRegistry.getGraphics(event.hover); 40346 } 40347 40348 var rootElement = canvas.getRootElement(); 40349 40350 // ensure bpmn:Group and label elements are dropped 40351 // always onto the root 40352 if (hover !== rootElement && (shape.labelTarget || is$1(shape, 'bpmn:Group'))) { 40353 event.hover = rootElement; 40354 event.hoverGfx = elementRegistry.getGraphics(event.hover); 40355 } 40356 }); 40357 40358 eventBus.on([ 40359 'connect.hover', 40360 'connect.out', 40361 'connect.end', 40362 'connect.cleanup', 40363 'global-connect.hover', 40364 'global-connect.out', 40365 'global-connect.end', 40366 'global-connect.cleanup' 40367 ], HIGH_PRIORITY$b, function(event) { 40368 var hover = event.hover; 40369 40370 // ensure connections start/end on bpmn:Participant, 40371 // not the underlying bpmn:Lane 40372 if (is$1(hover, 'bpmn:Lane')) { 40373 event.hover = getLanesRoot(hover) || hover; 40374 event.hoverGfx = elementRegistry.getGraphics(event.hover); 40375 } 40376 }); 40377 40378 40379 eventBus.on([ 40380 'bendpoint.move.hover' 40381 ], HIGH_PRIORITY$b, function(event) { 40382 var context = event.context, 40383 hover = event.hover, 40384 type = context.type; 40385 40386 // ensure reconnect start/end on bpmn:Participant, 40387 // not the underlying bpmn:Lane 40388 if (is$1(hover, 'bpmn:Lane') && /reconnect/.test(type)) { 40389 event.hover = getLanesRoot(hover) || hover; 40390 event.hoverGfx = elementRegistry.getGraphics(event.hover); 40391 } 40392 }); 40393 40394 40395 eventBus.on([ 40396 'connect.start' 40397 ], HIGH_PRIORITY$b, function(event) { 40398 var context = event.context, 40399 start = context.start; 40400 40401 // ensure connect start on bpmn:Participant, 40402 // not the underlying bpmn:Lane 40403 if (is$1(start, 'bpmn:Lane')) { 40404 context.start = getLanesRoot(start) || start; 40405 } 40406 }); 40407 40408 40409 // allow movement of participants from lanes 40410 eventBus.on('shape.move.start', HIGHEST_PRIORITY, function(event) { 40411 var shape = event.shape; 40412 40413 if (is$1(shape, 'bpmn:Lane')) { 40414 event.shape = getLanesRoot(shape) || shape; 40415 } 40416 }); 40417 40418 } 40419 40420 FixHoverBehavior.$inject = [ 40421 'elementRegistry', 40422 'eventBus', 40423 'canvas' 40424 ]; 40425 40426 /** 40427 * BPMN specific create data object behavior 40428 */ 40429 function CreateDataObjectBehavior(eventBus, bpmnFactory, moddle) { 40430 40431 CommandInterceptor.call(this, eventBus); 40432 40433 this.preExecute('shape.create', function(event) { 40434 40435 var context = event.context, 40436 shape = context.shape; 40437 40438 if (is$1(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') { 40439 40440 // create a DataObject every time a DataObjectReference is created 40441 var dataObject = bpmnFactory.create('bpmn:DataObject'); 40442 40443 // set the reference to the DataObject 40444 shape.businessObject.dataObjectRef = dataObject; 40445 } 40446 }); 40447 40448 } 40449 40450 CreateDataObjectBehavior.$inject = [ 40451 'eventBus', 40452 'bpmnFactory', 40453 'moddle' 40454 ]; 40455 40456 inherits$1(CreateDataObjectBehavior, CommandInterceptor); 40457 40458 var HORIZONTAL_PARTICIPANT_PADDING = 20, 40459 VERTICAL_PARTICIPANT_PADDING = 20; 40460 40461 var PARTICIPANT_BORDER_WIDTH = 30; 40462 40463 var HIGH_PRIORITY$a = 2000; 40464 40465 40466 /** 40467 * BPMN-specific behavior for creating participants. 40468 */ 40469 function CreateParticipantBehavior(canvas, eventBus, modeling) { 40470 CommandInterceptor.call(this, eventBus); 40471 40472 // fit participant 40473 eventBus.on([ 40474 'create.start', 40475 'shape.move.start' 40476 ], HIGH_PRIORITY$a, function(event) { 40477 var context = event.context, 40478 shape = context.shape, 40479 rootElement = canvas.getRootElement(); 40480 40481 if (!is$1(shape, 'bpmn:Participant') || 40482 !is$1(rootElement, 'bpmn:Process') || 40483 !rootElement.children.length) { 40484 return; 40485 } 40486 40487 // ignore connections, groups and labels 40488 var children = rootElement.children.filter(function(element) { 40489 return !is$1(element, 'bpmn:Group') && 40490 !isLabel$6(element) && 40491 !isConnection$9(element); 40492 }); 40493 40494 // ensure for available children to calculate bounds 40495 if (!children.length) { 40496 return; 40497 } 40498 40499 var childrenBBox = getBBox(children); 40500 40501 var participantBounds = getParticipantBounds(shape, childrenBBox); 40502 40503 // assign width and height 40504 assign(shape, participantBounds); 40505 40506 // assign create constraints 40507 context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox); 40508 }); 40509 40510 // force hovering process when creating first participant 40511 eventBus.on('create.start', HIGH_PRIORITY$a, function(event) { 40512 var context = event.context, 40513 shape = context.shape, 40514 rootElement = canvas.getRootElement(), 40515 rootElementGfx = canvas.getGraphics(rootElement); 40516 40517 function ensureHoveringProcess(event) { 40518 event.element = rootElement; 40519 event.gfx = rootElementGfx; 40520 } 40521 40522 if (is$1(shape, 'bpmn:Participant') && is$1(rootElement, 'bpmn:Process')) { 40523 eventBus.on('element.hover', HIGH_PRIORITY$a, ensureHoveringProcess); 40524 40525 eventBus.once('create.cleanup', function() { 40526 eventBus.off('element.hover', ensureHoveringProcess); 40527 }); 40528 } 40529 }); 40530 40531 function ensureCollaboration(context) { 40532 var parent = context.parent, 40533 collaboration; 40534 40535 var rootElement = canvas.getRootElement(); 40536 40537 if (is$1(rootElement, 'bpmn:Collaboration')) { 40538 collaboration = rootElement; 40539 } else { 40540 40541 // update root element by making collaboration 40542 collaboration = modeling.makeCollaboration(); 40543 40544 // re-use process when creating first participant 40545 context.process = parent; 40546 } 40547 40548 context.parent = collaboration; 40549 } 40550 40551 // turn process into collaboration before adding participant 40552 this.preExecute('shape.create', function(context) { 40553 var parent = context.parent, 40554 shape = context.shape; 40555 40556 if (is$1(shape, 'bpmn:Participant') && is$1(parent, 'bpmn:Process')) { 40557 ensureCollaboration(context); 40558 } 40559 }, true); 40560 40561 this.execute('shape.create', function(context) { 40562 var process = context.process, 40563 shape = context.shape; 40564 40565 if (process) { 40566 context.oldProcessRef = shape.businessObject.processRef; 40567 40568 // re-use process when creating first participant 40569 shape.businessObject.processRef = process.businessObject; 40570 } 40571 }, true); 40572 40573 this.revert('shape.create', function(context) { 40574 var process = context.process, 40575 shape = context.shape; 40576 40577 if (process) { 40578 40579 // re-use process when creating first participant 40580 shape.businessObject.processRef = context.oldProcessRef; 40581 } 40582 }, true); 40583 40584 this.postExecute('shape.create', function(context) { 40585 var process = context.process, 40586 shape = context.shape; 40587 40588 if (process) { 40589 40590 // move children from process to participant 40591 var processChildren = process.children.slice(); 40592 40593 modeling.moveElements(processChildren, { x: 0, y: 0 }, shape); 40594 } 40595 40596 }, true); 40597 40598 // turn process into collaboration when creating participants 40599 this.preExecute('elements.create', HIGH_PRIORITY$a, function(context) { 40600 var elements = context.elements, 40601 parent = context.parent, 40602 participant; 40603 40604 var hasParticipants = findParticipant(elements); 40605 40606 if (hasParticipants && is$1(parent, 'bpmn:Process')) { 40607 ensureCollaboration(context); 40608 40609 participant = findParticipant(elements); 40610 40611 context.oldProcessRef = participant.businessObject.processRef; 40612 40613 // re-use process when creating first participant 40614 participant.businessObject.processRef = parent.businessObject; 40615 } 40616 }, true); 40617 40618 this.revert('elements.create', function(context) { 40619 var elements = context.elements, 40620 process = context.process, 40621 participant; 40622 40623 if (process) { 40624 participant = findParticipant(elements); 40625 40626 // re-use process when creating first participant 40627 participant.businessObject.processRef = context.oldProcessRef; 40628 } 40629 }, true); 40630 40631 this.postExecute('elements.create', function(context) { 40632 var elements = context.elements, 40633 process = context.process, 40634 participant; 40635 40636 if (process) { 40637 participant = findParticipant(elements); 40638 40639 // move children from process to first participant 40640 var processChildren = process.children.slice(); 40641 40642 modeling.moveElements(processChildren, { x: 0, y: 0 }, participant); 40643 } 40644 40645 }, true); 40646 40647 } 40648 40649 CreateParticipantBehavior.$inject = [ 40650 'canvas', 40651 'eventBus', 40652 'modeling' 40653 ]; 40654 40655 inherits$1(CreateParticipantBehavior, CommandInterceptor); 40656 40657 // helpers ////////// 40658 40659 function getParticipantBounds(shape, childrenBBox) { 40660 childrenBBox = { 40661 width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH, 40662 height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2 40663 }; 40664 40665 var width = Math.max(shape.width, childrenBBox.width), 40666 height = Math.max(shape.height, childrenBBox.height); 40667 40668 return { 40669 x: -width / 2, 40670 y: -height / 2, 40671 width: width, 40672 height: height 40673 }; 40674 } 40675 40676 function getParticipantCreateConstraints(shape, childrenBBox) { 40677 childrenBBox = asTRBL(childrenBBox); 40678 40679 return { 40680 bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING, 40681 left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING, 40682 top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING, 40683 right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH 40684 }; 40685 } 40686 40687 function isConnection$9(element) { 40688 return !!element.waypoints; 40689 } 40690 40691 function findParticipant(elements) { 40692 return find(elements, function(element) { 40693 return is$1(element, 'bpmn:Participant'); 40694 }); 40695 } 40696 40697 var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder'; 40698 40699 40700 /** 40701 * This behavior makes sure we always set a fake 40702 * DataInputAssociation#targetRef as demanded by the BPMN 2.0 40703 * XSD schema. 40704 * 40705 * The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' } 40706 * which is created on the fly and cleaned up afterwards if not needed 40707 * anymore. 40708 * 40709 * @param {EventBus} eventBus 40710 * @param {BpmnFactory} bpmnFactory 40711 */ 40712 function DataInputAssociationBehavior(eventBus, bpmnFactory) { 40713 40714 CommandInterceptor.call(this, eventBus); 40715 40716 40717 this.executed([ 40718 'connection.create', 40719 'connection.delete', 40720 'connection.move', 40721 'connection.reconnect' 40722 ], ifDataInputAssociation(fixTargetRef)); 40723 40724 this.reverted([ 40725 'connection.create', 40726 'connection.delete', 40727 'connection.move', 40728 'connection.reconnect' 40729 ], ifDataInputAssociation(fixTargetRef)); 40730 40731 40732 function usesTargetRef(element, targetRef, removedConnection) { 40733 40734 var inputAssociations = element.get('dataInputAssociations'); 40735 40736 return find(inputAssociations, function(association) { 40737 return association !== removedConnection && 40738 association.targetRef === targetRef; 40739 }); 40740 } 40741 40742 function getTargetRef(element, create) { 40743 40744 var properties = element.get('properties'); 40745 40746 var targetRefProp = find(properties, function(p) { 40747 return p.name === TARGET_REF_PLACEHOLDER_NAME; 40748 }); 40749 40750 if (!targetRefProp && create) { 40751 targetRefProp = bpmnFactory.create('bpmn:Property', { 40752 name: TARGET_REF_PLACEHOLDER_NAME 40753 }); 40754 40755 add(properties, targetRefProp); 40756 } 40757 40758 return targetRefProp; 40759 } 40760 40761 function cleanupTargetRef(element, connection) { 40762 40763 var targetRefProp = getTargetRef(element); 40764 40765 if (!targetRefProp) { 40766 return; 40767 } 40768 40769 if (!usesTargetRef(element, targetRefProp, connection)) { 40770 remove(element.get('properties'), targetRefProp); 40771 } 40772 } 40773 40774 /** 40775 * Make sure targetRef is set to a valid property or 40776 * `null` if the connection is detached. 40777 * 40778 * @param {Event} event 40779 */ 40780 function fixTargetRef(event) { 40781 40782 var context = event.context, 40783 connection = context.connection, 40784 connectionBo = connection.businessObject, 40785 target = connection.target, 40786 targetBo = target && target.businessObject, 40787 newTarget = context.newTarget, 40788 newTargetBo = newTarget && newTarget.businessObject, 40789 oldTarget = context.oldTarget || context.target, 40790 oldTargetBo = oldTarget && oldTarget.businessObject; 40791 40792 var dataAssociation = connection.businessObject, 40793 targetRefProp; 40794 40795 if (oldTargetBo && oldTargetBo !== targetBo) { 40796 cleanupTargetRef(oldTargetBo, connectionBo); 40797 } 40798 40799 if (newTargetBo && newTargetBo !== targetBo) { 40800 cleanupTargetRef(newTargetBo, connectionBo); 40801 } 40802 40803 if (targetBo) { 40804 targetRefProp = getTargetRef(targetBo, true); 40805 dataAssociation.targetRef = targetRefProp; 40806 } else { 40807 dataAssociation.targetRef = null; 40808 } 40809 } 40810 } 40811 40812 DataInputAssociationBehavior.$inject = [ 40813 'eventBus', 40814 'bpmnFactory' 40815 ]; 40816 40817 inherits$1(DataInputAssociationBehavior, CommandInterceptor); 40818 40819 40820 /** 40821 * Only call the given function when the event 40822 * touches a bpmn:DataInputAssociation. 40823 * 40824 * @param {Function} fn 40825 * @return {Function} 40826 */ 40827 function ifDataInputAssociation(fn) { 40828 40829 return function(event) { 40830 var context = event.context, 40831 connection = context.connection; 40832 40833 if (is$1(connection, 'bpmn:DataInputAssociation')) { 40834 return fn(event); 40835 } 40836 }; 40837 } 40838 40839 function UpdateSemanticParentHandler(bpmnUpdater) { 40840 this._bpmnUpdater = bpmnUpdater; 40841 } 40842 40843 UpdateSemanticParentHandler.$inject = [ 'bpmnUpdater' ]; 40844 40845 40846 UpdateSemanticParentHandler.prototype.execute = function(context) { 40847 var dataStoreBo = context.dataStoreBo, 40848 newSemanticParent = context.newSemanticParent, 40849 newDiParent = context.newDiParent; 40850 40851 context.oldSemanticParent = dataStoreBo.$parent; 40852 context.oldDiParent = dataStoreBo.di.$parent; 40853 40854 // update semantic parent 40855 this._bpmnUpdater.updateSemanticParent(dataStoreBo, newSemanticParent); 40856 40857 // update DI parent 40858 this._bpmnUpdater.updateDiParent(dataStoreBo.di, newDiParent); 40859 }; 40860 40861 UpdateSemanticParentHandler.prototype.revert = function(context) { 40862 var dataStoreBo = context.dataStoreBo, 40863 oldSemanticParent = context.oldSemanticParent, 40864 oldDiParent = context.oldDiParent; 40865 40866 // update semantic parent 40867 this._bpmnUpdater.updateSemanticParent(dataStoreBo, oldSemanticParent); 40868 40869 // update DI parent 40870 this._bpmnUpdater.updateDiParent(dataStoreBo.di, oldDiParent); 40871 }; 40872 40873 /** 40874 * BPMN specific data store behavior 40875 */ 40876 function DataStoreBehavior( 40877 canvas, commandStack, elementRegistry, 40878 eventBus) { 40879 40880 CommandInterceptor.call(this, eventBus); 40881 40882 commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler); 40883 40884 function getFirstParticipantWithProcessRef() { 40885 return elementRegistry.filter(function(element) { 40886 return is$1(element, 'bpmn:Participant') && getBusinessObject(element).processRef; 40887 })[0]; 40888 } 40889 40890 function getDataStores(element) { 40891 return element.children.filter(function(child) { 40892 return is$1(child, 'bpmn:DataStoreReference') && !child.labelTarget; 40893 }); 40894 } 40895 40896 function updateDataStoreParent(dataStore, newDataStoreParent) { 40897 var dataStoreBo = dataStore.businessObject || dataStore; 40898 40899 newDataStoreParent = newDataStoreParent || getFirstParticipantWithProcessRef(); 40900 40901 if (newDataStoreParent) { 40902 var newDataStoreParentBo = newDataStoreParent.businessObject || newDataStoreParent; 40903 40904 commandStack.execute('dataStore.updateContainment', { 40905 dataStoreBo: dataStoreBo, 40906 newSemanticParent: newDataStoreParentBo.processRef || newDataStoreParentBo, 40907 newDiParent: newDataStoreParentBo.di 40908 }); 40909 } 40910 } 40911 40912 40913 // disable auto-resize for data stores 40914 this.preExecute('shape.create', function(event) { 40915 40916 var context = event.context, 40917 shape = context.shape; 40918 40919 if (is$1(shape, 'bpmn:DataStoreReference') && 40920 shape.type !== 'label') { 40921 40922 if (!context.hints) { 40923 context.hints = {}; 40924 } 40925 40926 // prevent auto resizing 40927 context.hints.autoResize = false; 40928 } 40929 }); 40930 40931 40932 // disable auto-resize for data stores 40933 this.preExecute('elements.move', function(event) { 40934 var context = event.context, 40935 shapes = context.shapes; 40936 40937 var dataStoreReferences = shapes.filter(function(shape) { 40938 return is$1(shape, 'bpmn:DataStoreReference'); 40939 }); 40940 40941 if (dataStoreReferences.length) { 40942 if (!context.hints) { 40943 context.hints = {}; 40944 } 40945 40946 // prevent auto resizing for data store references 40947 context.hints.autoResize = shapes.filter(function(shape) { 40948 return !is$1(shape, 'bpmn:DataStoreReference'); 40949 }); 40950 } 40951 }); 40952 40953 40954 // update parent on data store created 40955 this.postExecute('shape.create', function(event) { 40956 var context = event.context, 40957 shape = context.shape, 40958 parent = shape.parent; 40959 40960 40961 if (is$1(shape, 'bpmn:DataStoreReference') && 40962 shape.type !== 'label' && 40963 is$1(parent, 'bpmn:Collaboration')) { 40964 40965 updateDataStoreParent(shape); 40966 } 40967 }); 40968 40969 40970 // update parent on data store moved 40971 this.postExecute('shape.move', function(event) { 40972 var context = event.context, 40973 shape = context.shape, 40974 oldParent = context.oldParent, 40975 parent = shape.parent; 40976 40977 if (is$1(oldParent, 'bpmn:Collaboration')) { 40978 40979 // do nothing if not necessary 40980 return; 40981 } 40982 40983 if (is$1(shape, 'bpmn:DataStoreReference') && 40984 shape.type !== 'label' && 40985 is$1(parent, 'bpmn:Collaboration')) { 40986 40987 var participant = is$1(oldParent, 'bpmn:Participant') ? 40988 oldParent : 40989 getAncestor(oldParent, 'bpmn:Participant'); 40990 40991 updateDataStoreParent(shape, participant); 40992 } 40993 }); 40994 40995 40996 // update data store parents on participant or subprocess deleted 40997 this.postExecute('shape.delete', function(event) { 40998 var context = event.context, 40999 shape = context.shape, 41000 rootElement = canvas.getRootElement(); 41001 41002 if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess' ]) 41003 && is$1(rootElement, 'bpmn:Collaboration')) { 41004 getDataStores(rootElement) 41005 .filter(function(dataStore) { 41006 return isDescendant(dataStore, shape); 41007 }) 41008 .forEach(function(dataStore) { 41009 updateDataStoreParent(dataStore); 41010 }); 41011 } 41012 }); 41013 41014 // update data store parents on collaboration -> process 41015 this.postExecute('canvas.updateRoot', function(event) { 41016 var context = event.context, 41017 oldRoot = context.oldRoot, 41018 newRoot = context.newRoot; 41019 41020 var dataStores = getDataStores(oldRoot); 41021 41022 dataStores.forEach(function(dataStore) { 41023 41024 if (is$1(newRoot, 'bpmn:Process')) { 41025 updateDataStoreParent(dataStore, newRoot); 41026 } 41027 41028 }); 41029 }); 41030 } 41031 41032 DataStoreBehavior.$inject = [ 41033 'canvas', 41034 'commandStack', 41035 'elementRegistry', 41036 'eventBus', 41037 ]; 41038 41039 inherits$1(DataStoreBehavior, CommandInterceptor); 41040 41041 41042 // helpers ////////// 41043 41044 function isDescendant(descendant, ancestor) { 41045 var descendantBo = descendant.businessObject || descendant, 41046 ancestorBo = ancestor.businessObject || ancestor; 41047 41048 while (descendantBo.$parent) { 41049 if (descendantBo.$parent === ancestorBo.processRef || ancestorBo) { 41050 return true; 41051 } 41052 41053 descendantBo = descendantBo.$parent; 41054 } 41055 41056 return false; 41057 } 41058 41059 function getAncestor(element, type) { 41060 41061 while (element.parent) { 41062 if (is$1(element.parent, type)) { 41063 return element.parent; 41064 } 41065 41066 element = element.parent; 41067 } 41068 } 41069 41070 var LOW_PRIORITY$a = 500; 41071 41072 41073 /** 41074 * BPMN specific delete lane behavior 41075 */ 41076 function DeleteLaneBehavior(eventBus, modeling, spaceTool) { 41077 41078 CommandInterceptor.call(this, eventBus); 41079 41080 41081 function compensateLaneDelete(shape, oldParent) { 41082 41083 var siblings = getChildLanes(oldParent); 41084 41085 var topAffected = []; 41086 var bottomAffected = []; 41087 41088 eachElement(siblings, function(element) { 41089 41090 if (element.y > shape.y) { 41091 bottomAffected.push(element); 41092 } else { 41093 topAffected.push(element); 41094 } 41095 41096 return element.children; 41097 }); 41098 41099 if (!siblings.length) { 41100 return; 41101 } 41102 41103 var offset; 41104 41105 if (bottomAffected.length && topAffected.length) { 41106 offset = shape.height / 2; 41107 } else { 41108 offset = shape.height; 41109 } 41110 41111 var topAdjustments, 41112 bottomAdjustments; 41113 41114 if (topAffected.length) { 41115 topAdjustments = spaceTool.calculateAdjustments( 41116 topAffected, 'y', offset, shape.y - 10); 41117 41118 spaceTool.makeSpace( 41119 topAdjustments.movingShapes, 41120 topAdjustments.resizingShapes, 41121 { x: 0, y: offset }, 's'); 41122 } 41123 41124 if (bottomAffected.length) { 41125 bottomAdjustments = spaceTool.calculateAdjustments( 41126 bottomAffected, 'y', -offset, shape.y + shape.height + 10); 41127 41128 spaceTool.makeSpace( 41129 bottomAdjustments.movingShapes, 41130 bottomAdjustments.resizingShapes, 41131 { x: 0, y: -offset }, 'n'); 41132 } 41133 } 41134 41135 41136 /** 41137 * Adjust sizes of other lanes after lane deletion 41138 */ 41139 this.postExecuted('shape.delete', LOW_PRIORITY$a, function(event) { 41140 41141 var context = event.context, 41142 hints = context.hints, 41143 shape = context.shape, 41144 oldParent = context.oldParent; 41145 41146 // only compensate lane deletes 41147 if (!is$1(shape, 'bpmn:Lane')) { 41148 return; 41149 } 41150 41151 // compensate root deletes only 41152 if (hints && hints.nested) { 41153 return; 41154 } 41155 41156 compensateLaneDelete(shape, oldParent); 41157 }); 41158 } 41159 41160 DeleteLaneBehavior.$inject = [ 41161 'eventBus', 41162 'modeling', 41163 'spaceTool' 41164 ]; 41165 41166 inherits$1(DeleteLaneBehavior, CommandInterceptor); 41167 41168 var LOW_PRIORITY$9 = 500; 41169 41170 41171 /** 41172 * Replace boundary event with intermediate event when creating or moving results in detached event. 41173 */ 41174 function DetachEventBehavior(bpmnReplace, injector) { 41175 injector.invoke(CommandInterceptor, this); 41176 41177 this._bpmnReplace = bpmnReplace; 41178 41179 var self = this; 41180 41181 this.postExecuted('elements.create', LOW_PRIORITY$9, function(context) { 41182 var elements = context.elements; 41183 41184 elements.filter(function(shape) { 41185 var host = shape.host; 41186 41187 return shouldReplace(shape, host); 41188 }).map(function(shape) { 41189 return elements.indexOf(shape); 41190 }).forEach(function(index) { 41191 context.elements[ index ] = self.replaceShape(elements[ index ]); 41192 }); 41193 }, true); 41194 41195 this.preExecute('elements.move', LOW_PRIORITY$9, function(context) { 41196 var shapes = context.shapes, 41197 newHost = context.newHost; 41198 41199 shapes.forEach(function(shape, index) { 41200 var host = shape.host; 41201 41202 if (shouldReplace(shape, includes$6(shapes, host) ? host : newHost)) { 41203 shapes[ index ] = self.replaceShape(shape); 41204 } 41205 }); 41206 }, true); 41207 } 41208 41209 DetachEventBehavior.$inject = [ 41210 'bpmnReplace', 41211 'injector' 41212 ]; 41213 41214 inherits$1(DetachEventBehavior, CommandInterceptor); 41215 41216 DetachEventBehavior.prototype.replaceShape = function(shape) { 41217 var eventDefinition = getEventDefinition(shape), 41218 intermediateEvent; 41219 41220 if (eventDefinition) { 41221 intermediateEvent = { 41222 type: 'bpmn:IntermediateCatchEvent', 41223 eventDefinitionType: eventDefinition.$type 41224 }; 41225 } else { 41226 intermediateEvent = { 41227 type: 'bpmn:IntermediateThrowEvent' 41228 }; 41229 } 41230 41231 return this._bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false }); 41232 }; 41233 41234 41235 // helpers ////////// 41236 41237 function getEventDefinition(element) { 41238 var businessObject = getBusinessObject(element), 41239 eventDefinitions = businessObject.eventDefinitions; 41240 41241 return eventDefinitions && eventDefinitions[0]; 41242 } 41243 41244 function shouldReplace(shape, host) { 41245 return !isLabel$6(shape) && is$1(shape, 'bpmn:BoundaryEvent') && !host; 41246 } 41247 41248 function includes$6(array, item) { 41249 return array.indexOf(item) !== -1; 41250 } 41251 41252 function DropOnFlowBehavior(eventBus, bpmnRules, modeling) { 41253 41254 CommandInterceptor.call(this, eventBus); 41255 41256 /** 41257 * Reconnect start / end of a connection after 41258 * dropping an element on a flow. 41259 */ 41260 41261 function insertShape(shape, targetFlow, positionOrBounds) { 41262 var waypoints = targetFlow.waypoints, 41263 waypointsBefore, 41264 waypointsAfter, 41265 dockingPoint, 41266 source, 41267 target, 41268 incomingConnection, 41269 outgoingConnection, 41270 oldOutgoing = shape.outgoing.slice(), 41271 oldIncoming = shape.incoming.slice(); 41272 41273 var mid; 41274 41275 if (isNumber(positionOrBounds.width)) { 41276 mid = getMid(positionOrBounds); 41277 } else { 41278 mid = positionOrBounds; 41279 } 41280 41281 var intersection = getApproxIntersection(waypoints, mid); 41282 41283 if (intersection) { 41284 waypointsBefore = waypoints.slice(0, intersection.index); 41285 waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0)); 41286 41287 // due to inaccuracy intersection might have been found 41288 if (!waypointsBefore.length || !waypointsAfter.length) { 41289 return; 41290 } 41291 41292 dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : mid; 41293 41294 // if last waypointBefore is inside shape's bounds, ignore docking point 41295 if (!isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length-1])) { 41296 waypointsBefore.push(copy(dockingPoint)); 41297 } 41298 41299 // if first waypointAfter is inside shape's bounds, ignore docking point 41300 if (!isPointInsideBBox(shape, waypointsAfter[0])) { 41301 waypointsAfter.unshift(copy(dockingPoint)); 41302 } 41303 } 41304 41305 source = targetFlow.source; 41306 target = targetFlow.target; 41307 41308 if (bpmnRules.canConnect(source, shape, targetFlow)) { 41309 41310 // reconnect source -> inserted shape 41311 modeling.reconnectEnd(targetFlow, shape, waypointsBefore || mid); 41312 41313 incomingConnection = targetFlow; 41314 } 41315 41316 if (bpmnRules.canConnect(shape, target, targetFlow)) { 41317 41318 if (!incomingConnection) { 41319 41320 // reconnect inserted shape -> end 41321 modeling.reconnectStart(targetFlow, shape, waypointsAfter || mid); 41322 41323 outgoingConnection = targetFlow; 41324 } else { 41325 outgoingConnection = modeling.connect( 41326 shape, target, { type: targetFlow.type, waypoints: waypointsAfter } 41327 ); 41328 } 41329 } 41330 41331 var duplicateConnections = [].concat( 41332 41333 incomingConnection && filter(oldIncoming, function(connection) { 41334 return connection.source === incomingConnection.source; 41335 }) || [], 41336 41337 outgoingConnection && filter(oldOutgoing, function(connection) { 41338 return connection.target === outgoingConnection.target; 41339 }) || [] 41340 ); 41341 41342 if (duplicateConnections.length) { 41343 modeling.removeElements(duplicateConnections); 41344 } 41345 } 41346 41347 this.preExecute('elements.move', function(context) { 41348 41349 var newParent = context.newParent, 41350 shapes = context.shapes, 41351 delta = context.delta, 41352 shape = shapes[0]; 41353 41354 if (!shape || !newParent) { 41355 return; 41356 } 41357 41358 // if the new parent is a connection, 41359 // change it to the new parent's parent 41360 if (newParent && newParent.waypoints) { 41361 context.newParent = newParent = newParent.parent; 41362 } 41363 41364 var shapeMid = getMid(shape); 41365 var newShapeMid = { 41366 x: shapeMid.x + delta.x, 41367 y: shapeMid.y + delta.y 41368 }; 41369 41370 // find a connection which intersects with the 41371 // element's mid point 41372 var connection = find(newParent.children, function(element) { 41373 var canInsert = bpmnRules.canInsert(shapes, element); 41374 41375 return canInsert && getApproxIntersection(element.waypoints, newShapeMid); 41376 }); 41377 41378 if (connection) { 41379 context.targetFlow = connection; 41380 context.position = newShapeMid; 41381 } 41382 41383 }, true); 41384 41385 this.postExecuted('elements.move', function(context) { 41386 41387 var shapes = context.shapes, 41388 targetFlow = context.targetFlow, 41389 position = context.position; 41390 41391 if (targetFlow) { 41392 insertShape(shapes[0], targetFlow, position); 41393 } 41394 41395 }, true); 41396 41397 this.preExecute('shape.create', function(context) { 41398 41399 var parent = context.parent, 41400 shape = context.shape; 41401 41402 if (bpmnRules.canInsert(shape, parent)) { 41403 context.targetFlow = parent; 41404 context.parent = parent.parent; 41405 } 41406 }, true); 41407 41408 this.postExecuted('shape.create', function(context) { 41409 41410 var shape = context.shape, 41411 targetFlow = context.targetFlow, 41412 positionOrBounds = context.position; 41413 41414 if (targetFlow) { 41415 insertShape(shape, targetFlow, positionOrBounds); 41416 } 41417 }, true); 41418 } 41419 41420 inherits$1(DropOnFlowBehavior, CommandInterceptor); 41421 41422 DropOnFlowBehavior.$inject = [ 41423 'eventBus', 41424 'bpmnRules', 41425 'modeling' 41426 ]; 41427 41428 41429 // helpers ///////////////////// 41430 41431 function isPointInsideBBox(bbox, point) { 41432 var x = point.x, 41433 y = point.y; 41434 41435 return x >= bbox.x && 41436 x <= bbox.x + bbox.width && 41437 y >= bbox.y && 41438 y <= bbox.y + bbox.height; 41439 } 41440 41441 function copy(obj) { 41442 return assign({}, obj); 41443 } 41444 41445 function EventBasedGatewayBehavior(eventBus, modeling) { 41446 41447 CommandInterceptor.call(this, eventBus); 41448 41449 /** 41450 * Remove existing sequence flows of event-based target before connecting 41451 * from event-based gateway. 41452 */ 41453 this.preExecuted('connection.create', function(event) { 41454 41455 var context = event.context, 41456 source = context.source, 41457 target = context.target, 41458 existingIncomingConnections = target.incoming.slice(); 41459 41460 if (context.hints && context.hints.createElementsBehavior === false) { 41461 return; 41462 } 41463 41464 if ( 41465 is$1(source, 'bpmn:EventBasedGateway') && 41466 target.incoming.length 41467 ) { 41468 41469 existingIncomingConnections.filter(isSequenceFlow) 41470 .forEach(function(sequenceFlow) { 41471 modeling.removeConnection(sequenceFlow); 41472 }); 41473 } 41474 }); 41475 41476 /** 41477 * After replacing shape with event-based gateway, remove incoming sequence 41478 * flows of event-based targets which do not belong to event-based gateway 41479 * source. 41480 */ 41481 this.preExecuted('shape.replace', function(event) { 41482 41483 var newShape = event.context.newShape, 41484 newShapeTargets, 41485 newShapeTargetsIncomingSequenceFlows; 41486 41487 if (!is$1(newShape, 'bpmn:EventBasedGateway')) { 41488 return; 41489 } 41490 41491 newShapeTargets = newShape.outgoing.filter(isSequenceFlow) 41492 .map(function(sequenceFlow) { 41493 return sequenceFlow.target; 41494 }); 41495 41496 newShapeTargetsIncomingSequenceFlows = newShapeTargets.reduce(function(sequenceFlows, target) { 41497 var incomingSequenceFlows = target.incoming.filter(isSequenceFlow); 41498 41499 return sequenceFlows.concat(incomingSequenceFlows); 41500 }, []); 41501 41502 newShapeTargetsIncomingSequenceFlows.forEach(function(sequenceFlow) { 41503 if (sequenceFlow.source !== newShape) { 41504 modeling.removeConnection(sequenceFlow); 41505 } 41506 }); 41507 }); 41508 } 41509 41510 EventBasedGatewayBehavior.$inject = [ 41511 'eventBus', 41512 'modeling' 41513 ]; 41514 41515 inherits$1(EventBasedGatewayBehavior, CommandInterceptor); 41516 41517 41518 41519 // helpers ////////////////////// 41520 41521 function isSequenceFlow(connection) { 41522 return is$1(connection, 'bpmn:SequenceFlow'); 41523 } 41524 41525 var HIGH_PRIORITY$9 = 2000; 41526 41527 41528 /** 41529 * BPMN specific Group behavior 41530 */ 41531 function GroupBehavior( 41532 bpmnFactory, 41533 canvas, 41534 elementRegistry, 41535 eventBus, 41536 injector, 41537 moddleCopy 41538 ) { 41539 injector.invoke(CommandInterceptor, this); 41540 41541 /** 41542 * Gets process definitions 41543 * 41544 * @return {ModdleElement} definitions 41545 */ 41546 function getDefinitions() { 41547 var rootElement = canvas.getRootElement(), 41548 businessObject = getBusinessObject(rootElement); 41549 41550 return businessObject.$parent; 41551 } 41552 41553 /** 41554 * Removes a referenced category value for a given group shape 41555 * 41556 * @param {djs.model.Shape} shape 41557 */ 41558 function removeReferencedCategoryValue(shape) { 41559 41560 var businessObject = getBusinessObject(shape), 41561 categoryValue = businessObject.categoryValueRef; 41562 41563 if (!categoryValue) { 41564 return; 41565 } 41566 41567 var category = categoryValue.$parent; 41568 41569 if (!categoryValue) { 41570 return; 41571 } 41572 41573 remove(category.categoryValue, categoryValue); 41574 41575 // cleanup category if it is empty 41576 if (category && !category.categoryValue.length) { 41577 removeCategory(category); 41578 } 41579 } 41580 41581 /** 41582 * Removes a given category from the definitions 41583 * 41584 * @param {ModdleElement} category 41585 */ 41586 function removeCategory(category) { 41587 41588 var definitions = getDefinitions(); 41589 41590 remove(definitions.get('rootElements'), category); 41591 } 41592 41593 /** 41594 * Returns all group element in the current registry 41595 * 41596 * @return {Array<djs.model.shape>} a list of group shapes 41597 */ 41598 function getGroupElements() { 41599 return elementRegistry.filter(function(e) { 41600 return is$1(e, 'bpmn:Group'); 41601 }); 41602 } 41603 41604 /** 41605 * Returns true if given categoryValue is referenced in one of the given elements 41606 * 41607 * @param {Array<djs.model.shape>} elements 41608 * @param {ModdleElement} categoryValue 41609 * @return {boolean} 41610 */ 41611 function isReferenced(elements, categoryValue) { 41612 return elements.some(function(e) { 41613 41614 var businessObject = getBusinessObject(e); 41615 41616 return businessObject.categoryValueRef 41617 && businessObject.categoryValueRef === categoryValue; 41618 }); 41619 } 41620 41621 /** 41622 * remove referenced category + value when group was deleted 41623 */ 41624 this.executed('shape.delete', function(event) { 41625 41626 var context = event.context, 41627 shape = context.shape; 41628 41629 if (is$1(shape, 'bpmn:Group')) { 41630 41631 var businessObject = getBusinessObject(shape), 41632 categoryValueRef = businessObject.categoryValueRef, 41633 groupElements = getGroupElements(); 41634 41635 if (!isReferenced(groupElements, categoryValueRef)) { 41636 removeReferencedCategoryValue(shape); 41637 } 41638 } 41639 }); 41640 41641 /** 41642 * re-attach removed category 41643 */ 41644 this.reverted('shape.delete', function(event) { 41645 41646 var context = event.context, 41647 shape = context.shape; 41648 41649 if (is$1(shape, 'bpmn:Group')) { 41650 41651 var businessObject = getBusinessObject(shape), 41652 categoryValueRef = businessObject.categoryValueRef, 41653 definitions = getDefinitions(), 41654 category = categoryValueRef ? categoryValueRef.$parent : null; 41655 41656 add(category.get('categoryValue'), categoryValueRef); 41657 add(definitions.get('rootElements'), category); 41658 } 41659 }); 41660 41661 /** 41662 * create new category + value when group was created 41663 */ 41664 this.execute('shape.create', function(event) { 41665 var context = event.context, 41666 shape = context.shape, 41667 businessObject = getBusinessObject(shape); 41668 41669 if (is$1(businessObject, 'bpmn:Group') && !businessObject.categoryValueRef) { 41670 41671 var definitions = getDefinitions(), 41672 categoryValue = createCategoryValue(definitions, bpmnFactory); 41673 41674 // link the reference to the Group 41675 businessObject.categoryValueRef = categoryValue; 41676 } 41677 }); 41678 41679 41680 this.revert('shape.create', function(event) { 41681 41682 var context = event.context, 41683 shape = context.shape; 41684 41685 if (is$1(shape, 'bpmn:Group')) { 41686 removeReferencedCategoryValue(shape); 41687 41688 delete getBusinessObject(shape).categoryValueRef; 41689 41690 } 41691 }); 41692 41693 // copy bpmn:CategoryValue when copying element 41694 eventBus.on('moddleCopy.canCopyProperty', HIGH_PRIORITY$9, function(context) { 41695 var property = context.property, 41696 categoryValue; 41697 41698 if (is$1(property, 'bpmn:CategoryValue')) { 41699 categoryValue = createCategoryValue(getDefinitions(), bpmnFactory); 41700 41701 // return copy of category 41702 return moddleCopy.copyElement(property, categoryValue); 41703 } 41704 }); 41705 41706 } 41707 41708 GroupBehavior.$inject = [ 41709 'bpmnFactory', 41710 'canvas', 41711 'elementRegistry', 41712 'eventBus', 41713 'injector', 41714 'moddleCopy' 41715 ]; 41716 41717 inherits$1(GroupBehavior, CommandInterceptor); 41718 41719 /** 41720 * Returns the intersection between two line segments a and b. 41721 * 41722 * @param {Point} l1s 41723 * @param {Point} l1e 41724 * @param {Point} l2s 41725 * @param {Point} l2e 41726 * 41727 * @return {Point} 41728 */ 41729 function lineIntersect(l1s, l1e, l2s, l2e) { 41730 41731 // if the lines intersect, the result contains the x and y of the 41732 // intersection (treating the lines as infinite) and booleans for 41733 // whether line segment 1 or line segment 2 contain the point 41734 var denominator, a, b, c, numerator; 41735 41736 denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y)); 41737 41738 if (denominator == 0) { 41739 return null; 41740 } 41741 41742 a = l1s.y - l2s.y; 41743 b = l1s.x - l2s.x; 41744 numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b); 41745 41746 c = numerator / denominator; 41747 41748 // if we cast these lines infinitely in 41749 // both directions, they intersect here 41750 return { 41751 x: Math.round(l1s.x + (c * (l1e.x - l1s.x))), 41752 y: Math.round(l1s.y + (c * (l1e.y - l1s.y))) 41753 }; 41754 } 41755 41756 /** 41757 * Fix broken dockings after DI imports. 41758 * 41759 * @param {EventBus} eventBus 41760 */ 41761 function ImportDockingFix(eventBus) { 41762 41763 function adjustDocking(startPoint, nextPoint, elementMid) { 41764 41765 var elementTop = { 41766 x: elementMid.x, 41767 y: elementMid.y - 50 41768 }; 41769 41770 var elementLeft = { 41771 x: elementMid.x - 50, 41772 y: elementMid.y 41773 }; 41774 41775 var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop), 41776 horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft); 41777 41778 // original is horizontal or vertical center cross intersection 41779 var centerIntersect; 41780 41781 if (verticalIntersect && horizontalIntersect) { 41782 if (getDistance$1(verticalIntersect, elementMid) > getDistance$1(horizontalIntersect, elementMid)) { 41783 centerIntersect = horizontalIntersect; 41784 } else { 41785 centerIntersect = verticalIntersect; 41786 } 41787 } else { 41788 centerIntersect = verticalIntersect || horizontalIntersect; 41789 } 41790 41791 startPoint.original = centerIntersect; 41792 } 41793 41794 function fixDockings(connection) { 41795 var waypoints = connection.waypoints; 41796 41797 adjustDocking( 41798 waypoints[0], 41799 waypoints[1], 41800 getMid(connection.source) 41801 ); 41802 41803 adjustDocking( 41804 waypoints[waypoints.length - 1], 41805 waypoints[waypoints.length - 2], 41806 getMid(connection.target) 41807 ); 41808 } 41809 41810 eventBus.on('bpmnElement.added', function(e) { 41811 41812 var element = e.element; 41813 41814 if (element.waypoints) { 41815 fixDockings(element); 41816 } 41817 }); 41818 } 41819 41820 ImportDockingFix.$inject = [ 41821 'eventBus' 41822 ]; 41823 41824 41825 // helpers ////////////////////// 41826 41827 function getDistance$1(p1, p2) { 41828 return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); 41829 } 41830 41831 /** 41832 * A component that makes sure that each created or updated 41833 * Pool and Lane is assigned an isHorizontal property set to true. 41834 * 41835 * @param {EventBus} eventBus 41836 */ 41837 function IsHorizontalFix(eventBus) { 41838 41839 CommandInterceptor.call(this, eventBus); 41840 41841 var elementTypesToUpdate = [ 41842 'bpmn:Participant', 41843 'bpmn:Lane' 41844 ]; 41845 41846 this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], function(event) { 41847 var bo = getBusinessObject(event.context.shape); 41848 41849 if (isAny(bo, elementTypesToUpdate) && !bo.di.get('isHorizontal')) { 41850 41851 // set attribute directly to avoid modeling#updateProperty side effects 41852 bo.di.set('isHorizontal', true); 41853 } 41854 }); 41855 41856 } 41857 41858 IsHorizontalFix.$inject = [ 'eventBus' ]; 41859 41860 inherits$1(IsHorizontalFix, CommandInterceptor); 41861 41862 /** 41863 * Returns the length of a vector 41864 * 41865 * @param {Vector} 41866 * @return {Float} 41867 */ 41868 function vectorLength(v) { 41869 return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2)); 41870 } 41871 41872 41873 /** 41874 * Calculates the angle between a line a the yAxis 41875 * 41876 * @param {Array} 41877 * @return {Float} 41878 */ 41879 function getAngle(line) { 41880 41881 // return value is between 0, 180 and -180, -0 41882 // @janstuemmel: maybe replace return a/b with b/a 41883 return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x)); 41884 } 41885 41886 41887 /** 41888 * Rotates a vector by a given angle 41889 * 41890 * @param {Vector} 41891 * @param {Float} Angle in radians 41892 * @return {Vector} 41893 */ 41894 function rotateVector(vector, angle) { 41895 return (!angle) ? vector : { 41896 x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y, 41897 y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y 41898 }; 41899 } 41900 41901 41902 /** 41903 * Solves a 2D equation system 41904 * a + r*b = c, where a,b,c are 2D vectors 41905 * 41906 * @param {Vector} 41907 * @param {Vector} 41908 * @param {Vector} 41909 * @return {Float} 41910 */ 41911 function solveLambaSystem(a, b, c) { 41912 41913 // the 2d system 41914 var system = [ 41915 { n: a[0] - c[0], lambda: b[0] }, 41916 { n: a[1] - c[1], lambda: b[1] } 41917 ]; 41918 41919 // solve 41920 var n = system[0].n * b[0] + system[1].n * b[1], 41921 l = system[0].lambda * b[0] + system[1].lambda * b[1]; 41922 41923 return -n/l; 41924 } 41925 41926 41927 /** 41928 * Position of perpendicular foot 41929 * 41930 * @param {Point} 41931 * @param [ {Point}, {Point} ] line defined through two points 41932 * @return {Point} the perpendicular foot position 41933 */ 41934 function perpendicularFoot(point, line) { 41935 41936 var a = line[0], b = line[1]; 41937 41938 // relative position of b from a 41939 var bd = { x: b.x - a.x, y: b.y - a.y }; 41940 41941 // solve equation system to the parametrized vectors param real value 41942 var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]); 41943 41944 return { x: a.x + r*bd.x, y: a.y + r*bd.y }; 41945 } 41946 41947 41948 /** 41949 * Calculates the distance between a point and a line 41950 * 41951 * @param {Point} 41952 * @param [ {Point}, {Point} ] line defined through two points 41953 * @return {Float} distance 41954 */ 41955 function getDistancePointLine(point, line) { 41956 41957 var pfPoint = perpendicularFoot(point, line); 41958 41959 // distance vector 41960 var connectionVector = { 41961 x: pfPoint.x - point.x, 41962 y: pfPoint.y - point.y 41963 }; 41964 41965 return vectorLength(connectionVector); 41966 } 41967 41968 41969 /** 41970 * Calculates the distance between two points 41971 * 41972 * @param {Point} 41973 * @param {Point} 41974 * @return {Float} distance 41975 */ 41976 function getDistancePointPoint(point1, point2) { 41977 41978 return vectorLength({ 41979 x: point1.x - point2.x, 41980 y: point1.y - point2.y 41981 }); 41982 } 41983 41984 var sqrt = Math.sqrt, 41985 min$1 = Math.min, 41986 max$3 = Math.max, 41987 abs$3 = Math.abs; 41988 41989 /** 41990 * Calculate the square (power to two) of a number. 41991 * 41992 * @param {number} n 41993 * 41994 * @return {number} 41995 */ 41996 function sq(n) { 41997 return Math.pow(n, 2); 41998 } 41999 42000 /** 42001 * Get distance between two points. 42002 * 42003 * @param {Point} p1 42004 * @param {Point} p2 42005 * 42006 * @return {number} 42007 */ 42008 function getDistance(p1, p2) { 42009 return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y)); 42010 } 42011 42012 /** 42013 * Return the attachment of the given point on the specified line. 42014 * 42015 * The attachment is either a bendpoint (attached to the given point) 42016 * or segment (attached to a location on a line segment) attachment: 42017 * 42018 * ```javascript 42019 * var pointAttachment = { 42020 * type: 'bendpoint', 42021 * bendpointIndex: 3, 42022 * position: { x: 10, y: 10 } // the attach point on the line 42023 * }; 42024 * 42025 * var segmentAttachment = { 42026 * type: 'segment', 42027 * segmentIndex: 2, 42028 * relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end) 42029 * position: { x: 10, y: 10 } // the attach point on the line 42030 * }; 42031 * ``` 42032 * 42033 * @param {Point} point 42034 * @param {Array<Point>} line 42035 * 42036 * @return {Object} attachment 42037 */ 42038 function getAttachment(point, line) { 42039 42040 var idx = 0, 42041 segmentStart, 42042 segmentEnd, 42043 segmentStartDistance, 42044 segmentEndDistance, 42045 attachmentPosition, 42046 minDistance, 42047 intersections, 42048 attachment, 42049 attachmentDistance, 42050 closestAttachmentDistance, 42051 closestAttachment; 42052 42053 for (idx = 0; idx < line.length - 1; idx++) { 42054 42055 segmentStart = line[idx]; 42056 segmentEnd = line[idx + 1]; 42057 42058 if (pointsEqual(segmentStart, segmentEnd)) { 42059 intersections = [ segmentStart ]; 42060 } else { 42061 segmentStartDistance = getDistance(point, segmentStart); 42062 segmentEndDistance = getDistance(point, segmentEnd); 42063 42064 minDistance = min$1(segmentStartDistance, segmentEndDistance); 42065 42066 intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance); 42067 } 42068 42069 if (intersections.length < 1) { 42070 throw new Error('expected between [1, 2] circle -> line intersections'); 42071 } 42072 42073 // one intersection -> bendpoint attachment 42074 if (intersections.length === 1) { 42075 attachment = { 42076 type: 'bendpoint', 42077 position: intersections[0], 42078 segmentIndex: idx, 42079 bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1 42080 }; 42081 } 42082 42083 // two intersections -> segment attachment 42084 if (intersections.length === 2) { 42085 42086 attachmentPosition = mid$1(intersections[0], intersections[1]); 42087 42088 attachment = { 42089 type: 'segment', 42090 position: attachmentPosition, 42091 segmentIndex: idx, 42092 relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd) 42093 }; 42094 } 42095 42096 attachmentDistance = getDistance(attachment.position, point); 42097 42098 if (!closestAttachment || closestAttachmentDistance > attachmentDistance) { 42099 closestAttachment = attachment; 42100 closestAttachmentDistance = attachmentDistance; 42101 } 42102 } 42103 42104 return closestAttachment; 42105 } 42106 42107 /** 42108 * Gets the intersection between a circle and a line segment. 42109 * 42110 * @param {Point} s1 segment start 42111 * @param {Point} s2 segment end 42112 * @param {Point} cc circle center 42113 * @param {number} cr circle radius 42114 * 42115 * @return {Array<Point>} intersections 42116 */ 42117 function getCircleSegmentIntersections(s1, s2, cc, cr) { 42118 42119 var baX = s2.x - s1.x; 42120 var baY = s2.y - s1.y; 42121 var caX = cc.x - s1.x; 42122 var caY = cc.y - s1.y; 42123 42124 var a = baX * baX + baY * baY; 42125 var bBy2 = baX * caX + baY * caY; 42126 var c = caX * caX + caY * caY - cr * cr; 42127 42128 var pBy2 = bBy2 / a; 42129 var q = c / a; 42130 42131 var disc = pBy2 * pBy2 - q; 42132 42133 // check against negative value to work around 42134 // negative, very close to zero results (-4e-15) 42135 // being produced in some environments 42136 if (disc < 0 && disc > -0.000001) { 42137 disc = 0; 42138 } 42139 42140 if (disc < 0) { 42141 return []; 42142 } 42143 42144 // if disc == 0 ... dealt with later 42145 var tmpSqrt = sqrt(disc); 42146 var abScalingFactor1 = -pBy2 + tmpSqrt; 42147 var abScalingFactor2 = -pBy2 - tmpSqrt; 42148 42149 var i1 = { 42150 x: s1.x - baX * abScalingFactor1, 42151 y: s1.y - baY * abScalingFactor1 42152 }; 42153 42154 if (disc === 0) { // abScalingFactor1 == abScalingFactor2 42155 return [ i1 ]; 42156 } 42157 42158 var i2 = { 42159 x: s1.x - baX * abScalingFactor2, 42160 y: s1.y - baY * abScalingFactor2 42161 }; 42162 42163 // return only points on line segment 42164 return [ i1, i2 ].filter(function(p) { 42165 return isPointInSegment(p, s1, s2); 42166 }); 42167 } 42168 42169 42170 function isPointInSegment(p, segmentStart, segmentEnd) { 42171 return ( 42172 fenced(p.x, segmentStart.x, segmentEnd.x) && 42173 fenced(p.y, segmentStart.y, segmentEnd.y) 42174 ); 42175 } 42176 42177 function fenced(n, rangeStart, rangeEnd) { 42178 42179 // use matching threshold to work around 42180 // precision errors in intersection computation 42181 42182 return ( 42183 n >= min$1(rangeStart, rangeEnd) - EQUAL_THRESHOLD && 42184 n <= max$3(rangeStart, rangeEnd) + EQUAL_THRESHOLD 42185 ); 42186 } 42187 42188 /** 42189 * Calculate mid of two points. 42190 * 42191 * @param {Point} p1 42192 * @param {Point} p2 42193 * 42194 * @return {Point} 42195 */ 42196 function mid$1(p1, p2) { 42197 42198 return { 42199 x: (p1.x + p2.x) / 2, 42200 y: (p1.y + p2.y) / 2 42201 }; 42202 } 42203 42204 var EQUAL_THRESHOLD = 0.1; 42205 42206 function pointsEqual(p1, p2) { 42207 42208 return ( 42209 abs$3(p1.x - p2.x) <= EQUAL_THRESHOLD && 42210 abs$3(p1.y - p2.y) <= EQUAL_THRESHOLD 42211 ); 42212 } 42213 42214 function findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) { 42215 42216 var index = attachment.segmentIndex; 42217 42218 var offset = newWaypoints.length - oldWaypoints.length; 42219 42220 // segmentMove happened 42221 if (hints.segmentMove) { 42222 42223 var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex, 42224 newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex; 42225 42226 // if label was on moved segment return new segment index 42227 if (index === oldSegmentStartIndex) { 42228 return newSegmentStartIndex; 42229 } 42230 42231 // label is after new segment index 42232 if (index >= newSegmentStartIndex) { 42233 return (index+offset < newSegmentStartIndex) ? newSegmentStartIndex : index+offset; 42234 } 42235 42236 // if label is before new segment index 42237 return index; 42238 } 42239 42240 // bendpointMove happened 42241 if (hints.bendpointMove) { 42242 42243 var insert = hints.bendpointMove.insert, 42244 bendpointIndex = hints.bendpointMove.bendpointIndex, 42245 newIndex; 42246 42247 // waypoints length didnt change 42248 if (offset === 0) { 42249 return index; 42250 } 42251 42252 // label behind new/removed bendpoint 42253 if (index >= bendpointIndex) { 42254 newIndex = insert ? index + 1 : index - 1; 42255 } 42256 42257 // label before new/removed bendpoint 42258 if (index < bendpointIndex) { 42259 42260 newIndex = index; 42261 42262 // decide label should take right or left segment 42263 if (insert && attachment.type !== 'bendpoint' && bendpointIndex-1 === index) { 42264 42265 var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex); 42266 42267 if (rel < attachment.relativeLocation) { 42268 newIndex++; 42269 } 42270 } 42271 } 42272 42273 return newIndex; 42274 } 42275 42276 // start/end changed 42277 if (offset === 0) { 42278 return index; 42279 } 42280 42281 if (hints.connectionStart) { 42282 return (index === 0) ? 0 : null; 42283 } 42284 42285 if (hints.connectionEnd) { 42286 return (index === oldWaypoints.length - 2) ? newWaypoints.length - 2 : null; 42287 } 42288 42289 // if nothing fits, return null 42290 return null; 42291 } 42292 42293 42294 /** 42295 * Calculate the required adjustment (move delta) for the given label 42296 * after the connection waypoints got updated. 42297 * 42298 * @param {djs.model.Label} label 42299 * @param {Array<Point>} newWaypoints 42300 * @param {Array<Point>} oldWaypoints 42301 * @param {Object} hints 42302 * 42303 * @return {Point} delta 42304 */ 42305 function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) { 42306 42307 var x = 0, 42308 y = 0; 42309 42310 var labelPosition = getLabelMid(label); 42311 42312 // get closest attachment 42313 var attachment = getAttachment(labelPosition, oldWaypoints), 42314 oldLabelLineIndex = attachment.segmentIndex, 42315 newLabelLineIndex = findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints); 42316 42317 if (newLabelLineIndex === null) { 42318 return { x: x, y: y }; 42319 } 42320 42321 // should never happen 42322 // TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored 42323 if (newLabelLineIndex < 0 || 42324 newLabelLineIndex > newWaypoints.length - 2) { 42325 return { x: x, y: y }; 42326 } 42327 42328 var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex), 42329 newLabelLine = getLine(newWaypoints, newLabelLineIndex), 42330 oldFoot = attachment.position; 42331 42332 var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot), 42333 angleDelta = getAngleDelta(oldLabelLine, newLabelLine); 42334 42335 // special rule if label on bendpoint 42336 if (attachment.type === 'bendpoint') { 42337 42338 var offset = newWaypoints.length - oldWaypoints.length, 42339 oldBendpointIndex = attachment.bendpointIndex, 42340 oldBendpoint = oldWaypoints[oldBendpointIndex]; 42341 42342 // bendpoint position hasn't changed, return same position 42343 if (newWaypoints.indexOf(oldBendpoint) !== -1) { 42344 return { x: x, y: y }; 42345 } 42346 42347 // new bendpoint and old bendpoint have same index, then just return the offset 42348 if (offset === 0) { 42349 var newBendpoint = newWaypoints[oldBendpointIndex]; 42350 42351 return { 42352 x: newBendpoint.x - attachment.position.x, 42353 y: newBendpoint.y - attachment.position.y 42354 }; 42355 } 42356 42357 // if bendpoints get removed 42358 if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) { 42359 relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex); 42360 } 42361 } 42362 42363 var newFoot = { 42364 x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x, 42365 y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y 42366 }; 42367 42368 // the rotated vector to label 42369 var newLabelVector = rotateVector({ 42370 x: labelPosition.x - oldFoot.x, 42371 y: labelPosition.y - oldFoot.y 42372 }, angleDelta); 42373 42374 // the new relative position 42375 x = newFoot.x + newLabelVector.x - labelPosition.x; 42376 y = newFoot.y + newLabelVector.y - labelPosition.y; 42377 42378 return roundPoint({ 42379 x: x, 42380 y: y 42381 }); 42382 } 42383 42384 42385 // HELPERS ////////////////////// 42386 42387 function relativePositionMidWaypoint(waypoints, idx) { 42388 42389 var distanceSegment1 = getDistancePointPoint(waypoints[idx-1], waypoints[idx]), 42390 distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx+1]); 42391 42392 var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2); 42393 42394 return relativePosition; 42395 } 42396 42397 function getLabelMid(label) { 42398 return { 42399 x: label.x + label.width / 2, 42400 y: label.y + label.height / 2 42401 }; 42402 } 42403 42404 function getAngleDelta(l1, l2) { 42405 var a1 = getAngle(l1), 42406 a2 = getAngle(l2); 42407 return a2 - a1; 42408 } 42409 42410 function getLine(waypoints, idx) { 42411 return [ waypoints[idx], waypoints[idx+1] ]; 42412 } 42413 42414 function getRelativeFootPosition(line, foot) { 42415 42416 var length = getDistancePointPoint(line[0], line[1]), 42417 lengthToFoot = getDistancePointPoint(line[0], foot); 42418 42419 return length === 0 ? 0 : lengthToFoot / length; 42420 } 42421 42422 /** 42423 * Calculates the absolute point relative to the new element's position 42424 * 42425 * @param {point} point [absolute] 42426 * @param {bounds} oldBounds 42427 * @param {bounds} newBounds 42428 * 42429 * @return {point} point [absolute] 42430 */ 42431 function getNewAttachPoint(point, oldBounds, newBounds) { 42432 var oldCenter = center(oldBounds), 42433 newCenter = center(newBounds), 42434 oldDelta = delta(point, oldCenter); 42435 42436 var newDelta = { 42437 x: oldDelta.x * (newBounds.width / oldBounds.width), 42438 y: oldDelta.y * (newBounds.height / oldBounds.height) 42439 }; 42440 42441 return roundPoint({ 42442 x: newCenter.x + newDelta.x, 42443 y: newCenter.y + newDelta.y 42444 }); 42445 } 42446 42447 42448 /** 42449 * Calculates the shape's delta relative to a new position 42450 * of a certain element's bounds 42451 * 42452 * @param {djs.model.Shape} point [absolute] 42453 * @param {bounds} oldBounds 42454 * @param {bounds} newBounds 42455 * 42456 * @return {delta} delta 42457 */ 42458 function getNewAttachShapeDelta(shape, oldBounds, newBounds) { 42459 var shapeCenter = center(shape), 42460 oldCenter = center(oldBounds), 42461 newCenter = center(newBounds), 42462 shapeDelta = delta(shape, shapeCenter), 42463 oldCenterDelta = delta(shapeCenter, oldCenter), 42464 stickyPositionDelta = getStickyPositionDelta(shapeCenter, oldBounds, newBounds); 42465 42466 if (stickyPositionDelta) { 42467 return stickyPositionDelta; 42468 } 42469 42470 var newCenterDelta = { 42471 x: oldCenterDelta.x * (newBounds.width / oldBounds.width), 42472 y: oldCenterDelta.y * (newBounds.height / oldBounds.height) 42473 }; 42474 42475 var newShapeCenter = { 42476 x: newCenter.x + newCenterDelta.x, 42477 y: newCenter.y + newCenterDelta.y 42478 }; 42479 42480 return roundPoint({ 42481 x: newShapeCenter.x + shapeDelta.x - shape.x, 42482 y: newShapeCenter.y + shapeDelta.y - shape.y 42483 }); 42484 } 42485 42486 function getStickyPositionDelta(oldShapeCenter, oldBounds, newBounds) { 42487 var oldTRBL = asTRBL(oldBounds), 42488 newTRBL = asTRBL(newBounds); 42489 42490 if (isMoved(oldTRBL, newTRBL)) { 42491 return null; 42492 } 42493 42494 var oldOrientation = getOrientation(oldBounds, oldShapeCenter), 42495 stickyPositionDelta, 42496 newShapeCenter, 42497 newOrientation; 42498 42499 if (oldOrientation === 'top') { 42500 stickyPositionDelta = { 42501 x: 0, 42502 y: newTRBL.bottom - oldTRBL.bottom 42503 }; 42504 } else if (oldOrientation === 'bottom') { 42505 stickyPositionDelta = { 42506 x: 0, 42507 y: newTRBL.top - oldTRBL.top 42508 }; 42509 } else if (oldOrientation === 'right') { 42510 stickyPositionDelta = { 42511 x: newTRBL.left - oldTRBL.left, 42512 y: 0 42513 }; 42514 } else if (oldOrientation === 'left') { 42515 stickyPositionDelta = { 42516 x: newTRBL.right - oldTRBL.right, 42517 y: 0 42518 }; 42519 } else { 42520 42521 // fallback to proportional movement for corner-placed attachments 42522 return null; 42523 } 42524 42525 newShapeCenter = { 42526 x: oldShapeCenter.x + stickyPositionDelta.x, 42527 y: oldShapeCenter.y + stickyPositionDelta.y 42528 }; 42529 42530 newOrientation = getOrientation(newBounds, newShapeCenter); 42531 42532 if (newOrientation !== oldOrientation) { 42533 42534 // fallback to proportional movement if orientation would otherwise change 42535 return null; 42536 } 42537 42538 return stickyPositionDelta; 42539 } 42540 42541 function isMoved(oldTRBL, newTRBL) { 42542 return isHorizontallyMoved(oldTRBL, newTRBL) || isVerticallyMoved(oldTRBL, newTRBL); 42543 } 42544 42545 function isHorizontallyMoved(oldTRBL, newTRBL) { 42546 return oldTRBL.right !== newTRBL.right && oldTRBL.left !== newTRBL.left; 42547 } 42548 42549 function isVerticallyMoved(oldTRBL, newTRBL) { 42550 return oldTRBL.top !== newTRBL.top && oldTRBL.bottom !== newTRBL.bottom; 42551 } 42552 42553 var DEFAULT_LABEL_DIMENSIONS = { 42554 width: 90, 42555 height: 20 42556 }; 42557 42558 var NAME_PROPERTY = 'name'; 42559 var TEXT_PROPERTY = 'text'; 42560 42561 /** 42562 * A component that makes sure that external labels are added 42563 * together with respective elements and properly updated (DI wise) 42564 * during move. 42565 * 42566 * @param {EventBus} eventBus 42567 * @param {Modeling} modeling 42568 * @param {BpmnFactory} bpmnFactory 42569 * @param {TextRenderer} textRenderer 42570 */ 42571 function LabelBehavior( 42572 eventBus, modeling, bpmnFactory, 42573 textRenderer) { 42574 42575 CommandInterceptor.call(this, eventBus); 42576 42577 // update label if name property was updated 42578 this.postExecute('element.updateProperties', function(e) { 42579 var context = e.context, 42580 element = context.element, 42581 properties = context.properties; 42582 42583 if (NAME_PROPERTY in properties) { 42584 modeling.updateLabel(element, properties[NAME_PROPERTY]); 42585 } 42586 42587 if (TEXT_PROPERTY in properties 42588 && is$1(element, 'bpmn:TextAnnotation')) { 42589 42590 var newBounds = textRenderer.getTextAnnotationBounds( 42591 { 42592 x: element.x, 42593 y: element.y, 42594 width: element.width, 42595 height: element.height 42596 }, 42597 properties[TEXT_PROPERTY] || '' 42598 ); 42599 42600 modeling.updateLabel(element, properties.text, newBounds); 42601 } 42602 }); 42603 42604 // create label shape after shape/connection was created 42605 this.postExecute([ 'shape.create', 'connection.create' ], function(e) { 42606 var context = e.context, 42607 hints = context.hints || {}; 42608 42609 if (hints.createElementsBehavior === false) { 42610 return; 42611 } 42612 42613 var element = context.shape || context.connection, 42614 businessObject = element.businessObject; 42615 42616 if (isLabel$6(element) || !isLabelExternal(element)) { 42617 return; 42618 } 42619 42620 // only create label if attribute available 42621 if (!getLabel(element)) { 42622 return; 42623 } 42624 42625 var labelCenter = getExternalLabelMid(element); 42626 42627 // we don't care about x and y 42628 var labelDimensions = textRenderer.getExternalLabelBounds( 42629 DEFAULT_LABEL_DIMENSIONS, 42630 getLabel(element) 42631 ); 42632 42633 modeling.createLabel(element, labelCenter, { 42634 id: businessObject.id + '_label', 42635 businessObject: businessObject, 42636 width: labelDimensions.width, 42637 height: labelDimensions.height 42638 }); 42639 }); 42640 42641 // update label after label shape was deleted 42642 this.postExecute('shape.delete', function(event) { 42643 var context = event.context, 42644 labelTarget = context.labelTarget, 42645 hints = context.hints || {}; 42646 42647 // check if label 42648 if (labelTarget && hints.unsetLabel !== false) { 42649 modeling.updateLabel(labelTarget, null, null, { removeShape: false }); 42650 } 42651 }); 42652 42653 // update di information on label creation 42654 this.postExecute([ 'label.create' ], function(event) { 42655 42656 var context = event.context, 42657 element = context.shape, 42658 businessObject, 42659 di; 42660 42661 // we want to trigger on real labels only 42662 if (!element.labelTarget) { 42663 return; 42664 } 42665 42666 // we want to trigger on BPMN elements only 42667 if (!is$1(element.labelTarget || element, 'bpmn:BaseElement')) { 42668 return; 42669 } 42670 42671 businessObject = element.businessObject, 42672 di = businessObject.di; 42673 42674 42675 if (!di.label) { 42676 di.label = bpmnFactory.create('bpmndi:BPMNLabel', { 42677 bounds: bpmnFactory.create('dc:Bounds') 42678 }); 42679 } 42680 42681 assign(di.label.bounds, { 42682 x: element.x, 42683 y: element.y, 42684 width: element.width, 42685 height: element.height 42686 }); 42687 }); 42688 42689 function getVisibleLabelAdjustment(event) { 42690 42691 var context = event.context, 42692 connection = context.connection, 42693 label = connection.label, 42694 hints = assign({}, context.hints), 42695 newWaypoints = context.newWaypoints || connection.waypoints, 42696 oldWaypoints = context.oldWaypoints; 42697 42698 42699 if (typeof hints.startChanged === 'undefined') { 42700 hints.startChanged = !!hints.connectionStart; 42701 } 42702 42703 if (typeof hints.endChanged === 'undefined') { 42704 hints.endChanged = !!hints.connectionEnd; 42705 } 42706 42707 return getLabelAdjustment(label, newWaypoints, oldWaypoints, hints); 42708 } 42709 42710 this.postExecute([ 42711 'connection.layout', 42712 'connection.updateWaypoints' 42713 ], function(event) { 42714 var context = event.context, 42715 hints = context.hints || {}; 42716 42717 if (hints.labelBehavior === false) { 42718 return; 42719 } 42720 42721 var connection = context.connection, 42722 label = connection.label, 42723 labelAdjustment; 42724 42725 // handle missing label as well as the case 42726 // that the label parent does not exist (yet), 42727 // because it is being pasted / created via multi element create 42728 // 42729 // Cf. https://github.com/bpmn-io/bpmn-js/pull/1227 42730 if (!label || !label.parent) { 42731 return; 42732 } 42733 42734 labelAdjustment = getVisibleLabelAdjustment(event); 42735 42736 modeling.moveShape(label, labelAdjustment); 42737 }); 42738 42739 42740 // keep label position on shape replace 42741 this.postExecute([ 'shape.replace' ], function(event) { 42742 var context = event.context, 42743 newShape = context.newShape, 42744 oldShape = context.oldShape; 42745 42746 var businessObject = getBusinessObject(newShape); 42747 42748 if (businessObject 42749 && isLabelExternal(businessObject) 42750 && oldShape.label 42751 && newShape.label) { 42752 newShape.label.x = oldShape.label.x; 42753 newShape.label.y = oldShape.label.y; 42754 } 42755 }); 42756 42757 42758 // move external label after resizing 42759 this.postExecute('shape.resize', function(event) { 42760 42761 var context = event.context, 42762 shape = context.shape, 42763 newBounds = context.newBounds, 42764 oldBounds = context.oldBounds; 42765 42766 if (hasExternalLabel(shape)) { 42767 42768 var label = shape.label, 42769 labelMid = getMid(label), 42770 edges = asEdges(oldBounds); 42771 42772 // get nearest border point to label as reference point 42773 var referencePoint = getReferencePoint(labelMid, edges); 42774 42775 var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds); 42776 42777 modeling.moveShape(label, delta); 42778 42779 } 42780 42781 }); 42782 42783 } 42784 42785 inherits$1(LabelBehavior, CommandInterceptor); 42786 42787 LabelBehavior.$inject = [ 42788 'eventBus', 42789 'modeling', 42790 'bpmnFactory', 42791 'textRenderer' 42792 ]; 42793 42794 // helpers ////////////////////// 42795 42796 /** 42797 * Calculates a reference point delta relative to a new position 42798 * of a certain element's bounds 42799 * 42800 * @param {Point} point 42801 * @param {Bounds} oldBounds 42802 * @param {Bounds} newBounds 42803 * 42804 * @return {Delta} delta 42805 */ 42806 function getReferencePointDelta(referencePoint, oldBounds, newBounds) { 42807 42808 var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds); 42809 42810 return roundPoint(delta(newReferencePoint, referencePoint)); 42811 } 42812 42813 /** 42814 * Generates the nearest point (reference point) for a given point 42815 * onto given set of lines 42816 * 42817 * @param {Array<Point, Point>} lines 42818 * @param {Point} point 42819 * 42820 * @param {Point} 42821 */ 42822 function getReferencePoint(point, lines) { 42823 42824 if (!lines.length) { 42825 return; 42826 } 42827 42828 var nearestLine = getNearestLine(point, lines); 42829 42830 return perpendicularFoot(point, nearestLine); 42831 } 42832 42833 /** 42834 * Convert the given bounds to a lines array containing all edges 42835 * 42836 * @param {Bounds|Point} bounds 42837 * 42838 * @return Array<Point> 42839 */ 42840 function asEdges(bounds) { 42841 return [ 42842 [ // top 42843 { 42844 x: bounds.x, 42845 y: bounds.y 42846 }, 42847 { 42848 x: bounds.x + (bounds.width || 0), 42849 y: bounds.y 42850 } 42851 ], 42852 [ // right 42853 { 42854 x: bounds.x + (bounds.width || 0), 42855 y: bounds.y 42856 }, 42857 { 42858 x: bounds.x + (bounds.width || 0), 42859 y: bounds.y + (bounds.height || 0) 42860 } 42861 ], 42862 [ // bottom 42863 { 42864 x: bounds.x, 42865 y: bounds.y + (bounds.height || 0) 42866 }, 42867 { 42868 x: bounds.x + (bounds.width || 0), 42869 y: bounds.y + (bounds.height || 0) 42870 } 42871 ], 42872 [ // left 42873 { 42874 x: bounds.x, 42875 y: bounds.y 42876 }, 42877 { 42878 x: bounds.x, 42879 y: bounds.y + (bounds.height || 0) 42880 } 42881 ] 42882 ]; 42883 } 42884 42885 /** 42886 * Returns the nearest line for a given point by distance 42887 * @param {Point} point 42888 * @param Array<Point> lines 42889 * 42890 * @return Array<Point> 42891 */ 42892 function getNearestLine(point, lines) { 42893 42894 var distances = lines.map(function(l) { 42895 return { 42896 line: l, 42897 distance: getDistancePointLine(point, l) 42898 }; 42899 }); 42900 42901 var sorted = sortBy(distances, 'distance'); 42902 42903 return sorted[0].line; 42904 } 42905 42906 function getResizedSourceAnchor(connection, shape, oldBounds) { 42907 42908 var waypoints = safeGetWaypoints(connection), 42909 waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape), 42910 oldAnchor = waypoints[0]; 42911 42912 // new anchor is the last waypoint enclosed be resized source 42913 if (waypointsInsideNewBounds.length) { 42914 return waypointsInsideNewBounds[ waypointsInsideNewBounds.length - 1 ]; 42915 } 42916 42917 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape); 42918 } 42919 42920 42921 function getResizedTargetAnchor(connection, shape, oldBounds) { 42922 42923 var waypoints = safeGetWaypoints(connection), 42924 waypointsInsideNewBounds = getWaypointsInsideBounds(waypoints, shape), 42925 oldAnchor = waypoints[waypoints.length - 1]; 42926 42927 // new anchor is the first waypoint enclosed be resized target 42928 if (waypointsInsideNewBounds.length) { 42929 return waypointsInsideNewBounds[ 0 ]; 42930 } 42931 42932 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, shape); 42933 } 42934 42935 42936 function getMovedSourceAnchor(connection, source, moveDelta) { 42937 42938 var waypoints = safeGetWaypoints(connection), 42939 oldBounds = subtract(source, moveDelta), 42940 oldAnchor = waypoints[ 0 ]; 42941 42942 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, source); 42943 } 42944 42945 42946 function getMovedTargetAnchor(connection, target, moveDelta) { 42947 42948 var waypoints = safeGetWaypoints(connection), 42949 oldBounds = subtract(target, moveDelta), 42950 oldAnchor = waypoints[ waypoints.length - 1 ]; 42951 42952 return getNewAttachPoint(oldAnchor.original || oldAnchor, oldBounds, target); 42953 } 42954 42955 42956 // helpers ////////////////////// 42957 42958 function subtract(bounds, delta) { 42959 return { 42960 x: bounds.x - delta.x, 42961 y: bounds.y - delta.y, 42962 width: bounds.width, 42963 height: bounds.height 42964 }; 42965 } 42966 42967 42968 /** 42969 * Return waypoints of given connection; throw if non exists (should not happen!!). 42970 * 42971 * @param {Connection} connection 42972 * 42973 * @return {Array<Point>} 42974 */ 42975 function safeGetWaypoints(connection) { 42976 42977 var waypoints = connection.waypoints; 42978 42979 if (!waypoints.length) { 42980 throw new Error('connection#' + connection.id + ': no waypoints'); 42981 } 42982 42983 return waypoints; 42984 } 42985 42986 function getWaypointsInsideBounds(waypoints, bounds) { 42987 var originalWaypoints = map$1(waypoints, getOriginal); 42988 42989 return filter(originalWaypoints, function(waypoint) { 42990 return isInsideBounds(waypoint, bounds); 42991 }); 42992 } 42993 42994 /** 42995 * Checks if point is inside bounds, incl. edges. 42996 * 42997 * @param {Point} point 42998 * @param {Bounds} bounds 42999 */ 43000 function isInsideBounds(point, bounds) { 43001 return getOrientation(bounds, point, 1) === 'intersect'; 43002 } 43003 43004 function getOriginal(point) { 43005 return point.original || point; 43006 } 43007 43008 /** 43009 * BPMN-specific message flow behavior. 43010 */ 43011 function MessageFlowBehavior(eventBus, modeling) { 43012 43013 CommandInterceptor.call(this, eventBus); 43014 43015 this.postExecute('shape.replace', function(context) { 43016 var oldShape = context.oldShape, 43017 newShape = context.newShape; 43018 43019 if (!isParticipantCollapse(oldShape, newShape)) { 43020 return; 43021 } 43022 43023 var messageFlows = getMessageFlows(oldShape); 43024 43025 messageFlows.incoming.forEach(function(incoming) { 43026 var anchor = getResizedTargetAnchor(incoming, newShape, oldShape); 43027 43028 modeling.reconnectEnd(incoming, newShape, anchor); 43029 }); 43030 43031 messageFlows.outgoing.forEach(function(outgoing) { 43032 var anchor = getResizedSourceAnchor(outgoing, newShape, oldShape); 43033 43034 modeling.reconnectStart(outgoing, newShape, anchor); 43035 }); 43036 }, true); 43037 43038 } 43039 43040 MessageFlowBehavior.$inject = [ 'eventBus', 'modeling' ]; 43041 43042 inherits$1(MessageFlowBehavior, CommandInterceptor); 43043 43044 // helpers ////////// 43045 43046 function isParticipantCollapse(oldShape, newShape) { 43047 return is$1(oldShape, 'bpmn:Participant') 43048 && isExpanded(oldShape) 43049 && is$1(newShape, 'bpmn:Participant') 43050 && !isExpanded(newShape); 43051 } 43052 43053 function getMessageFlows(parent) { 43054 var elements = selfAndAllChildren([ parent ], false); 43055 43056 var incoming = [], 43057 outgoing = []; 43058 43059 elements.forEach(function(element) { 43060 if (element === parent) { 43061 return; 43062 } 43063 43064 element.incoming.forEach(function(connection) { 43065 if (is$1(connection, 'bpmn:MessageFlow')) { 43066 incoming.push(connection); 43067 } 43068 }); 43069 43070 element.outgoing.forEach(function(connection) { 43071 if (is$1(connection, 'bpmn:MessageFlow')) { 43072 outgoing.push(connection); 43073 } 43074 }); 43075 }, []); 43076 43077 return { 43078 incoming: incoming, 43079 outgoing: outgoing 43080 }; 43081 } 43082 43083 var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants'; 43084 43085 function ModelingFeedback(eventBus, tooltips, translate) { 43086 43087 function showError(position, message, timeout) { 43088 tooltips.add({ 43089 position: { 43090 x: position.x + 5, 43091 y: position.y + 5 43092 }, 43093 type: 'error', 43094 timeout: timeout || 2000, 43095 html: '<div>' + message + '</div>' 43096 }); 43097 } 43098 43099 eventBus.on([ 'shape.move.rejected', 'create.rejected' ], function(event) { 43100 var context = event.context, 43101 shape = context.shape, 43102 target = context.target; 43103 43104 if (is$1(target, 'bpmn:Collaboration') && is$1(shape, 'bpmn:FlowNode')) { 43105 showError(event, translate(COLLAB_ERR_MSG)); 43106 } 43107 }); 43108 43109 } 43110 43111 ModelingFeedback.$inject = [ 43112 'eventBus', 43113 'tooltips', 43114 'translate' 43115 ]; 43116 43117 function ReplaceConnectionBehavior(eventBus, modeling, bpmnRules, injector) { 43118 43119 CommandInterceptor.call(this, eventBus); 43120 43121 var dragging = injector.get('dragging', false); 43122 43123 function fixConnection(connection) { 43124 43125 var source = connection.source, 43126 target = connection.target, 43127 parent = connection.parent; 43128 43129 // do not do anything if connection 43130 // is already deleted (may happen due to other 43131 // behaviors plugged-in before) 43132 if (!parent) { 43133 return; 43134 } 43135 43136 var replacementType, 43137 remove; 43138 43139 /** 43140 * Check if incoming or outgoing connections 43141 * can stay or could be substituted with an 43142 * appropriate replacement. 43143 * 43144 * This holds true for SequenceFlow <> MessageFlow. 43145 */ 43146 43147 if (is$1(connection, 'bpmn:SequenceFlow')) { 43148 if (!bpmnRules.canConnectSequenceFlow(source, target)) { 43149 remove = true; 43150 } 43151 43152 if (bpmnRules.canConnectMessageFlow(source, target)) { 43153 replacementType = 'bpmn:MessageFlow'; 43154 } 43155 } 43156 43157 // transform message flows into sequence flows, if possible 43158 43159 if (is$1(connection, 'bpmn:MessageFlow')) { 43160 43161 if (!bpmnRules.canConnectMessageFlow(source, target)) { 43162 remove = true; 43163 } 43164 43165 if (bpmnRules.canConnectSequenceFlow(source, target)) { 43166 replacementType = 'bpmn:SequenceFlow'; 43167 } 43168 } 43169 43170 if (is$1(connection, 'bpmn:Association') && !bpmnRules.canConnectAssociation(source, target)) { 43171 remove = true; 43172 } 43173 43174 43175 // remove invalid connection, 43176 // unless it has been removed already 43177 if (remove) { 43178 modeling.removeConnection(connection); 43179 } 43180 43181 // replace SequenceFlow <> MessageFlow 43182 43183 if (replacementType) { 43184 modeling.connect(source, target, { 43185 type: replacementType, 43186 waypoints: connection.waypoints.slice() 43187 }); 43188 } 43189 } 43190 43191 function replaceReconnectedConnection(event) { 43192 43193 var context = event.context, 43194 connection = context.connection, 43195 source = context.newSource || connection.source, 43196 target = context.newTarget || connection.target, 43197 allowed, 43198 replacement; 43199 43200 allowed = bpmnRules.canConnect(source, target); 43201 43202 if (!allowed || allowed.type === connection.type) { 43203 return; 43204 } 43205 43206 replacement = modeling.connect(source, target, { 43207 type: allowed.type, 43208 waypoints: connection.waypoints.slice() 43209 }); 43210 43211 // remove old connection 43212 modeling.removeConnection(connection); 43213 43214 // replace connection in context to reconnect end/start 43215 context.connection = replacement; 43216 43217 if (dragging) { 43218 cleanDraggingSelection(connection, replacement); 43219 } 43220 } 43221 43222 // monkey-patch selection saved in dragging in order to re-select it when operation is finished 43223 function cleanDraggingSelection(oldConnection, newConnection) { 43224 var context = dragging.context(), 43225 previousSelection = context && context.payload.previousSelection, 43226 index; 43227 43228 // do nothing if not dragging or no selection was present 43229 if (!previousSelection || !previousSelection.length) { 43230 return; 43231 } 43232 43233 index = previousSelection.indexOf(oldConnection); 43234 43235 if (index === -1) { 43236 return; 43237 } 43238 43239 previousSelection.splice(index, 1, newConnection); 43240 } 43241 43242 // lifecycle hooks 43243 43244 this.postExecuted('elements.move', function(context) { 43245 43246 var closure = context.closure, 43247 allConnections = closure.allConnections; 43248 43249 forEach(allConnections, fixConnection); 43250 }, true); 43251 43252 this.preExecute('connection.reconnect', replaceReconnectedConnection); 43253 43254 this.postExecuted('element.updateProperties', function(event) { 43255 var context = event.context, 43256 properties = context.properties, 43257 element = context.element, 43258 businessObject = element.businessObject, 43259 connection; 43260 43261 // remove condition on change to default 43262 if (properties.default) { 43263 connection = find( 43264 element.outgoing, 43265 matchPattern({ id: element.businessObject.default.id }) 43266 ); 43267 43268 if (connection) { 43269 modeling.updateProperties(connection, { conditionExpression: undefined }); 43270 } 43271 } 43272 43273 // remove default from source on change to conditional 43274 if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) { 43275 modeling.updateProperties(element.source, { default: undefined }); 43276 } 43277 }); 43278 } 43279 43280 inherits$1(ReplaceConnectionBehavior, CommandInterceptor); 43281 43282 ReplaceConnectionBehavior.$inject = [ 43283 'eventBus', 43284 'modeling', 43285 'bpmnRules', 43286 'injector' 43287 ]; 43288 43289 /** 43290 * BPMN specific remove behavior 43291 */ 43292 function RemoveParticipantBehavior(eventBus, modeling) { 43293 43294 CommandInterceptor.call(this, eventBus); 43295 43296 43297 /** 43298 * morph collaboration diagram into process diagram 43299 * after the last participant has been removed 43300 */ 43301 43302 this.preExecute('shape.delete', function(context) { 43303 43304 var shape = context.shape, 43305 parent = shape.parent; 43306 43307 // activate the behavior if the shape to be removed 43308 // is a participant 43309 if (is$1(shape, 'bpmn:Participant')) { 43310 context.collaborationRoot = parent; 43311 } 43312 }, true); 43313 43314 this.postExecute('shape.delete', function(context) { 43315 43316 var collaborationRoot = context.collaborationRoot; 43317 43318 if (collaborationRoot && !collaborationRoot.businessObject.participants.length) { 43319 43320 // replace empty collaboration with process diagram 43321 modeling.makeProcess(); 43322 } 43323 }, true); 43324 43325 } 43326 43327 RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ]; 43328 43329 inherits$1(RemoveParticipantBehavior, CommandInterceptor); 43330 43331 /** 43332 * BPMN-specific replace behavior. 43333 */ 43334 function ReplaceElementBehaviour( 43335 bpmnReplace, 43336 bpmnRules, 43337 elementRegistry, 43338 injector, 43339 modeling, 43340 selection 43341 ) { 43342 injector.invoke(CommandInterceptor, this); 43343 43344 this._bpmnReplace = bpmnReplace; 43345 this._elementRegistry = elementRegistry; 43346 this._selection = selection; 43347 43348 // replace elements on create, e.g. during copy-paste 43349 this.postExecuted([ 'elements.create' ], 500, function(event) { 43350 var context = event.context, 43351 target = context.parent, 43352 elements = context.elements; 43353 43354 var canReplace = bpmnRules.canReplace(elements, target); 43355 43356 if (canReplace) { 43357 this.replaceElements(elements, canReplace.replacements); 43358 } 43359 }, this); 43360 43361 // replace elements on move 43362 this.postExecuted([ 'elements.move' ], 500, function(event) { 43363 var context = event.context, 43364 target = context.newParent, 43365 newHost = context.newHost, 43366 elements = []; 43367 43368 forEach(context.closure.topLevel, function(topLevelElements) { 43369 if (isEventSubProcess(topLevelElements)) { 43370 elements = elements.concat(topLevelElements.children); 43371 } else { 43372 elements = elements.concat(topLevelElements); 43373 } 43374 }); 43375 43376 // set target to host if attaching 43377 if (elements.length === 1 && newHost) { 43378 target = newHost; 43379 } 43380 43381 var canReplace = bpmnRules.canReplace(elements, target); 43382 43383 if (canReplace) { 43384 this.replaceElements(elements, canReplace.replacements, newHost); 43385 } 43386 }, this); 43387 43388 // update attachments on host replace 43389 this.postExecute([ 'shape.replace' ], 1500, function(e) { 43390 var context = e.context, 43391 oldShape = context.oldShape, 43392 newShape = context.newShape, 43393 attachers = oldShape.attachers, 43394 canReplace; 43395 43396 if (attachers && attachers.length) { 43397 canReplace = bpmnRules.canReplace(attachers, newShape); 43398 43399 this.replaceElements(attachers, canReplace.replacements); 43400 } 43401 43402 }, this); 43403 43404 // keep ID on shape replace 43405 this.postExecuted([ 'shape.replace' ], 1500, function(e) { 43406 var context = e.context, 43407 oldShape = context.oldShape, 43408 newShape = context.newShape; 43409 43410 modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject); 43411 modeling.updateProperties(newShape, { id: oldShape.id }); 43412 }); 43413 } 43414 43415 inherits$1(ReplaceElementBehaviour, CommandInterceptor); 43416 43417 ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements) { 43418 var elementRegistry = this._elementRegistry, 43419 bpmnReplace = this._bpmnReplace, 43420 selection = this._selection; 43421 43422 forEach(newElements, function(replacement) { 43423 var newElement = { 43424 type: replacement.newElementType 43425 }; 43426 43427 var oldElement = elementRegistry.get(replacement.oldElementId); 43428 43429 var idx = elements.indexOf(oldElement); 43430 43431 elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false }); 43432 }); 43433 43434 if (newElements) { 43435 selection.select(elements); 43436 } 43437 }; 43438 43439 ReplaceElementBehaviour.$inject = [ 43440 'bpmnReplace', 43441 'bpmnRules', 43442 'elementRegistry', 43443 'injector', 43444 'modeling', 43445 'selection' 43446 ]; 43447 43448 var HIGH_PRIORITY$8 = 1500; 43449 43450 var LANE_MIN_DIMENSIONS = { width: 300, height: 60 }; 43451 43452 var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 }; 43453 43454 var SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 }; 43455 43456 var TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 }; 43457 43458 /** 43459 * Set minimum bounds/resize constraints on resize. 43460 * 43461 * @param {EventBus} eventBus 43462 */ 43463 function ResizeBehavior(eventBus) { 43464 eventBus.on('resize.start', HIGH_PRIORITY$8, function(event) { 43465 var context = event.context, 43466 shape = context.shape, 43467 direction = context.direction, 43468 balanced = context.balanced; 43469 43470 if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) { 43471 context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced); 43472 } 43473 43474 if (is$1(shape, 'bpmn:Participant')) { 43475 context.minDimensions = PARTICIPANT_MIN_DIMENSIONS; 43476 } 43477 43478 if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) { 43479 context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS; 43480 } 43481 43482 if (is$1(shape, 'bpmn:TextAnnotation')) { 43483 context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS; 43484 } 43485 }); 43486 } 43487 43488 ResizeBehavior.$inject = [ 'eventBus' ]; 43489 43490 43491 var abs$2 = Math.abs, 43492 min = Math.min, 43493 max$2 = Math.max; 43494 43495 43496 function addToTrbl(trbl, attr, value, choice) { 43497 var current = trbl[attr]; 43498 43499 // make sure to set the value if it does not exist 43500 // or apply the correct value by comparing against 43501 // choice(value, currentValue) 43502 trbl[attr] = current === undefined ? value : choice(value, current); 43503 } 43504 43505 function addMin(trbl, attr, value) { 43506 return addToTrbl(trbl, attr, value, min); 43507 } 43508 43509 function addMax(trbl, attr, value) { 43510 return addToTrbl(trbl, attr, value, max$2); 43511 } 43512 43513 var LANE_RIGHT_PADDING = 20, 43514 LANE_LEFT_PADDING = 50, 43515 LANE_TOP_PADDING = 20, 43516 LANE_BOTTOM_PADDING = 20; 43517 43518 function getParticipantResizeConstraints(laneShape, resizeDirection, balanced) { 43519 var lanesRoot = getLanesRoot(laneShape); 43520 43521 var isFirst = true, 43522 isLast = true; 43523 43524 // max top/bottom size for lanes 43525 var allLanes = collectLanes(lanesRoot, [ lanesRoot ]); 43526 43527 var laneTrbl = asTRBL(laneShape); 43528 43529 var maxTrbl = {}, 43530 minTrbl = {}; 43531 43532 if (/e/.test(resizeDirection)) { 43533 minTrbl.right = laneTrbl.left + LANE_MIN_DIMENSIONS.width; 43534 } else 43535 if (/w/.test(resizeDirection)) { 43536 minTrbl.left = laneTrbl.right - LANE_MIN_DIMENSIONS.width; 43537 } 43538 43539 allLanes.forEach(function(other) { 43540 43541 var otherTrbl = asTRBL(other); 43542 43543 if (/n/.test(resizeDirection)) { 43544 43545 if (otherTrbl.top < (laneTrbl.top - 10)) { 43546 isFirst = false; 43547 } 43548 43549 // max top size (based on next element) 43550 if (balanced && abs$2(laneTrbl.top - otherTrbl.bottom) < 10) { 43551 addMax(maxTrbl, 'top', otherTrbl.top + LANE_MIN_DIMENSIONS.height); 43552 } 43553 43554 // min top size (based on self or nested element) 43555 if (abs$2(laneTrbl.top - otherTrbl.top) < 5) { 43556 addMin(minTrbl, 'top', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height); 43557 } 43558 } 43559 43560 if (/s/.test(resizeDirection)) { 43561 43562 if (otherTrbl.bottom > (laneTrbl.bottom + 10)) { 43563 isLast = false; 43564 } 43565 43566 // max bottom size (based on previous element) 43567 if (balanced && abs$2(laneTrbl.bottom - otherTrbl.top) < 10) { 43568 addMin(maxTrbl, 'bottom', otherTrbl.bottom - LANE_MIN_DIMENSIONS.height); 43569 } 43570 43571 // min bottom size (based on self or nested element) 43572 if (abs$2(laneTrbl.bottom - otherTrbl.bottom) < 5) { 43573 addMax(minTrbl, 'bottom', otherTrbl.top + LANE_MIN_DIMENSIONS.height); 43574 } 43575 } 43576 }); 43577 43578 // max top/bottom/left/right size based on flow nodes 43579 var flowElements = lanesRoot.children.filter(function(s) { 43580 return !s.hidden && !s.waypoints && (is$1(s, 'bpmn:FlowElement') || is$1(s, 'bpmn:Artifact')); 43581 }); 43582 43583 flowElements.forEach(function(flowElement) { 43584 43585 var flowElementTrbl = asTRBL(flowElement); 43586 43587 if (isFirst && /n/.test(resizeDirection)) { 43588 addMin(minTrbl, 'top', flowElementTrbl.top - LANE_TOP_PADDING); 43589 } 43590 43591 if (/e/.test(resizeDirection)) { 43592 addMax(minTrbl, 'right', flowElementTrbl.right + LANE_RIGHT_PADDING); 43593 } 43594 43595 if (isLast && /s/.test(resizeDirection)) { 43596 addMax(minTrbl, 'bottom', flowElementTrbl.bottom + LANE_BOTTOM_PADDING); 43597 } 43598 43599 if (/w/.test(resizeDirection)) { 43600 addMin(minTrbl, 'left', flowElementTrbl.left - LANE_LEFT_PADDING); 43601 } 43602 }); 43603 43604 return { 43605 min: minTrbl, 43606 max: maxTrbl 43607 }; 43608 } 43609 43610 var SLIGHTLY_HIGHER_PRIORITY = 1001; 43611 43612 43613 /** 43614 * Invoke {@link Modeling#resizeLane} instead of 43615 * {@link Modeling#resizeShape} when resizing a Lane 43616 * or Participant shape. 43617 */ 43618 function ResizeLaneBehavior(eventBus, modeling) { 43619 43620 eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) { 43621 var context = event.context, 43622 shape = context.shape; 43623 43624 if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) { 43625 43626 // should we resize the opposite lane(s) in 43627 // order to compensate for the resize operation? 43628 context.balanced = !hasPrimaryModifier(event); 43629 } 43630 }); 43631 43632 /** 43633 * Intercept resize end and call resize lane function instead. 43634 */ 43635 eventBus.on('resize.end', SLIGHTLY_HIGHER_PRIORITY, function(event) { 43636 var context = event.context, 43637 shape = context.shape, 43638 canExecute = context.canExecute, 43639 newBounds = context.newBounds; 43640 43641 if (is$1(shape, 'bpmn:Lane') || is$1(shape, 'bpmn:Participant')) { 43642 43643 if (canExecute) { 43644 43645 // ensure we have actual pixel values for new bounds 43646 // (important when zoom level was > 1 during move) 43647 newBounds = roundBounds(newBounds); 43648 43649 // perform the actual resize 43650 modeling.resizeLane(shape, newBounds, context.balanced); 43651 } 43652 43653 // stop propagation 43654 return false; 43655 } 43656 }); 43657 } 43658 43659 ResizeLaneBehavior.$inject = [ 43660 'eventBus', 43661 'modeling' 43662 ]; 43663 43664 function RemoveElementBehavior(eventBus, bpmnRules, modeling) { 43665 43666 CommandInterceptor.call(this, eventBus); 43667 43668 /** 43669 * Combine sequence flows when deleting an element 43670 * if there is one incoming and one outgoing 43671 * sequence flow 43672 */ 43673 this.preExecute('shape.delete', function(e) { 43674 43675 var shape = e.context.shape; 43676 43677 // only handle [a] -> [shape] -> [b] patterns 43678 if (shape.incoming.length !== 1 || shape.outgoing.length !== 1) { 43679 return; 43680 } 43681 43682 var inConnection = shape.incoming[0], 43683 outConnection = shape.outgoing[0]; 43684 43685 // only handle sequence flows 43686 if (!is$1(inConnection, 'bpmn:SequenceFlow') || !is$1(outConnection, 'bpmn:SequenceFlow')) { 43687 return; 43688 } 43689 43690 if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) { 43691 43692 // compute new, combined waypoints 43693 var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints); 43694 43695 modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints); 43696 } 43697 }); 43698 43699 } 43700 43701 inherits$1(RemoveElementBehavior, CommandInterceptor); 43702 43703 RemoveElementBehavior.$inject = [ 43704 'eventBus', 43705 'bpmnRules', 43706 'modeling' 43707 ]; 43708 43709 43710 // helpers ////////////////////// 43711 43712 function getDocking$1(point) { 43713 return point.original || point; 43714 } 43715 43716 43717 function getNewWaypoints(inWaypoints, outWaypoints) { 43718 43719 var intersection = lineIntersect( 43720 getDocking$1(inWaypoints[inWaypoints.length - 2]), 43721 getDocking$1(inWaypoints[inWaypoints.length - 1]), 43722 getDocking$1(outWaypoints[1]), 43723 getDocking$1(outWaypoints[0])); 43724 43725 if (intersection) { 43726 return [].concat( 43727 inWaypoints.slice(0, inWaypoints.length - 1), 43728 [ intersection ], 43729 outWaypoints.slice(1)); 43730 } else { 43731 return [ 43732 getDocking$1(inWaypoints[0]), 43733 getDocking$1(outWaypoints[outWaypoints.length - 1]) 43734 ]; 43735 } 43736 } 43737 43738 var max$1 = Math.max; 43739 43740 43741 function SpaceToolBehavior(eventBus) { 43742 eventBus.on('spaceTool.getMinDimensions', function(context) { 43743 var shapes = context.shapes, 43744 axis = context.axis, 43745 start = context.start, 43746 minDimensions = {}; 43747 43748 forEach(shapes, function(shape) { 43749 var id = shape.id; 43750 43751 if (is$1(shape, 'bpmn:Participant')) { 43752 43753 if (isHorizontal$1(axis)) { 43754 minDimensions[ id ] = PARTICIPANT_MIN_DIMENSIONS; 43755 } else { 43756 minDimensions[ id ] = { 43757 width: PARTICIPANT_MIN_DIMENSIONS.width, 43758 height: getParticipantMinHeight(shape, start) 43759 }; 43760 } 43761 43762 } 43763 43764 if (is$1(shape, 'bpmn:SubProcess') && isExpanded(shape)) { 43765 minDimensions[ id ] = SUB_PROCESS_MIN_DIMENSIONS; 43766 } 43767 43768 if (is$1(shape, 'bpmn:TextAnnotation')) { 43769 minDimensions[ id ] = TEXT_ANNOTATION_MIN_DIMENSIONS; 43770 } 43771 }); 43772 43773 return minDimensions; 43774 }); 43775 } 43776 43777 SpaceToolBehavior.$inject = [ 'eventBus' ]; 43778 43779 43780 // helpers ////////// 43781 function isHorizontal$1(axis) { 43782 return axis === 'x'; 43783 } 43784 43785 /** 43786 * Get minimum height for participant taking lanes into account. 43787 * 43788 * @param {<djs.model.Shape>} participant 43789 * @param {number} start 43790 * 43791 * @returns {Object} 43792 */ 43793 function getParticipantMinHeight(participant, start) { 43794 var lanesMinHeight; 43795 43796 if (!hasChildLanes(participant)) { 43797 return PARTICIPANT_MIN_DIMENSIONS.height; 43798 } 43799 43800 lanesMinHeight = getLanesMinHeight(participant, start); 43801 43802 return max$1(PARTICIPANT_MIN_DIMENSIONS.height, lanesMinHeight); 43803 } 43804 43805 function hasChildLanes(element) { 43806 return !!getChildLanes(element).length; 43807 } 43808 43809 function getLanesMinHeight(participant, resizeStart) { 43810 var lanes = getChildLanes(participant), 43811 resizedLane; 43812 43813 // find the nested lane which is currently resized 43814 resizedLane = findResizedLane(lanes, resizeStart); 43815 43816 // resized lane cannot shrink below the minimum height 43817 // but remaining lanes' dimensions are kept intact 43818 return participant.height - resizedLane.height + LANE_MIN_DIMENSIONS.height; 43819 } 43820 43821 /** 43822 * Find nested lane which is currently resized. 43823 * 43824 * @param {Array<djs.model.Shape>} lanes 43825 * @param {number} resizeStart 43826 */ 43827 function findResizedLane(lanes, resizeStart) { 43828 var i, lane, childLanes; 43829 43830 for (i = 0; i < lanes.length; i++) { 43831 lane = lanes[i]; 43832 43833 // resizing current lane or a lane nested 43834 if (resizeStart >= lane.y && resizeStart <= lane.y + lane.height) { 43835 childLanes = getChildLanes(lane); 43836 43837 // a nested lane is resized 43838 if (childLanes.length) { 43839 return findResizedLane(childLanes, resizeStart); 43840 } 43841 43842 // current lane is the resized one 43843 return lane; 43844 } 43845 } 43846 } 43847 43848 /** 43849 * Add start event replacing element with expanded sub process. 43850 * 43851 * @param {Injector} injector 43852 * @param {Modeling} modeling 43853 */ 43854 function SubProcessStartEventBehavior(injector, modeling) { 43855 injector.invoke(CommandInterceptor, this); 43856 43857 this.postExecuted('shape.replace', function(event) { 43858 var oldShape = event.context.oldShape, 43859 newShape = event.context.newShape; 43860 43861 if ( 43862 !is$1(newShape, 'bpmn:SubProcess') || 43863 !is$1(oldShape, 'bpmn:Task') || 43864 !isExpanded(newShape) 43865 ) { 43866 return; 43867 } 43868 43869 var position = getStartEventPosition(newShape); 43870 43871 modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape); 43872 }); 43873 } 43874 43875 SubProcessStartEventBehavior.$inject = [ 43876 'injector', 43877 'modeling' 43878 ]; 43879 43880 inherits$1(SubProcessStartEventBehavior, CommandInterceptor); 43881 43882 // helpers ////////// 43883 43884 function getStartEventPosition(shape) { 43885 return { 43886 x: shape.x + shape.width / 6, 43887 y: shape.y + shape.height / 2 43888 }; 43889 } 43890 43891 var LOW_PRIORITY$8 = 500; 43892 43893 43894 function ToggleElementCollapseBehaviour( 43895 eventBus, elementFactory, modeling, 43896 resize) { 43897 43898 CommandInterceptor.call(this, eventBus); 43899 43900 43901 function hideEmptyLabels(children) { 43902 if (children.length) { 43903 children.forEach(function(child) { 43904 if (child.type === 'label' && !child.businessObject.name) { 43905 child.hidden = true; 43906 } 43907 }); 43908 } 43909 } 43910 43911 function expandedBounds(shape, defaultSize) { 43912 var children = shape.children, 43913 newBounds = defaultSize, 43914 visibleElements, 43915 visibleBBox; 43916 43917 visibleElements = filterVisible(children).concat([ shape ]); 43918 43919 visibleBBox = computeChildrenBBox(visibleElements); 43920 43921 if (visibleBBox) { 43922 43923 // center to visibleBBox with max(defaultSize, childrenBounds) 43924 newBounds.width = Math.max(visibleBBox.width, newBounds.width); 43925 newBounds.height = Math.max(visibleBBox.height, newBounds.height); 43926 43927 newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2; 43928 newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2; 43929 } else { 43930 43931 // center to collapsed shape with defaultSize 43932 newBounds.x = shape.x + (shape.width - newBounds.width) / 2; 43933 newBounds.y = shape.y + (shape.height - newBounds.height) / 2; 43934 } 43935 43936 return newBounds; 43937 } 43938 43939 function collapsedBounds(shape, defaultSize) { 43940 43941 return { 43942 x: shape.x + (shape.width - defaultSize.width) / 2, 43943 y: shape.y + (shape.height - defaultSize.height) / 2, 43944 width: defaultSize.width, 43945 height: defaultSize.height 43946 }; 43947 } 43948 43949 this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) { 43950 43951 var context = e.context, 43952 shape = context.shape; 43953 43954 if (!is$1(shape, 'bpmn:SubProcess')) { 43955 return; 43956 } 43957 43958 if (!shape.collapsed) { 43959 43960 // all children got made visible through djs, hide empty labels 43961 hideEmptyLabels(shape.children); 43962 43963 // remove collapsed marker 43964 getBusinessObject(shape).di.isExpanded = true; 43965 } else { 43966 43967 // place collapsed marker 43968 getBusinessObject(shape).di.isExpanded = false; 43969 } 43970 }); 43971 43972 this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) { 43973 43974 var context = e.context; 43975 var shape = context.shape; 43976 43977 43978 // revert removing/placing collapsed marker 43979 if (!shape.collapsed) { 43980 getBusinessObject(shape).di.isExpanded = true; 43981 43982 } else { 43983 getBusinessObject(shape).di.isExpanded = false; 43984 } 43985 }); 43986 43987 this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY$8, function(e) { 43988 var shape = e.context.shape, 43989 defaultSize = elementFactory._getDefaultSize(shape), 43990 newBounds; 43991 43992 if (shape.collapsed) { 43993 43994 // resize to default size of collapsed shapes 43995 newBounds = collapsedBounds(shape, defaultSize); 43996 } else { 43997 43998 // resize to bounds of max(visible children, defaultSize) 43999 newBounds = expandedBounds(shape, defaultSize); 44000 } 44001 44002 modeling.resizeShape(shape, newBounds, null, { 44003 autoResize: shape.collapsed ? false : 'nwse' 44004 }); 44005 }); 44006 44007 } 44008 44009 44010 inherits$1(ToggleElementCollapseBehaviour, CommandInterceptor); 44011 44012 ToggleElementCollapseBehaviour.$inject = [ 44013 'eventBus', 44014 'elementFactory', 44015 'modeling' 44016 ]; 44017 44018 44019 // helpers ////////////////////// 44020 44021 function filterVisible(elements) { 44022 return elements.filter(function(e) { 44023 return !e.hidden; 44024 }); 44025 } 44026 44027 /** 44028 * Unclaims model IDs on element deletion. 44029 * 44030 * @param {Canvas} canvas 44031 * @param {Injector} injector 44032 * @param {Moddle} moddle 44033 * @param {Modeling} modeling 44034 */ 44035 function UnclaimIdBehavior(canvas, injector, moddle, modeling) { 44036 injector.invoke(CommandInterceptor, this); 44037 44038 this.preExecute('shape.delete', function(event) { 44039 var context = event.context, 44040 shape = context.shape, 44041 shapeBo = shape.businessObject; 44042 44043 if (isLabel$6(shape)) { 44044 return; 44045 } 44046 44047 if (is$1(shape, 'bpmn:Participant') && isExpanded(shape)) { 44048 moddle.ids.unclaim(shapeBo.processRef.id); 44049 } 44050 44051 modeling.unclaimId(shapeBo.id, shapeBo); 44052 }); 44053 44054 44055 this.preExecute('connection.delete', function(event) { 44056 var context = event.context, 44057 connection = context.connection, 44058 connectionBo = connection.businessObject; 44059 44060 modeling.unclaimId(connectionBo.id, connectionBo); 44061 }); 44062 44063 this.preExecute('canvas.updateRoot', function() { 44064 var rootElement = canvas.getRootElement(), 44065 rootElementBo = rootElement.businessObject; 44066 44067 moddle.ids.unclaim(rootElementBo.id); 44068 }); 44069 } 44070 44071 inherits$1(UnclaimIdBehavior, CommandInterceptor); 44072 44073 UnclaimIdBehavior.$inject = [ 'canvas', 'injector', 'moddle', 'modeling' ]; 44074 44075 var LOW_PRIORITY$7 = 500, 44076 HIGH_PRIORITY$7 = 5000; 44077 44078 44079 /** 44080 * BPMN specific delete lane behavior 44081 */ 44082 function UpdateFlowNodeRefsBehavior(eventBus, modeling, translate) { 44083 44084 CommandInterceptor.call(this, eventBus); 44085 44086 /** 44087 * Ok, this is it: 44088 * 44089 * We have to update the Lane#flowNodeRefs _and_ 44090 * FlowNode#lanes with every FlowNode move/resize and 44091 * Lane move/resize. 44092 * 44093 * We want to group that stuff to recompute containments 44094 * as efficient as possible. 44095 * 44096 * Yea! 44097 */ 44098 44099 // the update context 44100 var context; 44101 44102 44103 function initContext() { 44104 context = context || new UpdateContext(); 44105 context.enter(); 44106 44107 return context; 44108 } 44109 44110 function getContext() { 44111 if (!context) { 44112 throw new Error(translate('out of bounds release')); 44113 } 44114 44115 return context; 44116 } 44117 44118 function releaseContext() { 44119 44120 if (!context) { 44121 throw new Error(translate('out of bounds release')); 44122 } 44123 44124 var triggerUpdate = context.leave(); 44125 44126 if (triggerUpdate) { 44127 modeling.updateLaneRefs(context.flowNodes, context.lanes); 44128 44129 context = null; 44130 } 44131 44132 return triggerUpdate; 44133 } 44134 44135 44136 var laneRefUpdateEvents = [ 44137 'spaceTool', 44138 'lane.add', 44139 'lane.resize', 44140 'lane.split', 44141 'elements.create', 44142 'elements.delete', 44143 'elements.move', 44144 'shape.create', 44145 'shape.delete', 44146 'shape.move', 44147 'shape.resize' 44148 ]; 44149 44150 44151 // listen to a lot of stuff to group lane updates 44152 44153 this.preExecute(laneRefUpdateEvents, HIGH_PRIORITY$7, function(event) { 44154 initContext(); 44155 }); 44156 44157 this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY$7, function(event) { 44158 releaseContext(); 44159 }); 44160 44161 44162 // Mark flow nodes + lanes that need an update 44163 44164 this.preExecute([ 44165 'shape.create', 44166 'shape.move', 44167 'shape.delete', 44168 'shape.resize' 44169 ], function(event) { 44170 44171 var context = event.context, 44172 shape = context.shape; 44173 44174 var updateContext = getContext(); 44175 44176 // no need to update labels 44177 if (shape.labelTarget) { 44178 return; 44179 } 44180 44181 if (is$1(shape, 'bpmn:Lane')) { 44182 updateContext.addLane(shape); 44183 } 44184 44185 if (is$1(shape, 'bpmn:FlowNode')) { 44186 updateContext.addFlowNode(shape); 44187 } 44188 }); 44189 } 44190 44191 UpdateFlowNodeRefsBehavior.$inject = [ 44192 'eventBus', 44193 'modeling' , 44194 'translate' 44195 ]; 44196 44197 inherits$1(UpdateFlowNodeRefsBehavior, CommandInterceptor); 44198 44199 44200 function UpdateContext() { 44201 44202 this.flowNodes = []; 44203 this.lanes = []; 44204 44205 this.counter = 0; 44206 44207 this.addLane = function(lane) { 44208 this.lanes.push(lane); 44209 }; 44210 44211 this.addFlowNode = function(flowNode) { 44212 this.flowNodes.push(flowNode); 44213 }; 44214 44215 this.enter = function() { 44216 this.counter++; 44217 }; 44218 44219 this.leave = function() { 44220 this.counter--; 44221 44222 return !this.counter; 44223 }; 44224 } 44225 44226 /** 44227 * A behavior that unsets the Default property of 44228 * sequence flow source on element delete, if the 44229 * removed element is the Gateway or Task's default flow. 44230 * 44231 * @param {EventBus} eventBus 44232 * @param {Modeling} modeling 44233 */ 44234 function DeleteSequenceFlowBehavior(eventBus, modeling) { 44235 44236 CommandInterceptor.call(this, eventBus); 44237 44238 44239 this.preExecute('connection.delete', function(event) { 44240 var context = event.context, 44241 connection = context.connection, 44242 source = connection.source; 44243 44244 if (isDefaultFlow(connection, source)) { 44245 modeling.updateProperties(source, { 44246 'default': null 44247 }); 44248 } 44249 }); 44250 } 44251 44252 inherits$1(DeleteSequenceFlowBehavior, CommandInterceptor); 44253 44254 DeleteSequenceFlowBehavior.$inject = [ 44255 'eventBus', 44256 'modeling' 44257 ]; 44258 44259 44260 // helpers ////////////////////// 44261 44262 function isDefaultFlow(connection, source) { 44263 44264 if (!is$1(connection, 'bpmn:SequenceFlow')) { 44265 return false; 44266 } 44267 44268 var sourceBo = getBusinessObject(source), 44269 sequenceFlow = getBusinessObject(connection); 44270 44271 return sourceBo.get('default') === sequenceFlow; 44272 } 44273 44274 var BehaviorModule = { 44275 __init__: [ 44276 'adaptiveLabelPositioningBehavior', 44277 'appendBehavior', 44278 'associationBehavior', 44279 'attachEventBehavior', 44280 'boundaryEventBehavior', 44281 'rootElementReferenceBehavior', 44282 'createBehavior', 44283 'fixHoverBehavior', 44284 'createDataObjectBehavior', 44285 'createParticipantBehavior', 44286 'dataStoreBehavior', 44287 'dataInputAssociationBehavior', 44288 'deleteLaneBehavior', 44289 'detachEventBehavior', 44290 'dropOnFlowBehavior', 44291 'eventBasedGatewayBehavior', 44292 'groupBehavior', 44293 'importDockingFix', 44294 'isHorizontalFix', 44295 'labelBehavior', 44296 'messageFlowBehavior', 44297 'modelingFeedback', 44298 'removeElementBehavior', 44299 'removeParticipantBehavior', 44300 'replaceConnectionBehavior', 44301 'replaceElementBehaviour', 44302 'resizeBehavior', 44303 'resizeLaneBehavior', 44304 'toggleElementCollapseBehaviour', 44305 'spaceToolBehavior', 44306 'subProcessStartEventBehavior', 44307 'unclaimIdBehavior', 44308 'unsetDefaultFlowBehavior', 44309 'updateFlowNodeRefsBehavior' 44310 ], 44311 adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ], 44312 appendBehavior: [ 'type', AppendBehavior ], 44313 associationBehavior: [ 'type', AssociationBehavior ], 44314 attachEventBehavior: [ 'type', AttachEventBehavior ], 44315 boundaryEventBehavior: [ 'type', BoundaryEventBehavior ], 44316 rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ], 44317 createBehavior: [ 'type', CreateBehavior ], 44318 fixHoverBehavior: [ 'type', FixHoverBehavior ], 44319 createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ], 44320 createParticipantBehavior: [ 'type', CreateParticipantBehavior ], 44321 dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ], 44322 dataStoreBehavior: [ 'type', DataStoreBehavior ], 44323 deleteLaneBehavior: [ 'type', DeleteLaneBehavior ], 44324 detachEventBehavior: [ 'type', DetachEventBehavior ], 44325 dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ], 44326 eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ], 44327 groupBehavior: [ 'type', GroupBehavior ], 44328 importDockingFix: [ 'type', ImportDockingFix ], 44329 isHorizontalFix: [ 'type', IsHorizontalFix ], 44330 labelBehavior: [ 'type', LabelBehavior ], 44331 messageFlowBehavior: [ 'type', MessageFlowBehavior ], 44332 modelingFeedback: [ 'type', ModelingFeedback ], 44333 replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ], 44334 removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ], 44335 replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ], 44336 resizeBehavior: [ 'type', ResizeBehavior ], 44337 resizeLaneBehavior: [ 'type', ResizeLaneBehavior ], 44338 removeElementBehavior: [ 'type', RemoveElementBehavior ], 44339 toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ], 44340 spaceToolBehavior: [ 'type', SpaceToolBehavior ], 44341 subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ], 44342 unclaimIdBehavior: [ 'type', UnclaimIdBehavior ], 44343 updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ], 44344 unsetDefaultFlowBehavior: [ 'type', DeleteSequenceFlowBehavior ] 44345 }; 44346 44347 function getBoundaryAttachment(position, targetBounds) { 44348 44349 var orientation = getOrientation(position, targetBounds, -15); 44350 44351 if (orientation !== 'intersect') { 44352 return orientation; 44353 } else { 44354 return null; 44355 } 44356 } 44357 44358 /** 44359 * BPMN specific modeling rule 44360 */ 44361 function BpmnRules(eventBus) { 44362 RuleProvider.call(this, eventBus); 44363 } 44364 44365 inherits$1(BpmnRules, RuleProvider); 44366 44367 BpmnRules.$inject = [ 'eventBus' ]; 44368 44369 BpmnRules.prototype.init = function() { 44370 44371 this.addRule('connection.start', function(context) { 44372 var source = context.source; 44373 44374 return canStartConnection(source); 44375 }); 44376 44377 this.addRule('connection.create', function(context) { 44378 var source = context.source, 44379 target = context.target, 44380 hints = context.hints || {}, 44381 targetParent = hints.targetParent, 44382 targetAttach = hints.targetAttach; 44383 44384 // don't allow incoming connections on 44385 // newly created boundary events 44386 // to boundary events 44387 if (targetAttach) { 44388 return false; 44389 } 44390 44391 // temporarily set target parent for scoping 44392 // checks to work 44393 if (targetParent) { 44394 target.parent = targetParent; 44395 } 44396 44397 try { 44398 return canConnect(source, target); 44399 } finally { 44400 44401 // unset temporary target parent 44402 if (targetParent) { 44403 target.parent = null; 44404 } 44405 } 44406 }); 44407 44408 this.addRule('connection.reconnect', function(context) { 44409 44410 var connection = context.connection, 44411 source = context.source, 44412 target = context.target; 44413 44414 return canConnect(source, target, connection); 44415 }); 44416 44417 this.addRule('connection.updateWaypoints', function(context) { 44418 return { 44419 type: context.connection.type 44420 }; 44421 }); 44422 44423 this.addRule('shape.resize', function(context) { 44424 44425 var shape = context.shape, 44426 newBounds = context.newBounds; 44427 44428 return canResize(shape, newBounds); 44429 }); 44430 44431 this.addRule('elements.create', function(context) { 44432 var elements = context.elements, 44433 position = context.position, 44434 target = context.target; 44435 44436 if (isConnection$8(target) && !canInsert(elements, target)) { 44437 return false; 44438 } 44439 44440 return every(elements, function(element) { 44441 if (isConnection$8(element)) { 44442 return canConnect(element.source, element.target, element); 44443 } 44444 44445 if (element.host) { 44446 return canAttach(element, element.host, null, position); 44447 } 44448 44449 return canCreate(element, target, null); 44450 }); 44451 }); 44452 44453 this.addRule('elements.move', function(context) { 44454 44455 var target = context.target, 44456 shapes = context.shapes, 44457 position = context.position; 44458 44459 return canAttach(shapes, target, null, position) || 44460 canReplace(shapes, target, position) || 44461 canMove(shapes, target) || 44462 canInsert(shapes, target); 44463 }); 44464 44465 this.addRule('shape.create', function(context) { 44466 return canCreate( 44467 context.shape, 44468 context.target, 44469 context.source, 44470 context.position 44471 ); 44472 }); 44473 44474 this.addRule('shape.attach', function(context) { 44475 44476 return canAttach( 44477 context.shape, 44478 context.target, 44479 null, 44480 context.position 44481 ); 44482 }); 44483 44484 this.addRule('element.copy', function(context) { 44485 var element = context.element, 44486 elements = context.elements; 44487 44488 return canCopy(elements, element); 44489 }); 44490 }; 44491 44492 BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow; 44493 44494 BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow; 44495 44496 BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation; 44497 44498 BpmnRules.prototype.canConnectAssociation = canConnectAssociation; 44499 44500 BpmnRules.prototype.canMove = canMove; 44501 44502 BpmnRules.prototype.canAttach = canAttach; 44503 44504 BpmnRules.prototype.canReplace = canReplace; 44505 44506 BpmnRules.prototype.canDrop = canDrop; 44507 44508 BpmnRules.prototype.canInsert = canInsert; 44509 44510 BpmnRules.prototype.canCreate = canCreate; 44511 44512 BpmnRules.prototype.canConnect = canConnect; 44513 44514 BpmnRules.prototype.canResize = canResize; 44515 44516 BpmnRules.prototype.canCopy = canCopy; 44517 44518 /** 44519 * Utility functions for rule checking 44520 */ 44521 44522 /** 44523 * Checks if given element can be used for starting connection. 44524 * 44525 * @param {Element} source 44526 * @return {boolean} 44527 */ 44528 function canStartConnection(element) { 44529 if (nonExistingOrLabel(element)) { 44530 return null; 44531 } 44532 44533 return isAny(element, [ 44534 'bpmn:FlowNode', 44535 'bpmn:InteractionNode', 44536 'bpmn:DataObjectReference', 44537 'bpmn:DataStoreReference', 44538 'bpmn:Group', 44539 'bpmn:TextAnnotation' 44540 ]); 44541 } 44542 44543 function nonExistingOrLabel(element) { 44544 return !element || isLabel$6(element); 44545 } 44546 44547 function isSame$1(a, b) { 44548 return a === b; 44549 } 44550 44551 function getOrganizationalParent(element) { 44552 44553 do { 44554 if (is$1(element, 'bpmn:Process')) { 44555 return getBusinessObject(element); 44556 } 44557 44558 if (is$1(element, 'bpmn:Participant')) { 44559 return ( 44560 getBusinessObject(element).processRef || 44561 getBusinessObject(element) 44562 ); 44563 } 44564 } while ((element = element.parent)); 44565 44566 } 44567 44568 function isTextAnnotation(element) { 44569 return is$1(element, 'bpmn:TextAnnotation'); 44570 } 44571 44572 function isGroup(element) { 44573 return is$1(element, 'bpmn:Group') && !element.labelTarget; 44574 } 44575 44576 function isCompensationBoundary(element) { 44577 return is$1(element, 'bpmn:BoundaryEvent') && 44578 hasEventDefinition(element, 'bpmn:CompensateEventDefinition'); 44579 } 44580 44581 function isForCompensation(e) { 44582 return getBusinessObject(e).isForCompensation; 44583 } 44584 44585 function isSameOrganization(a, b) { 44586 var parentA = getOrganizationalParent(a), 44587 parentB = getOrganizationalParent(b); 44588 44589 return parentA === parentB; 44590 } 44591 44592 function isMessageFlowSource(element) { 44593 return ( 44594 is$1(element, 'bpmn:InteractionNode') && 44595 !is$1(element, 'bpmn:BoundaryEvent') && ( 44596 !is$1(element, 'bpmn:Event') || ( 44597 is$1(element, 'bpmn:ThrowEvent') && 44598 hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition') 44599 ) 44600 ) 44601 ); 44602 } 44603 44604 function isMessageFlowTarget(element) { 44605 return ( 44606 is$1(element, 'bpmn:InteractionNode') && 44607 !isForCompensation(element) && ( 44608 !is$1(element, 'bpmn:Event') || ( 44609 is$1(element, 'bpmn:CatchEvent') && 44610 hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition') 44611 ) 44612 ) && !( 44613 is$1(element, 'bpmn:BoundaryEvent') && 44614 !hasEventDefinition(element, 'bpmn:MessageEventDefinition') 44615 ) 44616 ); 44617 } 44618 44619 function getScopeParent(element) { 44620 44621 var parent = element; 44622 44623 while ((parent = parent.parent)) { 44624 44625 if (is$1(parent, 'bpmn:FlowElementsContainer')) { 44626 return getBusinessObject(parent); 44627 } 44628 44629 if (is$1(parent, 'bpmn:Participant')) { 44630 return getBusinessObject(parent).processRef; 44631 } 44632 } 44633 44634 return null; 44635 } 44636 44637 function isSameScope(a, b) { 44638 var scopeParentA = getScopeParent(a), 44639 scopeParentB = getScopeParent(b); 44640 44641 return scopeParentA === scopeParentB; 44642 } 44643 44644 function hasEventDefinition(element, eventDefinition) { 44645 var bo = getBusinessObject(element); 44646 44647 return !!find(bo.eventDefinitions || [], function(definition) { 44648 return is$1(definition, eventDefinition); 44649 }); 44650 } 44651 44652 function hasEventDefinitionOrNone(element, eventDefinition) { 44653 var bo = getBusinessObject(element); 44654 44655 return (bo.eventDefinitions || []).every(function(definition) { 44656 return is$1(definition, eventDefinition); 44657 }); 44658 } 44659 44660 function isSequenceFlowSource(element) { 44661 return ( 44662 is$1(element, 'bpmn:FlowNode') && 44663 !is$1(element, 'bpmn:EndEvent') && 44664 !isEventSubProcess(element) && 44665 !(is$1(element, 'bpmn:IntermediateThrowEvent') && 44666 hasEventDefinition(element, 'bpmn:LinkEventDefinition') 44667 ) && 44668 !isCompensationBoundary(element) && 44669 !isForCompensation(element) 44670 ); 44671 } 44672 44673 function isSequenceFlowTarget(element) { 44674 return ( 44675 is$1(element, 'bpmn:FlowNode') && 44676 !is$1(element, 'bpmn:StartEvent') && 44677 !is$1(element, 'bpmn:BoundaryEvent') && 44678 !isEventSubProcess(element) && 44679 !(is$1(element, 'bpmn:IntermediateCatchEvent') && 44680 hasEventDefinition(element, 'bpmn:LinkEventDefinition') 44681 ) && 44682 !isForCompensation(element) 44683 ); 44684 } 44685 44686 function isEventBasedTarget(element) { 44687 return ( 44688 is$1(element, 'bpmn:ReceiveTask') || ( 44689 is$1(element, 'bpmn:IntermediateCatchEvent') && ( 44690 hasEventDefinition(element, 'bpmn:MessageEventDefinition') || 44691 hasEventDefinition(element, 'bpmn:TimerEventDefinition') || 44692 hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') || 44693 hasEventDefinition(element, 'bpmn:SignalEventDefinition') 44694 ) 44695 ) 44696 ); 44697 } 44698 44699 function isConnection$8(element) { 44700 return element.waypoints; 44701 } 44702 44703 function getParents(element) { 44704 44705 var parents = []; 44706 44707 while (element) { 44708 element = element.parent; 44709 44710 if (element) { 44711 parents.push(element); 44712 } 44713 } 44714 44715 return parents; 44716 } 44717 44718 function isParent(possibleParent, element) { 44719 var allParents = getParents(element); 44720 return allParents.indexOf(possibleParent) !== -1; 44721 } 44722 44723 function canConnect(source, target, connection) { 44724 44725 if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) { 44726 return null; 44727 } 44728 44729 if (!is$1(connection, 'bpmn:DataAssociation')) { 44730 44731 if (canConnectMessageFlow(source, target)) { 44732 return { type: 'bpmn:MessageFlow' }; 44733 } 44734 44735 if (canConnectSequenceFlow(source, target)) { 44736 return { type: 'bpmn:SequenceFlow' }; 44737 } 44738 } 44739 44740 var connectDataAssociation = canConnectDataAssociation(source, target); 44741 44742 if (connectDataAssociation) { 44743 return connectDataAssociation; 44744 } 44745 44746 if (isCompensationBoundary(source) && isForCompensation(target)) { 44747 return { 44748 type: 'bpmn:Association', 44749 associationDirection: 'One' 44750 }; 44751 } 44752 44753 if (canConnectAssociation(source, target)) { 44754 44755 return { 44756 type: 'bpmn:Association' 44757 }; 44758 } 44759 44760 return false; 44761 } 44762 44763 /** 44764 * Can an element be dropped into the target element 44765 * 44766 * @return {boolean} 44767 */ 44768 function canDrop(element, target, position) { 44769 44770 // can move labels and groups everywhere 44771 if (isLabel$6(element) || isGroup(element)) { 44772 return true; 44773 } 44774 44775 44776 // disallow to create elements on collapsed pools 44777 if (is$1(target, 'bpmn:Participant') && !isExpanded(target)) { 44778 return false; 44779 } 44780 44781 // allow to create new participants on 44782 // existing collaboration and process diagrams 44783 if (is$1(element, 'bpmn:Participant')) { 44784 return is$1(target, 'bpmn:Process') || is$1(target, 'bpmn:Collaboration'); 44785 } 44786 44787 // allow moving DataInput / DataOutput within its original container only 44788 if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) { 44789 44790 if (element.parent) { 44791 return target === element.parent; 44792 } 44793 } 44794 44795 // allow creating lanes on participants and other lanes only 44796 if (is$1(element, 'bpmn:Lane')) { 44797 return is$1(target, 'bpmn:Participant') || is$1(target, 'bpmn:Lane'); 44798 } 44799 44800 // disallow dropping boundary events which cannot replace with intermediate event 44801 if (is$1(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) { 44802 return false; 44803 } 44804 44805 // drop flow elements onto flow element containers 44806 // and participants 44807 if (is$1(element, 'bpmn:FlowElement') && !is$1(element, 'bpmn:DataStoreReference')) { 44808 if (is$1(target, 'bpmn:FlowElementsContainer')) { 44809 return isExpanded(target); 44810 } 44811 44812 return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]); 44813 } 44814 44815 // disallow dropping data store reference if there is no process to append to 44816 if (is$1(element, 'bpmn:DataStoreReference') && is$1(target, 'bpmn:Collaboration')) { 44817 return some(getBusinessObject(target).get('participants'), function(participant) { 44818 return !!participant.get('processRef'); 44819 }); 44820 } 44821 44822 // account for the fact that data associations are always 44823 // rendered and moved to top (Process or Collaboration level) 44824 // 44825 // artifacts may be placed wherever, too 44826 if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) { 44827 return isAny(target, [ 44828 'bpmn:Collaboration', 44829 'bpmn:Lane', 44830 'bpmn:Participant', 44831 'bpmn:Process', 44832 'bpmn:SubProcess' ]); 44833 } 44834 44835 if (is$1(element, 'bpmn:MessageFlow')) { 44836 return is$1(target, 'bpmn:Collaboration') 44837 || element.source.parent == target 44838 || element.target.parent == target; 44839 } 44840 44841 return false; 44842 } 44843 44844 function isDroppableBoundaryEvent(event) { 44845 return getBusinessObject(event).cancelActivity && ( 44846 hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event) 44847 ); 44848 } 44849 44850 function isBoundaryEvent(element) { 44851 return !isLabel$6(element) && is$1(element, 'bpmn:BoundaryEvent'); 44852 } 44853 44854 function isLane(element) { 44855 return is$1(element, 'bpmn:Lane'); 44856 } 44857 44858 /** 44859 * We treat IntermediateThrowEvents as boundary events during create, 44860 * this must be reflected in the rules. 44861 */ 44862 function isBoundaryCandidate(element) { 44863 if (isBoundaryEvent(element)) { 44864 return true; 44865 } 44866 44867 if (is$1(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) { 44868 return true; 44869 } 44870 44871 return ( 44872 is$1(element, 'bpmn:IntermediateCatchEvent') && 44873 hasCommonBoundaryIntermediateEventDefinition(element) 44874 ); 44875 } 44876 44877 function hasNoEventDefinition(element) { 44878 var bo = getBusinessObject(element); 44879 44880 return bo && !(bo.eventDefinitions && bo.eventDefinitions.length); 44881 } 44882 44883 function hasCommonBoundaryIntermediateEventDefinition(element) { 44884 return hasOneOfEventDefinitions(element, [ 44885 'bpmn:MessageEventDefinition', 44886 'bpmn:TimerEventDefinition', 44887 'bpmn:SignalEventDefinition', 44888 'bpmn:ConditionalEventDefinition' 44889 ]); 44890 } 44891 44892 function hasOneOfEventDefinitions(element, eventDefinitions) { 44893 return eventDefinitions.some(function(definition) { 44894 return hasEventDefinition(element, definition); 44895 }); 44896 } 44897 44898 function isReceiveTaskAfterEventBasedGateway(element) { 44899 return ( 44900 is$1(element, 'bpmn:ReceiveTask') && 44901 find(element.incoming, function(incoming) { 44902 return is$1(incoming.source, 'bpmn:EventBasedGateway'); 44903 }) 44904 ); 44905 } 44906 44907 44908 function canAttach(elements, target, source, position) { 44909 44910 if (!Array.isArray(elements)) { 44911 elements = [ elements ]; 44912 } 44913 44914 // only (re-)attach one element at a time 44915 if (elements.length !== 1) { 44916 return false; 44917 } 44918 44919 var element = elements[0]; 44920 44921 // do not attach labels 44922 if (isLabel$6(element)) { 44923 return false; 44924 } 44925 44926 // only handle boundary events 44927 if (!isBoundaryCandidate(element)) { 44928 return false; 44929 } 44930 44931 // disallow drop on event sub processes 44932 if (isEventSubProcess(target)) { 44933 return false; 44934 } 44935 44936 // only allow drop on non compensation activities 44937 if (!is$1(target, 'bpmn:Activity') || isForCompensation(target)) { 44938 return false; 44939 } 44940 44941 // only attach to subprocess border 44942 if (position && !getBoundaryAttachment(position, target)) { 44943 return false; 44944 } 44945 44946 // do not attach on receive tasks after event based gateways 44947 if (isReceiveTaskAfterEventBasedGateway(target)) { 44948 return false; 44949 } 44950 44951 return 'attach'; 44952 } 44953 44954 44955 /** 44956 * Defines how to replace elements for a given target. 44957 * 44958 * Returns an array containing all elements which will be replaced. 44959 * 44960 * @example 44961 * 44962 * [{ id: 'IntermediateEvent_2', 44963 * type: 'bpmn:StartEvent' 44964 * }, 44965 * { id: 'IntermediateEvent_5', 44966 * type: 'bpmn:EndEvent' 44967 * }] 44968 * 44969 * @param {Array} elements 44970 * @param {Object} target 44971 * 44972 * @return {Object} an object containing all elements which have to be replaced 44973 */ 44974 function canReplace(elements, target, position) { 44975 44976 if (!target) { 44977 return false; 44978 } 44979 44980 var canExecute = { 44981 replacements: [] 44982 }; 44983 44984 forEach(elements, function(element) { 44985 44986 if (!isEventSubProcess(target)) { 44987 44988 if (is$1(element, 'bpmn:StartEvent') && 44989 element.type !== 'label' && 44990 canDrop(element, target)) { 44991 44992 // replace a non-interrupting start event by a blank interrupting start event 44993 // when the target is not an event sub process 44994 if (!isInterrupting(element)) { 44995 canExecute.replacements.push({ 44996 oldElementId: element.id, 44997 newElementType: 'bpmn:StartEvent' 44998 }); 44999 } 45000 45001 // replace an error/escalation/compensate start event by a blank interrupting start event 45002 // when the target is not an event sub process 45003 if (hasErrorEventDefinition(element) || 45004 hasEscalationEventDefinition(element) || 45005 hasCompensateEventDefinition(element)) { 45006 canExecute.replacements.push({ 45007 oldElementId: element.id, 45008 newElementType: 'bpmn:StartEvent' 45009 }); 45010 } 45011 45012 // replace a typed start event by a blank interrupting start event 45013 // when the target is a sub process but not an event sub process 45014 if (hasOneOfEventDefinitions(element, 45015 [ 45016 'bpmn:MessageEventDefinition', 45017 'bpmn:TimerEventDefinition', 45018 'bpmn:SignalEventDefinition', 45019 'bpmn:ConditionalEventDefinition' 45020 ]) && 45021 is$1(target, 'bpmn:SubProcess')) { 45022 canExecute.replacements.push({ 45023 oldElementId: element.id, 45024 newElementType: 'bpmn:StartEvent' 45025 }); 45026 } 45027 } 45028 } 45029 45030 if (!is$1(target, 'bpmn:Transaction')) { 45031 if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') && 45032 element.type !== 'label') { 45033 45034 if (is$1(element, 'bpmn:EndEvent') && canDrop(element, target)) { 45035 canExecute.replacements.push({ 45036 oldElementId: element.id, 45037 newElementType: 'bpmn:EndEvent' 45038 }); 45039 } 45040 45041 if (is$1(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) { 45042 canExecute.replacements.push({ 45043 oldElementId: element.id, 45044 newElementType: 'bpmn:BoundaryEvent' 45045 }); 45046 } 45047 } 45048 } 45049 }); 45050 45051 return canExecute.replacements.length ? canExecute : false; 45052 } 45053 45054 function canMove(elements, target) { 45055 45056 // do not move selection containing lanes 45057 if (some(elements, isLane)) { 45058 return false; 45059 } 45060 45061 // allow default move check to start move operation 45062 if (!target) { 45063 return true; 45064 } 45065 45066 return elements.every(function(element) { 45067 return canDrop(element, target); 45068 }); 45069 } 45070 45071 function canCreate(shape, target, source, position) { 45072 45073 if (!target) { 45074 return false; 45075 } 45076 45077 if (isLabel$6(shape) || isGroup(shape)) { 45078 return true; 45079 } 45080 45081 if (isSame$1(source, target)) { 45082 return false; 45083 } 45084 45085 // ensure we do not drop the element 45086 // into source 45087 if (source && isParent(source, target)) { 45088 return false; 45089 } 45090 45091 return canDrop(shape, target) || canInsert(shape, target); 45092 } 45093 45094 function canResize(shape, newBounds) { 45095 if (is$1(shape, 'bpmn:SubProcess')) { 45096 return ( 45097 isExpanded(shape) && ( 45098 !newBounds || (newBounds.width >= 100 && newBounds.height >= 80) 45099 ) 45100 ); 45101 } 45102 45103 if (is$1(shape, 'bpmn:Lane')) { 45104 return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60); 45105 } 45106 45107 if (is$1(shape, 'bpmn:Participant')) { 45108 return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50); 45109 } 45110 45111 if (isTextAnnotation(shape)) { 45112 return true; 45113 } 45114 45115 if (isGroup(shape)) { 45116 return true; 45117 } 45118 45119 return false; 45120 } 45121 45122 /** 45123 * Check, whether one side of the relationship 45124 * is a text annotation. 45125 */ 45126 function isOneTextAnnotation(source, target) { 45127 45128 var sourceTextAnnotation = isTextAnnotation(source), 45129 targetTextAnnotation = isTextAnnotation(target); 45130 45131 return ( 45132 (sourceTextAnnotation || targetTextAnnotation) && 45133 (sourceTextAnnotation !== targetTextAnnotation) 45134 ); 45135 } 45136 45137 45138 function canConnectAssociation(source, target) { 45139 45140 // do not connect connections 45141 if (isConnection$8(source) || isConnection$8(target)) { 45142 return false; 45143 } 45144 45145 // compensation boundary events are exception 45146 if (isCompensationBoundary(source) && isForCompensation(target)) { 45147 return true; 45148 } 45149 45150 // don't connect parent <-> child 45151 if (isParent(target, source) || isParent(source, target)) { 45152 return false; 45153 } 45154 45155 // allow connection of associations between <!TextAnnotation> and <TextAnnotation> 45156 if (isOneTextAnnotation(source, target)) { 45157 return true; 45158 } 45159 45160 // can connect associations where we can connect 45161 // data associations, too (!) 45162 return !!canConnectDataAssociation(source, target); 45163 } 45164 45165 function canConnectMessageFlow(source, target) { 45166 45167 // during connect user might move mouse out of canvas 45168 // https://github.com/bpmn-io/bpmn-js/issues/1033 45169 if (getRootElement(source) && !getRootElement(target)) { 45170 return false; 45171 } 45172 45173 return ( 45174 isMessageFlowSource(source) && 45175 isMessageFlowTarget(target) && 45176 !isSameOrganization(source, target) 45177 ); 45178 } 45179 45180 function canConnectSequenceFlow(source, target) { 45181 45182 if ( 45183 isEventBasedTarget(target) && 45184 target.incoming.length > 0 && 45185 areOutgoingEventBasedGatewayConnections(target.incoming) && 45186 !is$1(source, 'bpmn:EventBasedGateway') 45187 ) { 45188 return false; 45189 } 45190 45191 return isSequenceFlowSource(source) && 45192 isSequenceFlowTarget(target) && 45193 isSameScope(source, target) && 45194 !(is$1(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target)); 45195 } 45196 45197 45198 function canConnectDataAssociation(source, target) { 45199 45200 if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) && 45201 isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) { 45202 return { type: 'bpmn:DataInputAssociation' }; 45203 } 45204 45205 if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) && 45206 isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) { 45207 return { type: 'bpmn:DataOutputAssociation' }; 45208 } 45209 45210 return false; 45211 } 45212 45213 function canInsert(shape, flow, position) { 45214 45215 if (!flow) { 45216 return false; 45217 } 45218 45219 if (Array.isArray(shape)) { 45220 if (shape.length !== 1) { 45221 return false; 45222 } 45223 45224 shape = shape[0]; 45225 } 45226 45227 if (flow.source === shape || 45228 flow.target === shape) { 45229 return false; 45230 } 45231 45232 // return true if we can drop on the 45233 // underlying flow parent 45234 // 45235 // at this point we are not really able to talk 45236 // about connection rules (yet) 45237 45238 return ( 45239 isAny(flow, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) && 45240 !isLabel$6(flow) && 45241 is$1(shape, 'bpmn:FlowNode') && 45242 !is$1(shape, 'bpmn:BoundaryEvent') && 45243 canDrop(shape, flow.parent)); 45244 } 45245 45246 function includes$5(elements, element) { 45247 return (elements && element) && elements.indexOf(element) !== -1; 45248 } 45249 45250 function canCopy(elements, element) { 45251 if (isLabel$6(element)) { 45252 return true; 45253 } 45254 45255 if (is$1(element, 'bpmn:Lane') && !includes$5(elements, element.parent)) { 45256 return false; 45257 } 45258 45259 return true; 45260 } 45261 45262 function isOutgoingEventBasedGatewayConnection(connection) { 45263 45264 if (connection && connection.source) { 45265 return is$1(connection.source, 'bpmn:EventBasedGateway'); 45266 } 45267 } 45268 45269 function areOutgoingEventBasedGatewayConnections(connections) { 45270 connections = connections || []; 45271 45272 return connections.some(isOutgoingEventBasedGatewayConnection); 45273 } 45274 45275 function getRootElement(element) { 45276 return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration'); 45277 } 45278 45279 var RulesModule = { 45280 __depends__: [ 45281 RulesModule$1 45282 ], 45283 __init__: [ 'bpmnRules' ], 45284 bpmnRules: [ 'type', BpmnRules ] 45285 }; 45286 45287 var HIGH_PRIORITY$6 = 2000; 45288 45289 function BpmnDiOrdering(eventBus, canvas) { 45290 45291 eventBus.on('saveXML.start', HIGH_PRIORITY$6, orderDi); 45292 45293 function orderDi() { 45294 var root = canvas.getRootElement(), 45295 rootDi = getBusinessObject(root).di, 45296 elements, 45297 diElements; 45298 45299 elements = selfAndAllChildren([ root ], false); 45300 45301 // only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane 45302 elements = filter(elements, function(element) { 45303 return element !== root && !element.labelTarget; 45304 }); 45305 45306 diElements = map$1(elements, getDi); 45307 45308 rootDi.set('planeElement', diElements); 45309 } 45310 } 45311 45312 BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ]; 45313 45314 var DiOrderingModule = { 45315 __init__: [ 45316 'bpmnDiOrdering' 45317 ], 45318 bpmnDiOrdering: [ 'type', BpmnDiOrdering ] 45319 }; 45320 45321 /** 45322 * An abstract provider that allows modelers to implement a custom 45323 * ordering of diagram elements on the canvas. 45324 * 45325 * It makes sure that the order is always preserved during element 45326 * creation and move operations. 45327 * 45328 * In order to use this behavior, inherit from it and override 45329 * the method {@link OrderingProvider#getOrdering}. 45330 * 45331 * @example 45332 * 45333 * ```javascript 45334 * function CustomOrderingProvider(eventBus) { 45335 * OrderingProvider.call(this, eventBus); 45336 * 45337 * this.getOrdering = function(element, newParent) { 45338 * // always insert elements at the front 45339 * // when moving 45340 * return { 45341 * index: 0, 45342 * parent: newParent 45343 * }; 45344 * }; 45345 * } 45346 * ``` 45347 * 45348 * @param {EventBus} eventBus 45349 */ 45350 function OrderingProvider(eventBus) { 45351 45352 CommandInterceptor.call(this, eventBus); 45353 45354 45355 var self = this; 45356 45357 this.preExecute([ 'shape.create', 'connection.create' ], function(event) { 45358 45359 var context = event.context, 45360 element = context.shape || context.connection, 45361 parent = context.parent; 45362 45363 var ordering = self.getOrdering(element, parent); 45364 45365 if (ordering) { 45366 45367 if (ordering.parent !== undefined) { 45368 context.parent = ordering.parent; 45369 } 45370 45371 context.parentIndex = ordering.index; 45372 } 45373 }); 45374 45375 this.preExecute([ 'shape.move', 'connection.move' ], function(event) { 45376 45377 var context = event.context, 45378 element = context.shape || context.connection, 45379 parent = context.newParent || element.parent; 45380 45381 var ordering = self.getOrdering(element, parent); 45382 45383 if (ordering) { 45384 45385 if (ordering.parent !== undefined) { 45386 context.newParent = ordering.parent; 45387 } 45388 45389 context.newParentIndex = ordering.index; 45390 } 45391 }); 45392 } 45393 45394 /** 45395 * Return a custom ordering of the element, both in terms 45396 * of parent element and index in the new parent. 45397 * 45398 * Implementors of this method must return an object with 45399 * `parent` _and_ `index` in it. 45400 * 45401 * @param {djs.model.Base} element 45402 * @param {djs.model.Shape} newParent 45403 * 45404 * @return {Object} ordering descriptor 45405 */ 45406 OrderingProvider.prototype.getOrdering = function(element, newParent) { 45407 return null; 45408 }; 45409 45410 inherits$1(OrderingProvider, CommandInterceptor); 45411 45412 /** 45413 * a simple ordering provider that makes sure: 45414 * 45415 * (0) labels and groups are rendered always on top 45416 * (1) elements are ordered by a {level} property 45417 */ 45418 function BpmnOrderingProvider(eventBus, canvas, translate) { 45419 45420 OrderingProvider.call(this, eventBus); 45421 45422 var orders = [ 45423 { type: 'bpmn:SubProcess', order: { level: 6 } }, 45424 { 45425 type: 'bpmn:SequenceFlow', 45426 order: { 45427 level: 3, 45428 containers: [ 45429 'bpmn:Participant', 45430 'bpmn:FlowElementsContainer' 45431 ] 45432 } 45433 }, 45434 45435 // handle DataAssociation(s) like message flows and render them always on top 45436 { 45437 type: 'bpmn:DataAssociation', 45438 order: { 45439 level: 9, 45440 containers: [ 45441 'bpmn:Collaboration', 45442 'bpmn:Process' 45443 ] 45444 } 45445 }, 45446 { 45447 type: 'bpmn:MessageFlow', order: { 45448 level: 9, 45449 containers: [ 'bpmn:Collaboration' ] 45450 } 45451 }, 45452 { 45453 type: 'bpmn:Association', 45454 order: { 45455 level: 6, 45456 containers: [ 45457 'bpmn:Participant', 45458 'bpmn:FlowElementsContainer', 45459 'bpmn:Collaboration' 45460 ] 45461 } 45462 }, 45463 { type: 'bpmn:BoundaryEvent', order: { level: 8 } }, 45464 { 45465 type: 'bpmn:Group', 45466 order: { 45467 level: 10, 45468 containers: [ 45469 'bpmn:Collaboration', 45470 'bpmn:Process' 45471 ] 45472 } 45473 }, 45474 { type: 'bpmn:FlowElement', order: { level: 5 } }, 45475 { type: 'bpmn:Participant', order: { level: -2 } }, 45476 { type: 'bpmn:Lane', order: { level: -1 } } 45477 ]; 45478 45479 function computeOrder(element) { 45480 if (element.labelTarget) { 45481 return { level: 10 }; 45482 } 45483 45484 var entry = find(orders, function(o) { 45485 return isAny(element, [ o.type ]); 45486 }); 45487 45488 return entry && entry.order || { level: 1 }; 45489 } 45490 45491 function getOrder(element) { 45492 45493 var order = element.order; 45494 45495 if (!order) { 45496 element.order = order = computeOrder(element); 45497 } 45498 45499 if (!order) { 45500 throw new Error('no order for <' + element.id + '>'); 45501 } 45502 45503 return order; 45504 } 45505 45506 function findActualParent(element, newParent, containers) { 45507 45508 var actualParent = newParent; 45509 45510 while (actualParent) { 45511 45512 if (isAny(actualParent, containers)) { 45513 break; 45514 } 45515 45516 actualParent = actualParent.parent; 45517 } 45518 45519 if (!actualParent) { 45520 throw new Error('no parent for <' + element.id + '> in <' + (newParent && newParent.id) + '>'); 45521 } 45522 45523 return actualParent; 45524 } 45525 45526 this.getOrdering = function(element, newParent) { 45527 45528 // render labels always on top 45529 if (element.labelTarget) { 45530 return { 45531 parent: canvas.getRootElement(), 45532 index: -1 45533 }; 45534 } 45535 45536 var elementOrder = getOrder(element); 45537 45538 if (elementOrder.containers) { 45539 newParent = findActualParent(element, newParent, elementOrder.containers); 45540 } 45541 45542 var currentIndex = newParent.children.indexOf(element); 45543 45544 var insertIndex = findIndex(newParent.children, function(child) { 45545 45546 // do not compare with labels, they are created 45547 // in the wrong order (right after elements) during import and 45548 // mess up the positioning. 45549 if (!element.labelTarget && child.labelTarget) { 45550 return false; 45551 } 45552 45553 return elementOrder.level < getOrder(child).level; 45554 }); 45555 45556 45557 // if the element is already in the child list at 45558 // a smaller index, we need to adjust the insert index. 45559 // this takes into account that the element is being removed 45560 // before being re-inserted 45561 if (insertIndex !== -1) { 45562 if (currentIndex !== -1 && currentIndex < insertIndex) { 45563 insertIndex -= 1; 45564 } 45565 } 45566 45567 return { 45568 index: insertIndex, 45569 parent: newParent 45570 }; 45571 }; 45572 } 45573 45574 BpmnOrderingProvider.$inject = [ 'eventBus', 'canvas', 'translate' ]; 45575 45576 inherits$1(BpmnOrderingProvider, OrderingProvider); 45577 45578 var OrderingModule = { 45579 __depends__: [ 45580 translate 45581 ], 45582 __init__: [ 'bpmnOrderingProvider' ], 45583 bpmnOrderingProvider: [ 'type', BpmnOrderingProvider ] 45584 }; 45585 45586 /** 45587 * A service that offers un- and redoable execution of commands. 45588 * 45589 * The command stack is responsible for executing modeling actions 45590 * in a un- and redoable manner. To do this it delegates the actual 45591 * command execution to {@link CommandHandler}s. 45592 * 45593 * Command handlers provide {@link CommandHandler#execute(ctx)} and 45594 * {@link CommandHandler#revert(ctx)} methods to un- and redo a command 45595 * identified by a command context. 45596 * 45597 * 45598 * ## Life-Cycle events 45599 * 45600 * In the process the command stack fires a number of life-cycle events 45601 * that other components to participate in the command execution. 45602 * 45603 * * preExecute 45604 * * preExecuted 45605 * * execute 45606 * * executed 45607 * * postExecute 45608 * * postExecuted 45609 * * revert 45610 * * reverted 45611 * 45612 * A special event is used for validating, whether a command can be 45613 * performed prior to its execution. 45614 * 45615 * * canExecute 45616 * 45617 * Each of the events is fired as `commandStack.{eventName}` and 45618 * `commandStack.{commandName}.{eventName}`, respectively. This gives 45619 * components fine grained control on where to hook into. 45620 * 45621 * The event object fired transports `command`, the name of the 45622 * command and `context`, the command context. 45623 * 45624 * 45625 * ## Creating Command Handlers 45626 * 45627 * Command handlers should provide the {@link CommandHandler#execute(ctx)} 45628 * and {@link CommandHandler#revert(ctx)} methods to implement 45629 * redoing and undoing of a command. 45630 * 45631 * A command handler _must_ ensure undo is performed properly in order 45632 * not to break the undo chain. It must also return the shapes that 45633 * got changed during the `execute` and `revert` operations. 45634 * 45635 * Command handlers may execute other modeling operations (and thus 45636 * commands) in their `preExecute` and `postExecute` phases. The command 45637 * stack will properly group all commands together into a logical unit 45638 * that may be re- and undone atomically. 45639 * 45640 * Command handlers must not execute other commands from within their 45641 * core implementation (`execute`, `revert`). 45642 * 45643 * 45644 * ## Change Tracking 45645 * 45646 * During the execution of the CommandStack it will keep track of all 45647 * elements that have been touched during the command's execution. 45648 * 45649 * At the end of the CommandStack execution it will notify interested 45650 * components via an 'elements.changed' event with all the dirty 45651 * elements. 45652 * 45653 * The event can be picked up by components that are interested in the fact 45654 * that elements have been changed. One use case for this is updating 45655 * their graphical representation after moving / resizing or deletion. 45656 * 45657 * @see CommandHandler 45658 * 45659 * @param {EventBus} eventBus 45660 * @param {Injector} injector 45661 */ 45662 function CommandStack(eventBus, injector) { 45663 45664 /** 45665 * A map of all registered command handlers. 45666 * 45667 * @type {Object} 45668 */ 45669 this._handlerMap = {}; 45670 45671 /** 45672 * A stack containing all re/undoable actions on the diagram 45673 * 45674 * @type {Array<Object>} 45675 */ 45676 this._stack = []; 45677 45678 /** 45679 * The current index on the stack 45680 * 45681 * @type {number} 45682 */ 45683 this._stackIdx = -1; 45684 45685 /** 45686 * Current active commandStack execution 45687 * 45688 * @type {Object} 45689 * @property {Object[]} actions 45690 * @property {Object[]} dirty 45691 * @property { 'undo' | 'redo' | 'clear' | 'execute' | null } trigger the cause of the current excecution 45692 */ 45693 this._currentExecution = { 45694 actions: [], 45695 dirty: [], 45696 trigger: null 45697 }; 45698 45699 45700 this._injector = injector; 45701 this._eventBus = eventBus; 45702 45703 this._uid = 1; 45704 45705 eventBus.on([ 45706 'diagram.destroy', 45707 'diagram.clear' 45708 ], function() { 45709 this.clear(false); 45710 }, this); 45711 } 45712 45713 CommandStack.$inject = [ 'eventBus', 'injector' ]; 45714 45715 45716 /** 45717 * Execute a command 45718 * 45719 * @param {string} command the command to execute 45720 * @param {Object} context the environment to execute the command in 45721 */ 45722 CommandStack.prototype.execute = function(command, context) { 45723 if (!command) { 45724 throw new Error('command required'); 45725 } 45726 45727 this._currentExecution.trigger = 'execute'; 45728 45729 var action = { command: command, context: context }; 45730 45731 this._pushAction(action); 45732 this._internalExecute(action); 45733 this._popAction(action); 45734 }; 45735 45736 45737 /** 45738 * Ask whether a given command can be executed. 45739 * 45740 * Implementors may hook into the mechanism on two ways: 45741 * 45742 * * in event listeners: 45743 * 45744 * Users may prevent the execution via an event listener. 45745 * It must prevent the default action for `commandStack.(<command>.)canExecute` events. 45746 * 45747 * * in command handlers: 45748 * 45749 * If the method {@link CommandHandler#canExecute} is implemented in a handler 45750 * it will be called to figure out whether the execution is allowed. 45751 * 45752 * @param {string} command the command to execute 45753 * @param {Object} context the environment to execute the command in 45754 * 45755 * @return {boolean} true if the command can be executed 45756 */ 45757 CommandStack.prototype.canExecute = function(command, context) { 45758 45759 var action = { command: command, context: context }; 45760 45761 var handler = this._getHandler(command); 45762 45763 var result = this._fire(command, 'canExecute', action); 45764 45765 // handler#canExecute will only be called if no listener 45766 // decided on a result already 45767 if (result === undefined) { 45768 if (!handler) { 45769 return false; 45770 } 45771 45772 if (handler.canExecute) { 45773 result = handler.canExecute(context); 45774 } 45775 } 45776 45777 return result; 45778 }; 45779 45780 45781 /** 45782 * Clear the command stack, erasing all undo / redo history 45783 */ 45784 CommandStack.prototype.clear = function(emit) { 45785 this._stack.length = 0; 45786 this._stackIdx = -1; 45787 45788 if (emit !== false) { 45789 this._fire('changed', { trigger: 'clear' }); 45790 } 45791 }; 45792 45793 45794 /** 45795 * Undo last command(s) 45796 */ 45797 CommandStack.prototype.undo = function() { 45798 var action = this._getUndoAction(), 45799 next; 45800 45801 if (action) { 45802 this._currentExecution.trigger = 'undo'; 45803 45804 this._pushAction(action); 45805 45806 while (action) { 45807 this._internalUndo(action); 45808 next = this._getUndoAction(); 45809 45810 if (!next || next.id !== action.id) { 45811 break; 45812 } 45813 45814 action = next; 45815 } 45816 45817 this._popAction(); 45818 } 45819 }; 45820 45821 45822 /** 45823 * Redo last command(s) 45824 */ 45825 CommandStack.prototype.redo = function() { 45826 var action = this._getRedoAction(), 45827 next; 45828 45829 if (action) { 45830 this._currentExecution.trigger = 'redo'; 45831 45832 this._pushAction(action); 45833 45834 while (action) { 45835 this._internalExecute(action, true); 45836 next = this._getRedoAction(); 45837 45838 if (!next || next.id !== action.id) { 45839 break; 45840 } 45841 45842 action = next; 45843 } 45844 45845 this._popAction(); 45846 } 45847 }; 45848 45849 45850 /** 45851 * Register a handler instance with the command stack 45852 * 45853 * @param {string} command 45854 * @param {CommandHandler} handler 45855 */ 45856 CommandStack.prototype.register = function(command, handler) { 45857 this._setHandler(command, handler); 45858 }; 45859 45860 45861 /** 45862 * Register a handler type with the command stack 45863 * by instantiating it and injecting its dependencies. 45864 * 45865 * @param {string} command 45866 * @param {Function} a constructor for a {@link CommandHandler} 45867 */ 45868 CommandStack.prototype.registerHandler = function(command, handlerCls) { 45869 45870 if (!command || !handlerCls) { 45871 throw new Error('command and handlerCls must be defined'); 45872 } 45873 45874 var handler = this._injector.instantiate(handlerCls); 45875 this.register(command, handler); 45876 }; 45877 45878 CommandStack.prototype.canUndo = function() { 45879 return !!this._getUndoAction(); 45880 }; 45881 45882 CommandStack.prototype.canRedo = function() { 45883 return !!this._getRedoAction(); 45884 }; 45885 45886 // stack access ////////////////////// 45887 45888 CommandStack.prototype._getRedoAction = function() { 45889 return this._stack[this._stackIdx + 1]; 45890 }; 45891 45892 45893 CommandStack.prototype._getUndoAction = function() { 45894 return this._stack[this._stackIdx]; 45895 }; 45896 45897 45898 // internal functionality ////////////////////// 45899 45900 CommandStack.prototype._internalUndo = function(action) { 45901 var self = this; 45902 45903 var command = action.command, 45904 context = action.context; 45905 45906 var handler = this._getHandler(command); 45907 45908 // guard against illegal nested command stack invocations 45909 this._atomicDo(function() { 45910 self._fire(command, 'revert', action); 45911 45912 if (handler.revert) { 45913 self._markDirty(handler.revert(context)); 45914 } 45915 45916 self._revertedAction(action); 45917 45918 self._fire(command, 'reverted', action); 45919 }); 45920 }; 45921 45922 45923 CommandStack.prototype._fire = function(command, qualifier, event) { 45924 if (arguments.length < 3) { 45925 event = qualifier; 45926 qualifier = null; 45927 } 45928 45929 var names = qualifier ? [ command + '.' + qualifier, qualifier ] : [ command ], 45930 i, name, result; 45931 45932 event = this._eventBus.createEvent(event); 45933 45934 for (i = 0; (name = names[i]); i++) { 45935 result = this._eventBus.fire('commandStack.' + name, event); 45936 45937 if (event.cancelBubble) { 45938 break; 45939 } 45940 } 45941 45942 return result; 45943 }; 45944 45945 CommandStack.prototype._createId = function() { 45946 return this._uid++; 45947 }; 45948 45949 CommandStack.prototype._atomicDo = function(fn) { 45950 45951 var execution = this._currentExecution; 45952 45953 execution.atomic = true; 45954 45955 try { 45956 fn(); 45957 } finally { 45958 execution.atomic = false; 45959 } 45960 }; 45961 45962 CommandStack.prototype._internalExecute = function(action, redo) { 45963 var self = this; 45964 45965 var command = action.command, 45966 context = action.context; 45967 45968 var handler = this._getHandler(command); 45969 45970 if (!handler) { 45971 throw new Error('no command handler registered for <' + command + '>'); 45972 } 45973 45974 this._pushAction(action); 45975 45976 if (!redo) { 45977 this._fire(command, 'preExecute', action); 45978 45979 if (handler.preExecute) { 45980 handler.preExecute(context); 45981 } 45982 45983 this._fire(command, 'preExecuted', action); 45984 } 45985 45986 // guard against illegal nested command stack invocations 45987 this._atomicDo(function() { 45988 45989 self._fire(command, 'execute', action); 45990 45991 if (handler.execute) { 45992 45993 // actual execute + mark return results as dirty 45994 self._markDirty(handler.execute(context)); 45995 } 45996 45997 // log to stack 45998 self._executedAction(action, redo); 45999 46000 self._fire(command, 'executed', action); 46001 }); 46002 46003 if (!redo) { 46004 this._fire(command, 'postExecute', action); 46005 46006 if (handler.postExecute) { 46007 handler.postExecute(context); 46008 } 46009 46010 this._fire(command, 'postExecuted', action); 46011 } 46012 46013 this._popAction(action); 46014 }; 46015 46016 46017 CommandStack.prototype._pushAction = function(action) { 46018 46019 var execution = this._currentExecution, 46020 actions = execution.actions; 46021 46022 var baseAction = actions[0]; 46023 46024 if (execution.atomic) { 46025 throw new Error('illegal invocation in <execute> or <revert> phase (action: ' + action.command + ')'); 46026 } 46027 46028 if (!action.id) { 46029 action.id = (baseAction && baseAction.id) || this._createId(); 46030 } 46031 46032 actions.push(action); 46033 }; 46034 46035 46036 CommandStack.prototype._popAction = function() { 46037 var execution = this._currentExecution, 46038 trigger = execution.trigger, 46039 actions = execution.actions, 46040 dirty = execution.dirty; 46041 46042 actions.pop(); 46043 46044 if (!actions.length) { 46045 this._eventBus.fire('elements.changed', { elements: uniqueBy('id', dirty.reverse()) }); 46046 46047 dirty.length = 0; 46048 46049 this._fire('changed', { trigger: trigger }); 46050 46051 execution.trigger = null; 46052 } 46053 }; 46054 46055 46056 CommandStack.prototype._markDirty = function(elements) { 46057 var execution = this._currentExecution; 46058 46059 if (!elements) { 46060 return; 46061 } 46062 46063 elements = isArray$2(elements) ? elements : [ elements ]; 46064 46065 execution.dirty = execution.dirty.concat(elements); 46066 }; 46067 46068 46069 CommandStack.prototype._executedAction = function(action, redo) { 46070 var stackIdx = ++this._stackIdx; 46071 46072 if (!redo) { 46073 this._stack.splice(stackIdx, this._stack.length, action); 46074 } 46075 }; 46076 46077 46078 CommandStack.prototype._revertedAction = function(action) { 46079 this._stackIdx--; 46080 }; 46081 46082 46083 CommandStack.prototype._getHandler = function(command) { 46084 return this._handlerMap[command]; 46085 }; 46086 46087 CommandStack.prototype._setHandler = function(command, handler) { 46088 if (!command || !handler) { 46089 throw new Error('command and handler required'); 46090 } 46091 46092 if (this._handlerMap[command]) { 46093 throw new Error('overriding handler for command <' + command + '>'); 46094 } 46095 46096 this._handlerMap[command] = handler; 46097 }; 46098 46099 var CommandModule = { 46100 commandStack: [ 'type', CommandStack ] 46101 }; 46102 46103 // document wide unique tooltip ids 46104 var ids = new IdGenerator('tt'); 46105 46106 46107 function createRoot(parentNode) { 46108 var root = domify( 46109 '<div class="djs-tooltip-container" style="position: absolute; width: 0; height: 0;" />' 46110 ); 46111 46112 parentNode.insertBefore(root, parentNode.firstChild); 46113 46114 return root; 46115 } 46116 46117 46118 function setPosition(el, x, y) { 46119 assign(el.style, { left: x + 'px', top: y + 'px' }); 46120 } 46121 46122 function setVisible(el, visible) { 46123 el.style.display = visible === false ? 'none' : ''; 46124 } 46125 46126 46127 var tooltipClass = 'djs-tooltip', 46128 tooltipSelector = '.' + tooltipClass; 46129 46130 /** 46131 * A service that allows users to render tool tips on the diagram. 46132 * 46133 * The tooltip service will take care of updating the tooltip positioning 46134 * during navigation + zooming. 46135 * 46136 * @example 46137 * 46138 * ```javascript 46139 * 46140 * // add a pink badge on the top left of the shape 46141 * tooltips.add({ 46142 * position: { 46143 * x: 50, 46144 * y: 100 46145 * }, 46146 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>' 46147 * }); 46148 * 46149 * // or with optional life span 46150 * tooltips.add({ 46151 * position: { 46152 * top: -5, 46153 * left: -5 46154 * }, 46155 * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>', 46156 * ttl: 2000 46157 * }); 46158 * 46159 * // remove a tool tip 46160 * var id = tooltips.add(...); 46161 * tooltips.remove(id); 46162 * ``` 46163 * 46164 * @param {EventBus} eventBus 46165 * @param {Canvas} canvas 46166 */ 46167 function Tooltips(eventBus, canvas) { 46168 46169 this._eventBus = eventBus; 46170 this._canvas = canvas; 46171 46172 this._ids = ids; 46173 46174 this._tooltipDefaults = { 46175 show: { 46176 minZoom: 0.7, 46177 maxZoom: 5.0 46178 } 46179 }; 46180 46181 /** 46182 * Mapping tooltipId -> tooltip 46183 */ 46184 this._tooltips = {}; 46185 46186 // root html element for all tooltips 46187 this._tooltipRoot = createRoot(canvas.getContainer()); 46188 46189 46190 var self = this; 46191 46192 delegate.bind(this._tooltipRoot, tooltipSelector, 'mousedown', function(event) { 46193 event.stopPropagation(); 46194 }); 46195 46196 delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseover', function(event) { 46197 self.trigger('mouseover', event); 46198 }); 46199 46200 delegate.bind(this._tooltipRoot, tooltipSelector, 'mouseout', function(event) { 46201 self.trigger('mouseout', event); 46202 }); 46203 46204 this._init(); 46205 } 46206 46207 46208 Tooltips.$inject = [ 'eventBus', 'canvas' ]; 46209 46210 46211 /** 46212 * Adds a HTML tooltip to the diagram 46213 * 46214 * @param {Object} tooltip the tooltip configuration 46215 * 46216 * @param {string|DOMElement} tooltip.html html element to use as an tooltip 46217 * @param {Object} [tooltip.show] show configuration 46218 * @param {number} [tooltip.show.minZoom] minimal zoom level to show the tooltip 46219 * @param {number} [tooltip.show.maxZoom] maximum zoom level to show the tooltip 46220 * @param {Object} tooltip.position where to attach the tooltip 46221 * @param {number} [tooltip.position.left] relative to element bbox left attachment 46222 * @param {number} [tooltip.position.top] relative to element bbox top attachment 46223 * @param {number} [tooltip.position.bottom] relative to element bbox bottom attachment 46224 * @param {number} [tooltip.position.right] relative to element bbox right attachment 46225 * @param {number} [tooltip.timeout=-1] 46226 * 46227 * @return {string} id that may be used to reference the tooltip for update or removal 46228 */ 46229 Tooltips.prototype.add = function(tooltip) { 46230 46231 if (!tooltip.position) { 46232 throw new Error('must specifiy tooltip position'); 46233 } 46234 46235 if (!tooltip.html) { 46236 throw new Error('must specifiy tooltip html'); 46237 } 46238 46239 var id = this._ids.next(); 46240 46241 tooltip = assign({}, this._tooltipDefaults, tooltip, { 46242 id: id 46243 }); 46244 46245 this._addTooltip(tooltip); 46246 46247 if (tooltip.timeout) { 46248 this.setTimeout(tooltip); 46249 } 46250 46251 return id; 46252 }; 46253 46254 Tooltips.prototype.trigger = function(action, event) { 46255 46256 var node = event.delegateTarget || event.target; 46257 46258 var tooltip = this.get(attr$1(node, 'data-tooltip-id')); 46259 46260 if (!tooltip) { 46261 return; 46262 } 46263 46264 if (action === 'mouseover' && tooltip.timeout) { 46265 this.clearTimeout(tooltip); 46266 } 46267 46268 if (action === 'mouseout' && tooltip.timeout) { 46269 46270 // cut timeout after mouse out 46271 tooltip.timeout = 1000; 46272 46273 this.setTimeout(tooltip); 46274 } 46275 }; 46276 46277 /** 46278 * Get a tooltip with the given id 46279 * 46280 * @param {string} id 46281 */ 46282 Tooltips.prototype.get = function(id) { 46283 46284 if (typeof id !== 'string') { 46285 id = id.id; 46286 } 46287 46288 return this._tooltips[id]; 46289 }; 46290 46291 Tooltips.prototype.clearTimeout = function(tooltip) { 46292 46293 tooltip = this.get(tooltip); 46294 46295 if (!tooltip) { 46296 return; 46297 } 46298 46299 var removeTimer = tooltip.removeTimer; 46300 46301 if (removeTimer) { 46302 clearTimeout(removeTimer); 46303 tooltip.removeTimer = null; 46304 } 46305 }; 46306 46307 Tooltips.prototype.setTimeout = function(tooltip) { 46308 46309 tooltip = this.get(tooltip); 46310 46311 if (!tooltip) { 46312 return; 46313 } 46314 46315 this.clearTimeout(tooltip); 46316 46317 var self = this; 46318 46319 tooltip.removeTimer = setTimeout(function() { 46320 self.remove(tooltip); 46321 }, tooltip.timeout); 46322 }; 46323 46324 /** 46325 * Remove an tooltip with the given id 46326 * 46327 * @param {string} id 46328 */ 46329 Tooltips.prototype.remove = function(id) { 46330 46331 var tooltip = this.get(id); 46332 46333 if (tooltip) { 46334 remove$2(tooltip.html); 46335 remove$2(tooltip.htmlContainer); 46336 46337 delete tooltip.htmlContainer; 46338 46339 delete this._tooltips[tooltip.id]; 46340 } 46341 }; 46342 46343 46344 Tooltips.prototype.show = function() { 46345 setVisible(this._tooltipRoot); 46346 }; 46347 46348 46349 Tooltips.prototype.hide = function() { 46350 setVisible(this._tooltipRoot, false); 46351 }; 46352 46353 46354 Tooltips.prototype._updateRoot = function(viewbox) { 46355 var a = viewbox.scale || 1; 46356 var d = viewbox.scale || 1; 46357 46358 var matrix = 'matrix(' + a + ',0,0,' + d + ',' + (-1 * viewbox.x * a) + ',' + (-1 * viewbox.y * d) + ')'; 46359 46360 this._tooltipRoot.style.transform = matrix; 46361 this._tooltipRoot.style['-ms-transform'] = matrix; 46362 }; 46363 46364 46365 Tooltips.prototype._addTooltip = function(tooltip) { 46366 46367 var id = tooltip.id, 46368 html = tooltip.html, 46369 htmlContainer, 46370 tooltipRoot = this._tooltipRoot; 46371 46372 // unwrap jquery (for those who need it) 46373 if (html.get && html.constructor.prototype.jquery) { 46374 html = html.get(0); 46375 } 46376 46377 // create proper html elements from 46378 // tooltip HTML strings 46379 if (isString(html)) { 46380 html = domify(html); 46381 } 46382 46383 htmlContainer = domify('<div data-tooltip-id="' + id + '" class="' + tooltipClass + '" style="position: absolute">'); 46384 46385 htmlContainer.appendChild(html); 46386 46387 if (tooltip.type) { 46388 classes$1(htmlContainer).add('djs-tooltip-' + tooltip.type); 46389 } 46390 46391 if (tooltip.className) { 46392 classes$1(htmlContainer).add(tooltip.className); 46393 } 46394 46395 tooltip.htmlContainer = htmlContainer; 46396 46397 tooltipRoot.appendChild(htmlContainer); 46398 46399 this._tooltips[id] = tooltip; 46400 46401 this._updateTooltip(tooltip); 46402 }; 46403 46404 46405 Tooltips.prototype._updateTooltip = function(tooltip) { 46406 46407 var position = tooltip.position, 46408 htmlContainer = tooltip.htmlContainer; 46409 46410 // update overlay html based on tooltip x, y 46411 46412 setPosition(htmlContainer, position.x, position.y); 46413 }; 46414 46415 46416 Tooltips.prototype._updateTooltipVisibilty = function(viewbox) { 46417 46418 forEach(this._tooltips, function(tooltip) { 46419 var show = tooltip.show, 46420 htmlContainer = tooltip.htmlContainer, 46421 visible = true; 46422 46423 if (show) { 46424 if (show.minZoom > viewbox.scale || 46425 show.maxZoom < viewbox.scale) { 46426 visible = false; 46427 } 46428 46429 setVisible(htmlContainer, visible); 46430 } 46431 }); 46432 }; 46433 46434 Tooltips.prototype._init = function() { 46435 46436 var self = this; 46437 46438 // scroll/zoom integration 46439 46440 function updateViewbox(viewbox) { 46441 self._updateRoot(viewbox); 46442 self._updateTooltipVisibilty(viewbox); 46443 46444 self.show(); 46445 } 46446 46447 this._eventBus.on('canvas.viewbox.changing', function(event) { 46448 self.hide(); 46449 }); 46450 46451 this._eventBus.on('canvas.viewbox.changed', function(event) { 46452 updateViewbox(event.viewbox); 46453 }); 46454 }; 46455 46456 var TooltipsModule = { 46457 __init__: [ 'tooltips' ], 46458 tooltips: [ 'type', Tooltips ] 46459 }; 46460 46461 /** 46462 * Remove from the beginning of a collection until it is empty. 46463 * 46464 * This is a null-safe operation that ensures elements 46465 * are being removed from the given collection until the 46466 * collection is empty. 46467 * 46468 * The implementation deals with the fact that a remove operation 46469 * may touch, i.e. remove multiple elements in the collection 46470 * at a time. 46471 * 46472 * @param {Array<Object>} [collection] 46473 * @param {Function} removeFn 46474 * 46475 * @return {Array<Object>} the cleared collection 46476 */ 46477 function saveClear(collection, removeFn) { 46478 46479 if (typeof removeFn !== 'function') { 46480 throw new Error('removeFn iterator must be a function'); 46481 } 46482 46483 if (!collection) { 46484 return; 46485 } 46486 46487 var e; 46488 46489 while ((e = collection[0])) { 46490 removeFn(e); 46491 } 46492 46493 return collection; 46494 } 46495 46496 var LOW_PRIORITY$6 = 250, 46497 HIGH_PRIORITY$5 = 1400; 46498 46499 46500 /** 46501 * A handler that makes sure labels are properly moved with 46502 * their label targets. 46503 * 46504 * @param {didi.Injector} injector 46505 * @param {EventBus} eventBus 46506 * @param {Modeling} modeling 46507 */ 46508 function LabelSupport(injector, eventBus, modeling) { 46509 46510 CommandInterceptor.call(this, eventBus); 46511 46512 var movePreview = injector.get('movePreview', false); 46513 46514 // remove labels from the collection that are being 46515 // moved with other elements anyway 46516 eventBus.on('shape.move.start', HIGH_PRIORITY$5, function(e) { 46517 46518 var context = e.context, 46519 shapes = context.shapes, 46520 validatedShapes = context.validatedShapes; 46521 46522 context.shapes = removeLabels(shapes); 46523 context.validatedShapes = removeLabels(validatedShapes); 46524 }); 46525 46526 // add labels to visual's group 46527 movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$6, function(e) { 46528 46529 var context = e.context, 46530 shapes = context.shapes; 46531 46532 var labels = []; 46533 46534 forEach(shapes, function(element) { 46535 46536 forEach(element.labels, function(label) { 46537 46538 if (!label.hidden && context.shapes.indexOf(label) === -1) { 46539 labels.push(label); 46540 } 46541 46542 if (element.labelTarget) { 46543 labels.push(element); 46544 } 46545 }); 46546 }); 46547 46548 forEach(labels, function(label) { 46549 movePreview.makeDraggable(context, label, true); 46550 }); 46551 46552 }); 46553 46554 // add all labels to move closure 46555 this.preExecuted('elements.move', HIGH_PRIORITY$5, function(e) { 46556 var context = e.context, 46557 closure = context.closure, 46558 enclosedElements = closure.enclosedElements; 46559 46560 var enclosedLabels = []; 46561 46562 // find labels that are not part of 46563 // move closure yet and add them 46564 forEach(enclosedElements, function(element) { 46565 forEach(element.labels, function(label) { 46566 46567 if (!enclosedElements[label.id]) { 46568 enclosedLabels.push(label); 46569 } 46570 }); 46571 }); 46572 46573 closure.addAll(enclosedLabels); 46574 }); 46575 46576 46577 this.preExecute([ 46578 'connection.delete', 46579 'shape.delete' 46580 ], function(e) { 46581 46582 var context = e.context, 46583 element = context.connection || context.shape; 46584 46585 saveClear(element.labels, function(label) { 46586 modeling.removeShape(label, { nested: true }); 46587 }); 46588 }); 46589 46590 46591 this.execute('shape.delete', function(e) { 46592 46593 var context = e.context, 46594 shape = context.shape, 46595 labelTarget = shape.labelTarget; 46596 46597 // unset labelTarget 46598 if (labelTarget) { 46599 context.labelTargetIndex = indexOf(labelTarget.labels, shape); 46600 context.labelTarget = labelTarget; 46601 46602 shape.labelTarget = null; 46603 } 46604 }); 46605 46606 this.revert('shape.delete', function(e) { 46607 46608 var context = e.context, 46609 shape = context.shape, 46610 labelTarget = context.labelTarget, 46611 labelTargetIndex = context.labelTargetIndex; 46612 46613 // restore labelTarget 46614 if (labelTarget) { 46615 add(labelTarget.labels, shape, labelTargetIndex); 46616 46617 shape.labelTarget = labelTarget; 46618 } 46619 }); 46620 46621 } 46622 46623 inherits$1(LabelSupport, CommandInterceptor); 46624 46625 LabelSupport.$inject = [ 46626 'injector', 46627 'eventBus', 46628 'modeling' 46629 ]; 46630 46631 46632 /** 46633 * Return a filtered list of elements that do not 46634 * contain attached elements with hosts being part 46635 * of the selection. 46636 * 46637 * @param {Array<djs.model.Base>} elements 46638 * 46639 * @return {Array<djs.model.Base>} filtered 46640 */ 46641 function removeLabels(elements) { 46642 46643 return filter(elements, function(element) { 46644 46645 // filter out labels that are move together 46646 // with their label targets 46647 return elements.indexOf(element.labelTarget) === -1; 46648 }); 46649 } 46650 46651 var LabelSupportModule = { 46652 __init__: [ 'labelSupport'], 46653 labelSupport: [ 'type', LabelSupport ] 46654 }; 46655 46656 var LOW_PRIORITY$5 = 251, 46657 HIGH_PRIORITY$4 = 1401; 46658 46659 var MARKER_ATTACH$1 = 'attach-ok'; 46660 46661 46662 /** 46663 * Adds the notion of attached elements to the modeler. 46664 * 46665 * Optionally depends on `diagram-js/lib/features/move` to render 46666 * the attached elements during move preview. 46667 * 46668 * Optionally depends on `diagram-js/lib/features/label-support` 46669 * to render attached labels during move preview. 46670 * 46671 * @param {didi.Injector} injector 46672 * @param {EventBus} eventBus 46673 * @param {Canvas} canvas 46674 * @param {Rules} rules 46675 * @param {Modeling} modeling 46676 */ 46677 function AttachSupport(injector, eventBus, canvas, rules, modeling) { 46678 46679 CommandInterceptor.call(this, eventBus); 46680 46681 var movePreview = injector.get('movePreview', false); 46682 46683 46684 // remove all the attached elements from the shapes to be validated 46685 // add all the attached shapes to the overall list of moved shapes 46686 eventBus.on('shape.move.start', HIGH_PRIORITY$4, function(e) { 46687 46688 var context = e.context, 46689 shapes = context.shapes, 46690 validatedShapes = context.validatedShapes; 46691 46692 context.shapes = addAttached(shapes); 46693 46694 context.validatedShapes = removeAttached(validatedShapes); 46695 }); 46696 46697 // add attachers to the visual's group 46698 movePreview && eventBus.on('shape.move.start', LOW_PRIORITY$5, function(e) { 46699 46700 var context = e.context, 46701 shapes = context.shapes, 46702 attachers = getAttachers(shapes); 46703 46704 forEach(attachers, function(attacher) { 46705 movePreview.makeDraggable(context, attacher, true); 46706 46707 forEach(attacher.labels, function(label) { 46708 movePreview.makeDraggable(context, label, true); 46709 }); 46710 }); 46711 }); 46712 46713 // add attach-ok marker to current host 46714 movePreview && eventBus.on('shape.move.start', function(event) { 46715 var context = event.context, 46716 shapes = context.shapes; 46717 46718 if (shapes.length !== 1) { 46719 return; 46720 } 46721 46722 var shape = shapes[0]; 46723 46724 var host = shape.host; 46725 46726 if (host) { 46727 canvas.addMarker(host, MARKER_ATTACH$1); 46728 46729 eventBus.once([ 46730 'shape.move.out', 46731 'shape.move.cleanup' 46732 ], function() { 46733 canvas.removeMarker(host, MARKER_ATTACH$1); 46734 }); 46735 } 46736 }); 46737 46738 // add all attachers to move closure 46739 this.preExecuted('elements.move', HIGH_PRIORITY$4, function(e) { 46740 var context = e.context, 46741 closure = context.closure, 46742 shapes = context.shapes, 46743 attachers = getAttachers(shapes); 46744 46745 forEach(attachers, function(attacher) { 46746 closure.add(attacher, closure.topLevel[attacher.host.id]); 46747 }); 46748 }); 46749 46750 // perform the attaching after shapes are done moving 46751 this.postExecuted('elements.move', function(e) { 46752 46753 var context = e.context, 46754 shapes = context.shapes, 46755 newHost = context.newHost, 46756 attachers; 46757 46758 // only single elements can be attached 46759 // multiply elements can be detached 46760 if (newHost && shapes.length !== 1) { 46761 return; 46762 } 46763 46764 if (newHost) { 46765 attachers = shapes; 46766 } else { 46767 46768 // find attachers moved without host 46769 attachers = filter(shapes, function(shape) { 46770 var host = shape.host; 46771 46772 return isAttacher(shape) && !includes$4(shapes, host); 46773 }); 46774 } 46775 46776 forEach(attachers, function(attacher) { 46777 modeling.updateAttachment(attacher, newHost); 46778 }); 46779 }); 46780 46781 // ensure invalid attachment connections are removed 46782 this.postExecuted('elements.move', function(e) { 46783 46784 var shapes = e.context.shapes; 46785 46786 forEach(shapes, function(shape) { 46787 46788 forEach(shape.attachers, function(attacher) { 46789 46790 // remove invalid outgoing connections 46791 forEach(attacher.outgoing.slice(), function(connection) { 46792 var allowed = rules.allowed('connection.reconnect', { 46793 connection: connection, 46794 source: connection.source, 46795 target: connection.target 46796 }); 46797 46798 if (!allowed) { 46799 modeling.removeConnection(connection); 46800 } 46801 }); 46802 46803 // remove invalid incoming connections 46804 forEach(attacher.incoming.slice(), function(connection) { 46805 var allowed = rules.allowed('connection.reconnect', { 46806 connection: connection, 46807 source: connection.source, 46808 target: connection.target 46809 }); 46810 46811 if (!allowed) { 46812 modeling.removeConnection(connection); 46813 } 46814 }); 46815 }); 46816 }); 46817 }); 46818 46819 this.postExecute('shape.create', function(e) { 46820 var context = e.context, 46821 shape = context.shape, 46822 host = context.host; 46823 46824 if (host) { 46825 modeling.updateAttachment(shape, host); 46826 } 46827 }); 46828 46829 // update attachments if the host is replaced 46830 this.postExecute('shape.replace', function(e) { 46831 46832 var context = e.context, 46833 oldShape = context.oldShape, 46834 newShape = context.newShape; 46835 46836 // move the attachers to the new host 46837 saveClear(oldShape.attachers, function(attacher) { 46838 var allowed = rules.allowed('elements.move', { 46839 target: newShape, 46840 shapes: [attacher] 46841 }); 46842 46843 if (allowed === 'attach') { 46844 modeling.updateAttachment(attacher, newShape); 46845 } else { 46846 modeling.removeShape(attacher); 46847 } 46848 }); 46849 46850 // move attachers if new host has different size 46851 if (newShape.attachers.length) { 46852 46853 forEach(newShape.attachers, function(attacher) { 46854 var delta = getNewAttachShapeDelta(attacher, oldShape, newShape); 46855 modeling.moveShape(attacher, delta, attacher.parent); 46856 }); 46857 } 46858 46859 }); 46860 46861 // move shape on host resize 46862 this.postExecute('shape.resize', function(event) { 46863 var context = event.context, 46864 shape = context.shape, 46865 oldBounds = context.oldBounds, 46866 newBounds = context.newBounds, 46867 attachers = shape.attachers, 46868 hints = context.hints || {}; 46869 46870 if (hints.attachSupport === false) { 46871 return; 46872 } 46873 46874 forEach(attachers, function(attacher) { 46875 var delta = getNewAttachShapeDelta(attacher, oldBounds, newBounds); 46876 46877 modeling.moveShape(attacher, delta, attacher.parent); 46878 46879 forEach(attacher.labels, function(label) { 46880 modeling.moveShape(label, delta, label.parent); 46881 }); 46882 }); 46883 }); 46884 46885 // remove attachments 46886 this.preExecute('shape.delete', function(event) { 46887 46888 var shape = event.context.shape; 46889 46890 saveClear(shape.attachers, function(attacher) { 46891 modeling.removeShape(attacher); 46892 }); 46893 46894 if (shape.host) { 46895 modeling.updateAttachment(shape, null); 46896 } 46897 }); 46898 } 46899 46900 inherits$1(AttachSupport, CommandInterceptor); 46901 46902 AttachSupport.$inject = [ 46903 'injector', 46904 'eventBus', 46905 'canvas', 46906 'rules', 46907 'modeling' 46908 ]; 46909 46910 46911 /** 46912 * Return attachers of the given shapes 46913 * 46914 * @param {Array<djs.model.Base>} shapes 46915 * @return {Array<djs.model.Base>} 46916 */ 46917 function getAttachers(shapes) { 46918 return flatten(map$1(shapes, function(s) { 46919 return s.attachers || []; 46920 })); 46921 } 46922 46923 /** 46924 * Return a combined list of elements and 46925 * attachers. 46926 * 46927 * @param {Array<djs.model.Base>} elements 46928 * @return {Array<djs.model.Base>} filtered 46929 */ 46930 function addAttached(elements) { 46931 var attachers = getAttachers(elements); 46932 46933 return unionBy('id', elements, attachers); 46934 } 46935 46936 /** 46937 * Return a filtered list of elements that do not 46938 * contain attached elements with hosts being part 46939 * of the selection. 46940 * 46941 * @param {Array<djs.model.Base>} elements 46942 * 46943 * @return {Array<djs.model.Base>} filtered 46944 */ 46945 function removeAttached(elements) { 46946 46947 var ids = groupBy(elements, 'id'); 46948 46949 return filter(elements, function(element) { 46950 while (element) { 46951 46952 // host in selection 46953 if (element.host && ids[element.host.id]) { 46954 return false; 46955 } 46956 46957 element = element.parent; 46958 } 46959 46960 return true; 46961 }); 46962 } 46963 46964 function isAttacher(shape) { 46965 return !!shape.host; 46966 } 46967 46968 function includes$4(array, item) { 46969 return array.indexOf(item) !== -1; 46970 } 46971 46972 var AttachSupportModule = { 46973 __depends__: [ 46974 RulesModule$1 46975 ], 46976 __init__: [ 'attachSupport' ], 46977 attachSupport: [ 'type', AttachSupport ] 46978 }; 46979 46980 var LOW_PRIORITY$4 = 250; 46981 46982 /** 46983 * The tool manager acts as middle-man between the available tool's and the Palette, 46984 * it takes care of making sure that the correct active state is set. 46985 * 46986 * @param {Object} eventBus 46987 * @param {Object} dragging 46988 */ 46989 function ToolManager(eventBus, dragging) { 46990 this._eventBus = eventBus; 46991 this._dragging = dragging; 46992 46993 this._tools = []; 46994 this._active = null; 46995 } 46996 46997 ToolManager.$inject = [ 'eventBus', 'dragging' ]; 46998 46999 ToolManager.prototype.registerTool = function(name, events) { 47000 var tools = this._tools; 47001 47002 if (!events) { 47003 throw new Error('A tool has to be registered with it\'s "events"'); 47004 } 47005 47006 tools.push(name); 47007 47008 this.bindEvents(name, events); 47009 }; 47010 47011 ToolManager.prototype.isActive = function(tool) { 47012 return tool && this._active === tool; 47013 }; 47014 47015 ToolManager.prototype.length = function(tool) { 47016 return this._tools.length; 47017 }; 47018 47019 ToolManager.prototype.setActive = function(tool) { 47020 var eventBus = this._eventBus; 47021 47022 if (this._active !== tool) { 47023 this._active = tool; 47024 47025 eventBus.fire('tool-manager.update', { tool: tool }); 47026 } 47027 }; 47028 47029 ToolManager.prototype.bindEvents = function(name, events) { 47030 var eventBus = this._eventBus, 47031 dragging = this._dragging; 47032 47033 var eventsToRegister = []; 47034 47035 eventBus.on(events.tool + '.init', function(event) { 47036 var context = event.context; 47037 47038 // Active tools that want to reactivate themselves must do this explicitly 47039 if (!context.reactivate && this.isActive(name)) { 47040 this.setActive(null); 47041 47042 dragging.cancel(); 47043 return; 47044 } 47045 47046 this.setActive(name); 47047 47048 }, this); 47049 47050 // Todo[ricardo]: add test cases 47051 forEach(events, function(event) { 47052 eventsToRegister.push(event + '.ended'); 47053 eventsToRegister.push(event + '.canceled'); 47054 }); 47055 47056 eventBus.on(eventsToRegister, LOW_PRIORITY$4, function(event) { 47057 47058 // We defer the de-activation of the tool to the .activate phase, 47059 // so we're able to check if we want to toggle off the current 47060 // active tool or switch to a new one 47061 if (!this._active) { 47062 return; 47063 } 47064 47065 if (isPaletteClick(event)) { 47066 return; 47067 } 47068 47069 this.setActive(null); 47070 }, this); 47071 47072 }; 47073 47074 47075 // helpers /////////////// 47076 47077 /** 47078 * Check if a given event is a palette click event. 47079 * 47080 * @param {EventBus.Event} event 47081 * 47082 * @return {boolean} 47083 */ 47084 function isPaletteClick(event) { 47085 var target = event.originalEvent && event.originalEvent.target; 47086 47087 return target && closest(target, '.group[data-group="tools"]'); 47088 } 47089 47090 var ToolManagerModule = { 47091 __depends__: [ 47092 DraggingModule 47093 ], 47094 __init__: [ 'toolManager' ], 47095 toolManager: [ 'type', ToolManager ] 47096 }; 47097 47098 /** 47099 * Return direction given axis and delta. 47100 * 47101 * @param {string} axis 47102 * @param {number} delta 47103 * 47104 * @return {string} 47105 */ 47106 function getDirection(axis, delta) { 47107 47108 if (axis === 'x') { 47109 if (delta > 0) { 47110 return 'e'; 47111 } 47112 47113 if (delta < 0) { 47114 return 'w'; 47115 } 47116 } 47117 47118 if (axis === 'y') { 47119 if (delta > 0) { 47120 return 's'; 47121 } 47122 47123 if (delta < 0) { 47124 return 'n'; 47125 } 47126 } 47127 47128 return null; 47129 } 47130 47131 /** 47132 * Returns connections whose waypoints are to be updated. Waypoints are to be updated if start 47133 * or end is to be moved or resized. 47134 * 47135 * @param {Array<djs.model.Shape} movingShapes 47136 * @param {Array<djs.model.Shape} resizingShapes 47137 * 47138 * @returns {Array<djs.model.Connection>} 47139 */ 47140 function getWaypointsUpdatingConnections(movingShapes, resizingShapes) { 47141 var waypointsUpdatingConnections = []; 47142 47143 forEach(movingShapes.concat(resizingShapes), function(shape) { 47144 var incoming = shape.incoming, 47145 outgoing = shape.outgoing; 47146 47147 forEach(incoming.concat(outgoing), function(connection) { 47148 var source = connection.source, 47149 target = connection.target; 47150 47151 if (includes$3(movingShapes, source) || 47152 includes$3(movingShapes, target) || 47153 includes$3(resizingShapes, source) || 47154 includes$3(resizingShapes, target)) { 47155 47156 if (!includes$3(waypointsUpdatingConnections, connection)) { 47157 waypointsUpdatingConnections.push(connection); 47158 } 47159 } 47160 }); 47161 }); 47162 47163 return waypointsUpdatingConnections; 47164 } 47165 47166 function includes$3(array, item) { 47167 return array.indexOf(item) !== -1; 47168 } 47169 47170 /** 47171 * Resize bounds. 47172 * 47173 * @param {Object} bounds 47174 * @param {number} bounds.x 47175 * @param {number} bounds.y 47176 * @param {number} bounds.width 47177 * @param {number} bounds.height 47178 * @param {string} direction 47179 * @param {Object} delta 47180 * @param {number} delta.x 47181 * @param {number} delta.y 47182 * 47183 * @return {Object} 47184 */ 47185 function resizeBounds(bounds, direction, delta) { 47186 var x = bounds.x, 47187 y = bounds.y, 47188 width = bounds.width, 47189 height = bounds.height, 47190 dx = delta.x, 47191 dy = delta.y; 47192 47193 switch (direction) { 47194 case 'n': 47195 return { 47196 x: x, 47197 y: y + dy, 47198 width: width, 47199 height: height - dy 47200 }; 47201 case 's': 47202 return { 47203 x: x, 47204 y: y, 47205 width: width, 47206 height: height + dy 47207 }; 47208 case 'w': 47209 return { 47210 x: x + dx, 47211 y: y, 47212 width: width - dx, 47213 height: height 47214 }; 47215 case 'e': 47216 return { 47217 x: x, 47218 y: y, 47219 width: width + dx, 47220 height: height 47221 }; 47222 default: 47223 throw new Error('unknown direction: ' + direction); 47224 } 47225 } 47226 47227 var abs$1 = Math.abs, 47228 round$4 = Math.round; 47229 47230 var AXIS_TO_DIMENSION = { 47231 x: 'width', 47232 y: 'height' 47233 }; 47234 47235 var CURSOR_CROSSHAIR = 'crosshair'; 47236 47237 var DIRECTION_TO_TRBL = { 47238 n: 'top', 47239 w: 'left', 47240 s: 'bottom', 47241 e: 'right' 47242 }; 47243 47244 var HIGH_PRIORITY$3 = 1500; 47245 47246 var DIRECTION_TO_OPPOSITE = { 47247 n: 's', 47248 w: 'e', 47249 s: 'n', 47250 e: 'w' 47251 }; 47252 47253 var PADDING = 20; 47254 47255 47256 /** 47257 * Add or remove space by moving and resizing elements. 47258 * 47259 * @param {Canvas} canvas 47260 * @param {Dragging} dragging 47261 * @param {EventBus} eventBus 47262 * @param {Modeling} modeling 47263 * @param {Rules} rules 47264 * @param {ToolManager} toolManager 47265 * @param {Mouse} mouse 47266 */ 47267 function SpaceTool( 47268 canvas, dragging, eventBus, 47269 modeling, rules, toolManager, 47270 mouse) { 47271 47272 this._canvas = canvas; 47273 this._dragging = dragging; 47274 this._eventBus = eventBus; 47275 this._modeling = modeling; 47276 this._rules = rules; 47277 this._toolManager = toolManager; 47278 this._mouse = mouse; 47279 47280 var self = this; 47281 47282 toolManager.registerTool('space', { 47283 tool: 'spaceTool.selection', 47284 dragging: 'spaceTool' 47285 }); 47286 47287 eventBus.on('spaceTool.selection.end', function(event) { 47288 eventBus.once('spaceTool.selection.ended', function() { 47289 self.activateMakeSpace(event.originalEvent); 47290 }); 47291 }); 47292 47293 eventBus.on('spaceTool.move', HIGH_PRIORITY$3 , function(event) { 47294 var context = event.context, 47295 initialized = context.initialized; 47296 47297 if (!initialized) { 47298 initialized = context.initialized = self.init(event, context); 47299 } 47300 47301 if (initialized) { 47302 ensureConstraints(event); 47303 } 47304 }); 47305 47306 eventBus.on('spaceTool.end', function(event) { 47307 var context = event.context, 47308 axis = context.axis, 47309 direction = context.direction, 47310 movingShapes = context.movingShapes, 47311 resizingShapes = context.resizingShapes, 47312 start = context.start; 47313 47314 if (!context.initialized) { 47315 return; 47316 } 47317 47318 ensureConstraints(event); 47319 47320 var delta = { 47321 x: 0, 47322 y: 0 47323 }; 47324 47325 delta[ axis ] = round$4(event[ 'd' + axis ]); 47326 47327 self.makeSpace(movingShapes, resizingShapes, delta, direction, start); 47328 47329 eventBus.once('spaceTool.ended', function(event) { 47330 47331 // activate space tool selection after make space 47332 self.activateSelection(event.originalEvent, true, true); 47333 }); 47334 }); 47335 } 47336 47337 SpaceTool.$inject = [ 47338 'canvas', 47339 'dragging', 47340 'eventBus', 47341 'modeling', 47342 'rules', 47343 'toolManager', 47344 'mouse' 47345 ]; 47346 47347 /** 47348 * Activate space tool selection. 47349 * 47350 * @param {Object} event 47351 * @param {boolean} autoActivate 47352 */ 47353 SpaceTool.prototype.activateSelection = function(event, autoActivate, reactivate) { 47354 this._dragging.init(event, 'spaceTool.selection', { 47355 autoActivate: autoActivate, 47356 cursor: CURSOR_CROSSHAIR, 47357 data: { 47358 context: { 47359 reactivate: reactivate 47360 } 47361 }, 47362 trapClick: false 47363 }); 47364 }; 47365 47366 /** 47367 * Activate space tool make space. 47368 * 47369 * @param {MouseEvent} event 47370 */ 47371 SpaceTool.prototype.activateMakeSpace = function(event) { 47372 this._dragging.init(event, 'spaceTool', { 47373 autoActivate: true, 47374 cursor: CURSOR_CROSSHAIR, 47375 data: { 47376 context: {} 47377 } 47378 }); 47379 }; 47380 47381 /** 47382 * Make space. 47383 * 47384 * @param {Array<djs.model.Shape>} movingShapes 47385 * @param {Array<djs.model.Shape>} resizingShapes 47386 * @param {Object} delta 47387 * @param {number} delta.x 47388 * @param {number} delta.y 47389 * @param {string} direction 47390 * @param {number} start 47391 */ 47392 SpaceTool.prototype.makeSpace = function(movingShapes, resizingShapes, delta, direction, start) { 47393 return this._modeling.createSpace(movingShapes, resizingShapes, delta, direction, start); 47394 }; 47395 47396 /** 47397 * Initialize make space and return true if that was successful. 47398 * 47399 * @param {Object} event 47400 * @param {Object} context 47401 * 47402 * @return {boolean} 47403 */ 47404 SpaceTool.prototype.init = function(event, context) { 47405 var axis = abs$1(event.dx) > abs$1(event.dy) ? 'x' : 'y', 47406 delta = event[ 'd' + axis ], 47407 start = event[ axis ] - delta; 47408 47409 if (abs$1(delta) < 5) { 47410 return false; 47411 } 47412 47413 // invert delta to remove space when moving left 47414 if (delta < 0) { 47415 delta *= -1; 47416 } 47417 47418 // invert delta to add/remove space when removing/adding space if modifier key is pressed 47419 if (hasPrimaryModifier(event)) { 47420 delta *= -1; 47421 } 47422 47423 var direction = getDirection(axis, delta); 47424 47425 var root = this._canvas.getRootElement(); 47426 47427 var children = selfAndAllChildren(root, true); 47428 47429 var elements = this.calculateAdjustments(children, axis, delta, start); 47430 47431 var minDimensions = this._eventBus.fire('spaceTool.getMinDimensions', { 47432 axis: axis, 47433 direction: direction, 47434 shapes: elements.resizingShapes, 47435 start: start 47436 }); 47437 47438 var spaceToolConstraints = getSpaceToolConstraints(elements, axis, direction, start, minDimensions); 47439 47440 assign( 47441 context, 47442 elements, 47443 { 47444 axis: axis, 47445 direction: direction, 47446 spaceToolConstraints: spaceToolConstraints, 47447 start: start 47448 } 47449 ); 47450 47451 set('resize-' + (axis === 'x' ? 'ew' : 'ns')); 47452 47453 return true; 47454 }; 47455 47456 /** 47457 * Get elements to be moved and resized. 47458 * 47459 * @param {Array<djs.model.Shape>} elements 47460 * @param {string} axis 47461 * @param {number} delta 47462 * @param {number} start 47463 * 47464 * @return {Object} 47465 */ 47466 SpaceTool.prototype.calculateAdjustments = function(elements, axis, delta, start) { 47467 var rules = this._rules; 47468 47469 var movingShapes = [], 47470 resizingShapes = []; 47471 47472 forEach(elements, function(element) { 47473 if (!element.parent || isConnection$7(element)) { 47474 return; 47475 } 47476 47477 var shapeStart = element[ axis ], 47478 shapeEnd = shapeStart + element[ AXIS_TO_DIMENSION[ axis ] ]; 47479 47480 // shape to be moved 47481 if ((delta > 0 && shapeStart > start) || (delta < 0 && shapeEnd < start)) { 47482 return movingShapes.push(element); 47483 } 47484 47485 // shape to be resized 47486 if (shapeStart < start && 47487 shapeEnd > start && 47488 rules.allowed('shape.resize', { shape: element }) 47489 ) { 47490 47491 return resizingShapes.push(element); 47492 } 47493 }); 47494 47495 return { 47496 movingShapes: movingShapes, 47497 resizingShapes: resizingShapes 47498 }; 47499 }; 47500 47501 SpaceTool.prototype.toggle = function() { 47502 47503 if (this.isActive()) { 47504 return this._dragging.cancel(); 47505 } 47506 47507 var mouseEvent = this._mouse.getLastMoveEvent(); 47508 47509 this.activateSelection(mouseEvent, !!mouseEvent); 47510 }; 47511 47512 SpaceTool.prototype.isActive = function() { 47513 var context = this._dragging.context(); 47514 47515 return context && /^spaceTool/.test(context.prefix); 47516 }; 47517 47518 // helpers ////////// 47519 47520 function addPadding(trbl) { 47521 return { 47522 top: trbl.top - PADDING, 47523 right: trbl.right + PADDING, 47524 bottom: trbl.bottom + PADDING, 47525 left: trbl.left - PADDING 47526 }; 47527 } 47528 47529 function ensureConstraints(event) { 47530 var context = event.context, 47531 spaceToolConstraints = context.spaceToolConstraints; 47532 47533 if (!spaceToolConstraints) { 47534 return; 47535 } 47536 47537 var x, y; 47538 47539 if (isNumber(spaceToolConstraints.left)) { 47540 x = Math.max(event.x, spaceToolConstraints.left); 47541 47542 event.dx = event.dx + x - event.x; 47543 event.x = x; 47544 } 47545 47546 if (isNumber(spaceToolConstraints.right)) { 47547 x = Math.min(event.x, spaceToolConstraints.right); 47548 47549 event.dx = event.dx + x - event.x; 47550 event.x = x; 47551 } 47552 47553 if (isNumber(spaceToolConstraints.top)) { 47554 y = Math.max(event.y, spaceToolConstraints.top); 47555 47556 event.dy = event.dy + y - event.y; 47557 event.y = y; 47558 } 47559 47560 if (isNumber(spaceToolConstraints.bottom)) { 47561 y = Math.min(event.y, spaceToolConstraints.bottom); 47562 47563 event.dy = event.dy + y - event.y; 47564 event.y = y; 47565 } 47566 } 47567 47568 function getSpaceToolConstraints(elements, axis, direction, start, minDimensions) { 47569 var movingShapes = elements.movingShapes, 47570 resizingShapes = elements.resizingShapes; 47571 47572 if (!resizingShapes.length) { 47573 return; 47574 } 47575 47576 var spaceToolConstraints = {}, 47577 min, 47578 max; 47579 47580 forEach(resizingShapes, function(resizingShape) { 47581 var resizingShapeBBox = asTRBL(resizingShape); 47582 47583 // find children that are not moving or resizing 47584 var nonMovingResizingChildren = filter(resizingShape.children, function(child) { 47585 return !isConnection$7(child) && 47586 !isLabel$2(child) && 47587 !includes$2(movingShapes, child) && 47588 !includes$2(resizingShapes, child); 47589 }); 47590 47591 // find children that are moving 47592 var movingChildren = filter(resizingShape.children, function(child) { 47593 return !isConnection$7(child) && !isLabel$2(child) && includes$2(movingShapes, child); 47594 }); 47595 47596 var minOrMax, 47597 nonMovingResizingChildrenBBox, 47598 movingChildrenBBox; 47599 47600 if (nonMovingResizingChildren.length) { 47601 nonMovingResizingChildrenBBox = addPadding(asTRBL(getBBox(nonMovingResizingChildren))); 47602 47603 minOrMax = start - 47604 resizingShapeBBox[ DIRECTION_TO_TRBL[ direction ] ] + 47605 nonMovingResizingChildrenBBox[ DIRECTION_TO_TRBL[ direction ] ]; 47606 47607 if (direction === 'n') { 47608 spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; 47609 } else if (direction === 'w') { 47610 spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; 47611 } else if (direction === 's') { 47612 spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; 47613 } else if (direction === 'e') { 47614 spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; 47615 } 47616 } 47617 47618 if (movingChildren.length) { 47619 movingChildrenBBox = addPadding(asTRBL(getBBox(movingChildren))); 47620 47621 minOrMax = start - 47622 movingChildrenBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ] + 47623 resizingShapeBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ]; 47624 47625 if (direction === 'n') { 47626 spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; 47627 } else if (direction === 'w') { 47628 spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; 47629 } else if (direction === 's') { 47630 spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; 47631 } else if (direction === 'e') { 47632 spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; 47633 } 47634 } 47635 47636 var resizingShapeMinDimensions = minDimensions && minDimensions[ resizingShape.id ]; 47637 47638 if (resizingShapeMinDimensions) { 47639 if (direction === 'n') { 47640 minOrMax = start + 47641 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] - 47642 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ]; 47643 47644 spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; 47645 } else if (direction === 'w') { 47646 minOrMax = start + 47647 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] - 47648 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ]; 47649 47650 spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; 47651 } else if (direction === 's') { 47652 minOrMax = start - 47653 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] + 47654 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ]; 47655 47656 spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; 47657 } else if (direction === 'e') { 47658 minOrMax = start - 47659 resizingShape[ AXIS_TO_DIMENSION [ axis ] ] + 47660 resizingShapeMinDimensions[ AXIS_TO_DIMENSION[ axis ] ]; 47661 47662 spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; 47663 } 47664 } 47665 }); 47666 47667 return spaceToolConstraints; 47668 } 47669 47670 function includes$2(array, item) { 47671 return array.indexOf(item) !== -1; 47672 } 47673 47674 function isConnection$7(element) { 47675 return !!element.waypoints; 47676 } 47677 47678 function isLabel$2(element) { 47679 return !!element.labelTarget; 47680 } 47681 47682 var MARKER_DRAGGING$1 = 'djs-dragging', 47683 MARKER_RESIZING = 'djs-resizing'; 47684 47685 var LOW_PRIORITY$3 = 250; 47686 47687 var max = Math.max; 47688 47689 47690 /** 47691 * Provides previews for selecting/moving/resizing shapes when creating/removing space. 47692 * 47693 * @param {EventBus} eventBus 47694 * @param {ElementRegistry} elementRegistry 47695 * @param {Canvas} canvas 47696 * @param {Styles} styles 47697 */ 47698 function SpaceToolPreview( 47699 eventBus, elementRegistry, canvas, 47700 styles, previewSupport) { 47701 47702 function addPreviewGfx(collection, dragGroup) { 47703 forEach(collection, function(element) { 47704 previewSupport.addDragger(element, dragGroup); 47705 47706 canvas.addMarker(element, MARKER_DRAGGING$1); 47707 }); 47708 } 47709 47710 // add crosshair 47711 eventBus.on('spaceTool.selection.start', function(event) { 47712 var space = canvas.getLayer('space'), 47713 context = event.context; 47714 47715 var orientation = { 47716 x: 'M 0,-10000 L 0,10000', 47717 y: 'M -10000,0 L 10000,0' 47718 }; 47719 47720 var crosshairGroup = create$1('g'); 47721 attr(crosshairGroup, styles.cls('djs-crosshair-group', [ 'no-events' ])); 47722 47723 append(space, crosshairGroup); 47724 47725 // horizontal path 47726 var pathX = create$1('path'); 47727 attr(pathX, 'd', orientation.x); 47728 classes(pathX).add('djs-crosshair'); 47729 47730 append(crosshairGroup, pathX); 47731 47732 // vertical path 47733 var pathY = create$1('path'); 47734 attr(pathY, 'd', orientation.y); 47735 classes(pathY).add('djs-crosshair'); 47736 47737 append(crosshairGroup, pathY); 47738 47739 context.crosshairGroup = crosshairGroup; 47740 }); 47741 47742 // update crosshair 47743 eventBus.on('spaceTool.selection.move', function(event) { 47744 var crosshairGroup = event.context.crosshairGroup; 47745 47746 translate$2(crosshairGroup, event.x, event.y); 47747 }); 47748 47749 // remove crosshair 47750 eventBus.on('spaceTool.selection.cleanup', function(event) { 47751 var context = event.context, 47752 crosshairGroup = context.crosshairGroup; 47753 47754 if (crosshairGroup) { 47755 remove$1(crosshairGroup); 47756 } 47757 }); 47758 47759 // add and update move/resize previews 47760 eventBus.on('spaceTool.move', LOW_PRIORITY$3, function(event) { 47761 47762 var context = event.context, 47763 line = context.line, 47764 axis = context.axis, 47765 movingShapes = context.movingShapes, 47766 resizingShapes = context.resizingShapes; 47767 47768 if (!context.initialized) { 47769 return; 47770 } 47771 47772 if (!context.dragGroup) { 47773 var spaceLayer = canvas.getLayer('space'); 47774 47775 line = create$1('path'); 47776 attr(line, 'd', 'M0,0 L0,0'); 47777 classes(line).add('djs-crosshair'); 47778 47779 append(spaceLayer, line); 47780 47781 context.line = line; 47782 47783 var dragGroup = create$1('g'); 47784 attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ])); 47785 47786 append(canvas.getActiveLayer(), dragGroup); 47787 47788 // shapes 47789 addPreviewGfx(movingShapes, dragGroup); 47790 47791 // connections 47792 var movingConnections = context.movingConnections = elementRegistry.filter(function(element) { 47793 var sourceIsMoving = false; 47794 47795 forEach(movingShapes, function(shape) { 47796 forEach(shape.outgoing, function(connection) { 47797 if (element === connection) { 47798 sourceIsMoving = true; 47799 } 47800 }); 47801 }); 47802 47803 var targetIsMoving = false; 47804 47805 forEach(movingShapes, function(shape) { 47806 forEach(shape.incoming, function(connection) { 47807 if (element === connection) { 47808 targetIsMoving = true; 47809 } 47810 }); 47811 }); 47812 47813 var sourceIsResizing = false; 47814 47815 forEach(resizingShapes, function(shape) { 47816 forEach(shape.outgoing, function(connection) { 47817 if (element === connection) { 47818 sourceIsResizing = true; 47819 } 47820 }); 47821 }); 47822 47823 var targetIsResizing = false; 47824 47825 forEach(resizingShapes, function(shape) { 47826 forEach(shape.incoming, function(connection) { 47827 if (element === connection) { 47828 targetIsResizing = true; 47829 } 47830 }); 47831 }); 47832 47833 return isConnection$6(element) 47834 && (sourceIsMoving || sourceIsResizing) 47835 && (targetIsMoving || targetIsResizing); 47836 }); 47837 47838 47839 addPreviewGfx(movingConnections, dragGroup); 47840 47841 context.dragGroup = dragGroup; 47842 } 47843 47844 if (!context.frameGroup) { 47845 var frameGroup = create$1('g'); 47846 attr(frameGroup, styles.cls('djs-frame-group', [ 'no-events' ])); 47847 47848 append(canvas.getActiveLayer(), frameGroup); 47849 47850 var frames = []; 47851 47852 forEach(resizingShapes, function(shape) { 47853 var frame = previewSupport.addFrame(shape, frameGroup); 47854 47855 var initialBounds = frame.getBBox(); 47856 47857 frames.push({ 47858 element: frame, 47859 initialBounds: initialBounds 47860 }); 47861 47862 canvas.addMarker(shape, MARKER_RESIZING); 47863 }); 47864 47865 context.frameGroup = frameGroup; 47866 context.frames = frames; 47867 } 47868 47869 var orientation = { 47870 x: 'M' + event.x + ', -10000 L' + event.x + ', 10000', 47871 y: 'M -10000, ' + event.y + ' L 10000, ' + event.y 47872 }; 47873 47874 attr(line, { d: orientation[ axis ] }); 47875 47876 var opposite = { x: 'y', y: 'x' }; 47877 var delta = { x: event.dx, y: event.dy }; 47878 delta[ opposite[ context.axis ] ] = 0; 47879 47880 // update move previews 47881 translate$2(context.dragGroup, delta.x, delta.y); 47882 47883 // update resize previews 47884 forEach(context.frames, function(frame) { 47885 var element = frame.element, 47886 initialBounds = frame.initialBounds, 47887 width, 47888 height; 47889 47890 if (context.direction === 'e') { 47891 attr(element, { 47892 width: max(initialBounds.width + delta.x, 5) 47893 }); 47894 } else { 47895 width = max(initialBounds.width - delta.x, 5); 47896 47897 attr(element, { 47898 width: width, 47899 x: initialBounds.x + initialBounds.width - width 47900 }); 47901 } 47902 47903 if (context.direction === 's') { 47904 attr(element, { 47905 height: max(initialBounds.height + delta.y, 5) 47906 }); 47907 } else { 47908 height = max(initialBounds.height - delta.y, 5); 47909 47910 attr(element, { 47911 height: height, 47912 y: initialBounds.y + initialBounds.height - height 47913 }); 47914 } 47915 }); 47916 47917 }); 47918 47919 // remove move/resize previews 47920 eventBus.on('spaceTool.cleanup', function(event) { 47921 47922 var context = event.context, 47923 movingShapes = context.movingShapes, 47924 movingConnections = context.movingConnections, 47925 resizingShapes = context.resizingShapes, 47926 line = context.line, 47927 dragGroup = context.dragGroup, 47928 frameGroup = context.frameGroup; 47929 47930 // moving shapes 47931 forEach(movingShapes, function(shape) { 47932 canvas.removeMarker(shape, MARKER_DRAGGING$1); 47933 }); 47934 47935 // moving connections 47936 forEach(movingConnections, function(connection) { 47937 canvas.removeMarker(connection, MARKER_DRAGGING$1); 47938 }); 47939 47940 if (dragGroup) { 47941 remove$1(line); 47942 remove$1(dragGroup); 47943 } 47944 47945 forEach(resizingShapes, function(shape) { 47946 canvas.removeMarker(shape, MARKER_RESIZING); 47947 }); 47948 47949 if (frameGroup) { 47950 remove$1(frameGroup); 47951 } 47952 }); 47953 } 47954 47955 SpaceToolPreview.$inject = [ 47956 'eventBus', 47957 'elementRegistry', 47958 'canvas', 47959 'styles', 47960 'previewSupport' 47961 ]; 47962 47963 47964 // helpers ////////////////////// 47965 47966 /** 47967 * Checks if an element is a connection. 47968 */ 47969 function isConnection$6(element) { 47970 return element.waypoints; 47971 } 47972 47973 var SpaceToolModule = { 47974 __init__: ['spaceToolPreview'], 47975 __depends__: [ 47976 DraggingModule, 47977 RulesModule$1, 47978 ToolManagerModule, 47979 PreviewSupportModule, 47980 MouseModule 47981 ], 47982 spaceTool: ['type', SpaceTool ], 47983 spaceToolPreview: ['type', SpaceToolPreview ] 47984 }; 47985 47986 function BpmnFactory(moddle) { 47987 this._model = moddle; 47988 } 47989 47990 BpmnFactory.$inject = [ 'moddle' ]; 47991 47992 47993 BpmnFactory.prototype._needsId = function(element) { 47994 return isAny(element, [ 47995 'bpmn:RootElement', 47996 'bpmn:FlowElement', 47997 'bpmn:MessageFlow', 47998 'bpmn:DataAssociation', 47999 'bpmn:Artifact', 48000 'bpmn:Participant', 48001 'bpmn:Lane', 48002 'bpmn:LaneSet', 48003 'bpmn:Process', 48004 'bpmn:Collaboration', 48005 'bpmndi:BPMNShape', 48006 'bpmndi:BPMNEdge', 48007 'bpmndi:BPMNDiagram', 48008 'bpmndi:BPMNPlane', 48009 'bpmn:Property', 48010 'bpmn:CategoryValue' 48011 ]); 48012 }; 48013 48014 BpmnFactory.prototype._ensureId = function(element) { 48015 48016 // generate semantic ids for elements 48017 // bpmn:SequenceFlow -> SequenceFlow_ID 48018 var prefix; 48019 48020 if (is$1(element, 'bpmn:Activity')) { 48021 prefix = 'Activity'; 48022 } else if (is$1(element, 'bpmn:Event')) { 48023 prefix = 'Event'; 48024 } else if (is$1(element, 'bpmn:Gateway')) { 48025 prefix = 'Gateway'; 48026 } else if (isAny(element, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ])) { 48027 prefix = 'Flow'; 48028 } else { 48029 prefix = (element.$type || '').replace(/^[^:]*:/g, ''); 48030 } 48031 48032 prefix += '_'; 48033 48034 if (!element.id && this._needsId(element)) { 48035 element.id = this._model.ids.nextPrefixed(prefix, element); 48036 } 48037 }; 48038 48039 48040 BpmnFactory.prototype.create = function(type, attrs) { 48041 var element = this._model.create(type, attrs || {}); 48042 48043 this._ensureId(element); 48044 48045 return element; 48046 }; 48047 48048 48049 BpmnFactory.prototype.createDiLabel = function() { 48050 return this.create('bpmndi:BPMNLabel', { 48051 bounds: this.createDiBounds() 48052 }); 48053 }; 48054 48055 48056 BpmnFactory.prototype.createDiShape = function(semantic, bounds, attrs) { 48057 48058 return this.create('bpmndi:BPMNShape', assign({ 48059 bpmnElement: semantic, 48060 bounds: this.createDiBounds(bounds) 48061 }, attrs)); 48062 }; 48063 48064 48065 BpmnFactory.prototype.createDiBounds = function(bounds) { 48066 return this.create('dc:Bounds', bounds); 48067 }; 48068 48069 48070 BpmnFactory.prototype.createDiWaypoints = function(waypoints) { 48071 var self = this; 48072 48073 return map$1(waypoints, function(pos) { 48074 return self.createDiWaypoint(pos); 48075 }); 48076 }; 48077 48078 BpmnFactory.prototype.createDiWaypoint = function(point) { 48079 return this.create('dc:Point', pick(point, [ 'x', 'y' ])); 48080 }; 48081 48082 48083 BpmnFactory.prototype.createDiEdge = function(semantic, waypoints, attrs) { 48084 return this.create('bpmndi:BPMNEdge', assign({ 48085 bpmnElement: semantic 48086 }, attrs)); 48087 }; 48088 48089 BpmnFactory.prototype.createDiPlane = function(semantic) { 48090 return this.create('bpmndi:BPMNPlane', { 48091 bpmnElement: semantic 48092 }); 48093 }; 48094 48095 /** 48096 * A handler responsible for updating the underlying BPMN 2.0 XML + DI 48097 * once changes on the diagram happen 48098 */ 48099 function BpmnUpdater( 48100 eventBus, bpmnFactory, connectionDocking, 48101 translate) { 48102 48103 CommandInterceptor.call(this, eventBus); 48104 48105 this._bpmnFactory = bpmnFactory; 48106 this._translate = translate; 48107 48108 var self = this; 48109 48110 48111 48112 // connection cropping ////////////////////// 48113 48114 // crop connection ends during create/update 48115 function cropConnection(e) { 48116 var context = e.context, 48117 hints = context.hints || {}, 48118 connection; 48119 48120 if (!context.cropped && hints.createElementsBehavior !== false) { 48121 connection = context.connection; 48122 connection.waypoints = connectionDocking.getCroppedWaypoints(connection); 48123 context.cropped = true; 48124 } 48125 } 48126 48127 this.executed([ 48128 'connection.layout', 48129 'connection.create' 48130 ], cropConnection); 48131 48132 this.reverted([ 'connection.layout' ], function(e) { 48133 delete e.context.cropped; 48134 }); 48135 48136 48137 48138 // BPMN + DI update ////////////////////// 48139 48140 48141 // update parent 48142 function updateParent(e) { 48143 var context = e.context; 48144 48145 self.updateParent(context.shape || context.connection, context.oldParent); 48146 } 48147 48148 function reverseUpdateParent(e) { 48149 var context = e.context; 48150 48151 var element = context.shape || context.connection, 48152 48153 // oldParent is the (old) new parent, because we are undoing 48154 oldParent = context.parent || context.newParent; 48155 48156 self.updateParent(element, oldParent); 48157 } 48158 48159 this.executed([ 48160 'shape.move', 48161 'shape.create', 48162 'shape.delete', 48163 'connection.create', 48164 'connection.move', 48165 'connection.delete' 48166 ], ifBpmn(updateParent)); 48167 48168 this.reverted([ 48169 'shape.move', 48170 'shape.create', 48171 'shape.delete', 48172 'connection.create', 48173 'connection.move', 48174 'connection.delete' 48175 ], ifBpmn(reverseUpdateParent)); 48176 48177 /* 48178 * ## Updating Parent 48179 * 48180 * When morphing a Process into a Collaboration or vice-versa, 48181 * make sure that both the *semantic* and *di* parent of each element 48182 * is updated. 48183 * 48184 */ 48185 function updateRoot(event) { 48186 var context = event.context, 48187 oldRoot = context.oldRoot, 48188 children = oldRoot.children; 48189 48190 forEach(children, function(child) { 48191 if (is$1(child, 'bpmn:BaseElement')) { 48192 self.updateParent(child); 48193 } 48194 }); 48195 } 48196 48197 this.executed([ 'canvas.updateRoot' ], updateRoot); 48198 this.reverted([ 'canvas.updateRoot' ], updateRoot); 48199 48200 48201 // update bounds 48202 function updateBounds(e) { 48203 var shape = e.context.shape; 48204 48205 if (!is$1(shape, 'bpmn:BaseElement')) { 48206 return; 48207 } 48208 48209 self.updateBounds(shape); 48210 } 48211 48212 this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) { 48213 48214 // exclude labels because they're handled separately during shape.changed 48215 if (event.context.shape.type === 'label') { 48216 return; 48217 } 48218 48219 updateBounds(event); 48220 })); 48221 48222 this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) { 48223 48224 // exclude labels because they're handled separately during shape.changed 48225 if (event.context.shape.type === 'label') { 48226 return; 48227 } 48228 48229 updateBounds(event); 48230 })); 48231 48232 // Handle labels separately. This is necessary, because the label bounds have to be updated 48233 // every time its shape changes, not only on move, create and resize. 48234 eventBus.on('shape.changed', function(event) { 48235 if (event.element.type === 'label') { 48236 updateBounds({ context: { shape: event.element } }); 48237 } 48238 }); 48239 48240 // attach / detach connection 48241 function updateConnection(e) { 48242 self.updateConnection(e.context); 48243 } 48244 48245 this.executed([ 48246 'connection.create', 48247 'connection.move', 48248 'connection.delete', 48249 'connection.reconnect' 48250 ], ifBpmn(updateConnection)); 48251 48252 this.reverted([ 48253 'connection.create', 48254 'connection.move', 48255 'connection.delete', 48256 'connection.reconnect' 48257 ], ifBpmn(updateConnection)); 48258 48259 48260 // update waypoints 48261 function updateConnectionWaypoints(e) { 48262 self.updateConnectionWaypoints(e.context.connection); 48263 } 48264 48265 this.executed([ 48266 'connection.layout', 48267 'connection.move', 48268 'connection.updateWaypoints', 48269 ], ifBpmn(updateConnectionWaypoints)); 48270 48271 this.reverted([ 48272 'connection.layout', 48273 'connection.move', 48274 'connection.updateWaypoints', 48275 ], ifBpmn(updateConnectionWaypoints)); 48276 48277 // update conditional/default flows 48278 this.executed('connection.reconnect', ifBpmn(function(event) { 48279 var context = event.context, 48280 connection = context.connection, 48281 oldSource = context.oldSource, 48282 newSource = context.newSource, 48283 connectionBo = getBusinessObject(connection), 48284 oldSourceBo = getBusinessObject(oldSource), 48285 newSourceBo = getBusinessObject(newSource); 48286 48287 // remove condition from connection on reconnect to new source 48288 // if new source can NOT have condional sequence flow 48289 if (connectionBo.conditionExpression && !isAny(newSourceBo, [ 48290 'bpmn:Activity', 48291 'bpmn:ExclusiveGateway', 48292 'bpmn:InclusiveGateway' 48293 ])) { 48294 context.oldConditionExpression = connectionBo.conditionExpression; 48295 48296 delete connectionBo.conditionExpression; 48297 } 48298 48299 // remove default from old source flow on reconnect to new source 48300 // if source changed 48301 if (oldSource !== newSource && oldSourceBo.default === connectionBo) { 48302 context.oldDefault = oldSourceBo.default; 48303 48304 delete oldSourceBo.default; 48305 } 48306 })); 48307 48308 this.reverted('connection.reconnect', ifBpmn(function(event) { 48309 var context = event.context, 48310 connection = context.connection, 48311 oldSource = context.oldSource, 48312 newSource = context.newSource, 48313 connectionBo = getBusinessObject(connection), 48314 oldSourceBo = getBusinessObject(oldSource), 48315 newSourceBo = getBusinessObject(newSource); 48316 48317 // add condition to connection on revert reconnect to new source 48318 if (context.oldConditionExpression) { 48319 connectionBo.conditionExpression = context.oldConditionExpression; 48320 } 48321 48322 // add default to old source on revert reconnect to new source 48323 if (context.oldDefault) { 48324 oldSourceBo.default = context.oldDefault; 48325 48326 delete newSourceBo.default; 48327 } 48328 })); 48329 48330 // update attachments 48331 function updateAttachment(e) { 48332 self.updateAttachment(e.context); 48333 } 48334 48335 this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment)); 48336 this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment)); 48337 } 48338 48339 inherits$1(BpmnUpdater, CommandInterceptor); 48340 48341 BpmnUpdater.$inject = [ 48342 'eventBus', 48343 'bpmnFactory', 48344 'connectionDocking', 48345 'translate' 48346 ]; 48347 48348 48349 // implementation ////////////////////// 48350 48351 BpmnUpdater.prototype.updateAttachment = function(context) { 48352 48353 var shape = context.shape, 48354 businessObject = shape.businessObject, 48355 host = shape.host; 48356 48357 businessObject.attachedToRef = host && host.businessObject; 48358 }; 48359 48360 BpmnUpdater.prototype.updateParent = function(element, oldParent) { 48361 48362 // do not update BPMN 2.0 label parent 48363 if (element instanceof Label) { 48364 return; 48365 } 48366 48367 // data stores in collaborations are handled separately by DataStoreBehavior 48368 if (is$1(element, 'bpmn:DataStoreReference') && 48369 element.parent && 48370 is$1(element.parent, 'bpmn:Collaboration')) { 48371 return; 48372 } 48373 48374 var parentShape = element.parent; 48375 48376 var businessObject = element.businessObject, 48377 parentBusinessObject = parentShape && parentShape.businessObject, 48378 parentDi = parentBusinessObject && parentBusinessObject.di; 48379 48380 if (is$1(element, 'bpmn:FlowNode')) { 48381 this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject); 48382 } 48383 48384 if (is$1(element, 'bpmn:DataOutputAssociation')) { 48385 if (element.source) { 48386 parentBusinessObject = element.source.businessObject; 48387 } else { 48388 parentBusinessObject = null; 48389 } 48390 } 48391 48392 if (is$1(element, 'bpmn:DataInputAssociation')) { 48393 if (element.target) { 48394 parentBusinessObject = element.target.businessObject; 48395 } else { 48396 parentBusinessObject = null; 48397 } 48398 } 48399 48400 this.updateSemanticParent(businessObject, parentBusinessObject); 48401 48402 if (is$1(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) { 48403 this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject); 48404 } 48405 48406 this.updateDiParent(businessObject.di, parentDi); 48407 }; 48408 48409 48410 BpmnUpdater.prototype.updateBounds = function(shape) { 48411 48412 var di = shape.businessObject.di; 48413 48414 var target = (shape instanceof Label) ? this._getLabel(di) : di; 48415 48416 var bounds = target.bounds; 48417 48418 if (!bounds) { 48419 bounds = this._bpmnFactory.createDiBounds(); 48420 target.set('bounds', bounds); 48421 } 48422 48423 assign(bounds, { 48424 x: shape.x, 48425 y: shape.y, 48426 width: shape.width, 48427 height: shape.height 48428 }); 48429 }; 48430 48431 BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) { 48432 48433 if (oldContainment === newContainment) { 48434 return; 48435 } 48436 48437 var oldRefs, newRefs; 48438 48439 if (is$1 (oldContainment, 'bpmn:Lane')) { 48440 oldRefs = oldContainment.get('flowNodeRef'); 48441 remove(oldRefs, businessObject); 48442 } 48443 48444 if (is$1(newContainment, 'bpmn:Lane')) { 48445 newRefs = newContainment.get('flowNodeRef'); 48446 add(newRefs, businessObject); 48447 } 48448 }; 48449 48450 48451 // update existing sourceElement and targetElement di information 48452 BpmnUpdater.prototype.updateDiConnection = function(di, newSource, newTarget) { 48453 48454 if (di.sourceElement && di.sourceElement.bpmnElement !== newSource) { 48455 di.sourceElement = newSource && newSource.di; 48456 } 48457 48458 if (di.targetElement && di.targetElement.bpmnElement !== newTarget) { 48459 di.targetElement = newTarget && newTarget.di; 48460 } 48461 48462 }; 48463 48464 48465 BpmnUpdater.prototype.updateDiParent = function(di, parentDi) { 48466 48467 if (parentDi && !is$1(parentDi, 'bpmndi:BPMNPlane')) { 48468 parentDi = parentDi.$parent; 48469 } 48470 48471 if (di.$parent === parentDi) { 48472 return; 48473 } 48474 48475 var planeElements = (parentDi || di.$parent).get('planeElement'); 48476 48477 if (parentDi) { 48478 planeElements.push(di); 48479 di.$parent = parentDi; 48480 } else { 48481 remove(planeElements, di); 48482 di.$parent = null; 48483 } 48484 }; 48485 48486 function getDefinitions(element) { 48487 while (element && !is$1(element, 'bpmn:Definitions')) { 48488 element = element.$parent; 48489 } 48490 48491 return element; 48492 } 48493 48494 BpmnUpdater.prototype.getLaneSet = function(container) { 48495 48496 var laneSet, laneSets; 48497 48498 // bpmn:Lane 48499 if (is$1(container, 'bpmn:Lane')) { 48500 laneSet = container.childLaneSet; 48501 48502 if (!laneSet) { 48503 laneSet = this._bpmnFactory.create('bpmn:LaneSet'); 48504 container.childLaneSet = laneSet; 48505 laneSet.$parent = container; 48506 } 48507 48508 return laneSet; 48509 } 48510 48511 // bpmn:Participant 48512 if (is$1(container, 'bpmn:Participant')) { 48513 container = container.processRef; 48514 } 48515 48516 // bpmn:FlowElementsContainer 48517 laneSets = container.get('laneSets'); 48518 laneSet = laneSets[0]; 48519 48520 if (!laneSet) { 48521 laneSet = this._bpmnFactory.create('bpmn:LaneSet'); 48522 laneSet.$parent = container; 48523 laneSets.push(laneSet); 48524 } 48525 48526 return laneSet; 48527 }; 48528 48529 BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) { 48530 48531 var containment, 48532 translate = this._translate; 48533 48534 if (businessObject.$parent === newParent) { 48535 return; 48536 } 48537 48538 if (is$1(businessObject, 'bpmn:DataInput') || is$1(businessObject, 'bpmn:DataOutput')) { 48539 48540 if (is$1(newParent, 'bpmn:Participant') && 'processRef' in newParent) { 48541 newParent = newParent.processRef; 48542 } 48543 48544 // already in correct ioSpecification 48545 if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) { 48546 return; 48547 } 48548 } 48549 48550 if (is$1(businessObject, 'bpmn:Lane')) { 48551 48552 if (newParent) { 48553 newParent = this.getLaneSet(newParent); 48554 } 48555 48556 containment = 'lanes'; 48557 } else 48558 48559 if (is$1(businessObject, 'bpmn:FlowElement')) { 48560 48561 if (newParent) { 48562 48563 if (is$1(newParent, 'bpmn:Participant')) { 48564 newParent = newParent.processRef; 48565 } else 48566 48567 if (is$1(newParent, 'bpmn:Lane')) { 48568 do { 48569 48570 // unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer) 48571 newParent = newParent.$parent.$parent; 48572 } while (is$1(newParent, 'bpmn:Lane')); 48573 48574 } 48575 } 48576 48577 containment = 'flowElements'; 48578 48579 } else 48580 48581 if (is$1(businessObject, 'bpmn:Artifact')) { 48582 48583 while (newParent && 48584 !is$1(newParent, 'bpmn:Process') && 48585 !is$1(newParent, 'bpmn:SubProcess') && 48586 !is$1(newParent, 'bpmn:Collaboration')) { 48587 48588 if (is$1(newParent, 'bpmn:Participant')) { 48589 newParent = newParent.processRef; 48590 break; 48591 } else { 48592 newParent = newParent.$parent; 48593 } 48594 } 48595 48596 containment = 'artifacts'; 48597 } else 48598 48599 if (is$1(businessObject, 'bpmn:MessageFlow')) { 48600 containment = 'messageFlows'; 48601 48602 } else 48603 48604 if (is$1(businessObject, 'bpmn:Participant')) { 48605 containment = 'participants'; 48606 48607 // make sure the participants process is properly attached / detached 48608 // from the XML document 48609 48610 var process = businessObject.processRef, 48611 definitions; 48612 48613 if (process) { 48614 definitions = getDefinitions(businessObject.$parent || newParent); 48615 48616 if (businessObject.$parent) { 48617 remove(definitions.get('rootElements'), process); 48618 process.$parent = null; 48619 } 48620 48621 if (newParent) { 48622 add(definitions.get('rootElements'), process); 48623 process.$parent = definitions; 48624 } 48625 } 48626 } else 48627 48628 if (is$1(businessObject, 'bpmn:DataOutputAssociation')) { 48629 containment = 'dataOutputAssociations'; 48630 } else 48631 48632 if (is$1(businessObject, 'bpmn:DataInputAssociation')) { 48633 containment = 'dataInputAssociations'; 48634 } 48635 48636 if (!containment) { 48637 throw new Error(translate( 48638 'no parent for {element} in {parent}', 48639 { 48640 element: businessObject.id, 48641 parent: newParent.id 48642 } 48643 )); 48644 } 48645 48646 var children; 48647 48648 if (businessObject.$parent) { 48649 48650 // remove from old parent 48651 children = businessObject.$parent.get(containment); 48652 remove(children, businessObject); 48653 } 48654 48655 if (!newParent) { 48656 businessObject.$parent = null; 48657 } else { 48658 48659 // add to new parent 48660 children = newParent.get(containment); 48661 children.push(businessObject); 48662 businessObject.$parent = newParent; 48663 } 48664 48665 if (visualParent) { 48666 var diChildren = visualParent.get(containment); 48667 48668 remove(children, businessObject); 48669 48670 if (newParent) { 48671 48672 if (!diChildren) { 48673 diChildren = []; 48674 newParent.set(containment, diChildren); 48675 } 48676 48677 diChildren.push(businessObject); 48678 } 48679 } 48680 }; 48681 48682 48683 BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) { 48684 connection.businessObject.di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints)); 48685 }; 48686 48687 48688 BpmnUpdater.prototype.updateConnection = function(context) { 48689 48690 var connection = context.connection, 48691 businessObject = getBusinessObject(connection), 48692 newSource = getBusinessObject(connection.source), 48693 newTarget = getBusinessObject(connection.target), 48694 visualParent; 48695 48696 if (!is$1(businessObject, 'bpmn:DataAssociation')) { 48697 48698 var inverseSet = is$1(businessObject, 'bpmn:SequenceFlow'); 48699 48700 if (businessObject.sourceRef !== newSource) { 48701 if (inverseSet) { 48702 remove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject); 48703 48704 if (newSource && newSource.get('outgoing')) { 48705 newSource.get('outgoing').push(businessObject); 48706 } 48707 } 48708 48709 businessObject.sourceRef = newSource; 48710 } 48711 48712 if (businessObject.targetRef !== newTarget) { 48713 if (inverseSet) { 48714 remove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject); 48715 48716 if (newTarget && newTarget.get('incoming')) { 48717 newTarget.get('incoming').push(businessObject); 48718 } 48719 } 48720 48721 businessObject.targetRef = newTarget; 48722 } 48723 } else 48724 48725 if (is$1(businessObject, 'bpmn:DataInputAssociation')) { 48726 48727 // handle obnoxious isMsome sourceRef 48728 businessObject.get('sourceRef')[0] = newSource; 48729 48730 visualParent = context.parent || context.newParent || newTarget; 48731 48732 this.updateSemanticParent(businessObject, newTarget, visualParent); 48733 } else 48734 48735 if (is$1(businessObject, 'bpmn:DataOutputAssociation')) { 48736 visualParent = context.parent || context.newParent || newSource; 48737 48738 this.updateSemanticParent(businessObject, newSource, visualParent); 48739 48740 // targetRef = new target 48741 businessObject.targetRef = newTarget; 48742 } 48743 48744 this.updateConnectionWaypoints(connection); 48745 48746 this.updateDiConnection(businessObject.di, newSource, newTarget); 48747 }; 48748 48749 48750 // helpers ////////////////////// 48751 48752 BpmnUpdater.prototype._getLabel = function(di) { 48753 if (!di.label) { 48754 di.label = this._bpmnFactory.createDiLabel(); 48755 } 48756 48757 return di.label; 48758 }; 48759 48760 48761 /** 48762 * Make sure the event listener is only called 48763 * if the touched element is a BPMN element. 48764 * 48765 * @param {Function} fn 48766 * @return {Function} guarded function 48767 */ 48768 function ifBpmn(fn) { 48769 48770 return function(event) { 48771 48772 var context = event.context, 48773 element = context.shape || context.connection; 48774 48775 if (is$1(element, 'bpmn:BaseElement')) { 48776 fn(event); 48777 } 48778 }; 48779 } 48780 48781 /** 48782 * A bpmn-aware factory for diagram-js shapes 48783 */ 48784 function ElementFactory(bpmnFactory, moddle, translate) { 48785 ElementFactory$1.call(this); 48786 48787 this._bpmnFactory = bpmnFactory; 48788 this._moddle = moddle; 48789 this._translate = translate; 48790 } 48791 48792 inherits$1(ElementFactory, ElementFactory$1); 48793 48794 ElementFactory.$inject = [ 48795 'bpmnFactory', 48796 'moddle', 48797 'translate' 48798 ]; 48799 48800 ElementFactory.prototype.baseCreate = ElementFactory$1.prototype.create; 48801 48802 ElementFactory.prototype.create = function(elementType, attrs) { 48803 48804 // no special magic for labels, 48805 // we assume their businessObjects have already been created 48806 // and wired via attrs 48807 if (elementType === 'label') { 48808 return this.baseCreate(elementType, assign({ type: 'label' }, DEFAULT_LABEL_SIZE, attrs)); 48809 } 48810 48811 return this.createBpmnElement(elementType, attrs); 48812 }; 48813 48814 ElementFactory.prototype.createBpmnElement = function(elementType, attrs) { 48815 var size, 48816 translate = this._translate; 48817 48818 attrs = attrs || {}; 48819 48820 var businessObject = attrs.businessObject; 48821 48822 if (!businessObject) { 48823 if (!attrs.type) { 48824 throw new Error(translate('no shape type specified')); 48825 } 48826 48827 businessObject = this._bpmnFactory.create(attrs.type); 48828 } 48829 48830 if (!businessObject.di) { 48831 if (elementType === 'root') { 48832 businessObject.di = this._bpmnFactory.createDiPlane(businessObject, [], { 48833 id: businessObject.id + '_di' 48834 }); 48835 } else 48836 if (elementType === 'connection') { 48837 businessObject.di = this._bpmnFactory.createDiEdge(businessObject, [], { 48838 id: businessObject.id + '_di' 48839 }); 48840 } else { 48841 businessObject.di = this._bpmnFactory.createDiShape(businessObject, {}, { 48842 id: businessObject.id + '_di' 48843 }); 48844 } 48845 } 48846 48847 if (is$1(businessObject, 'bpmn:Group')) { 48848 attrs = assign({ 48849 isFrame: true 48850 }, attrs); 48851 } 48852 48853 if (attrs.di) { 48854 assign(businessObject.di, attrs.di); 48855 48856 delete attrs.di; 48857 } 48858 48859 applyAttributes(businessObject, attrs, [ 48860 'processRef', 48861 'isInterrupting', 48862 'associationDirection', 48863 'isForCompensation' 48864 ]); 48865 48866 if (attrs.isExpanded) { 48867 applyAttribute(businessObject.di, attrs, 'isExpanded'); 48868 } 48869 48870 if (is$1(businessObject, 'bpmn:ExclusiveGateway')) { 48871 businessObject.di.isMarkerVisible = true; 48872 } 48873 48874 var eventDefinitions, 48875 newEventDefinition; 48876 48877 if (attrs.eventDefinitionType) { 48878 eventDefinitions = businessObject.get('eventDefinitions') || []; 48879 newEventDefinition = this._bpmnFactory.create(attrs.eventDefinitionType, attrs.eventDefinitionAttrs); 48880 48881 if (attrs.eventDefinitionType === 'bpmn:ConditionalEventDefinition') { 48882 newEventDefinition.condition = this._bpmnFactory.create('bpmn:FormalExpression'); 48883 } 48884 48885 eventDefinitions.push(newEventDefinition); 48886 48887 newEventDefinition.$parent = businessObject; 48888 businessObject.eventDefinitions = eventDefinitions; 48889 48890 delete attrs.eventDefinitionType; 48891 } 48892 48893 size = this._getDefaultSize(businessObject); 48894 48895 attrs = assign({ 48896 businessObject: businessObject, 48897 id: businessObject.id 48898 }, size, attrs); 48899 48900 return this.baseCreate(elementType, attrs); 48901 }; 48902 48903 48904 ElementFactory.prototype._getDefaultSize = function(semantic) { 48905 48906 if (is$1(semantic, 'bpmn:SubProcess')) { 48907 48908 if (isExpanded(semantic)) { 48909 return { width: 350, height: 200 }; 48910 } else { 48911 return { width: 100, height: 80 }; 48912 } 48913 } 48914 48915 if (is$1(semantic, 'bpmn:Task')) { 48916 return { width: 100, height: 80 }; 48917 } 48918 48919 if (is$1(semantic, 'bpmn:Gateway')) { 48920 return { width: 50, height: 50 }; 48921 } 48922 48923 if (is$1(semantic, 'bpmn:Event')) { 48924 return { width: 36, height: 36 }; 48925 } 48926 48927 if (is$1(semantic, 'bpmn:Participant')) { 48928 if (isExpanded(semantic)) { 48929 return { width: 600, height: 250 }; 48930 } else { 48931 return { width: 400, height: 60 }; 48932 } 48933 } 48934 48935 if (is$1(semantic, 'bpmn:Lane')) { 48936 return { width: 400, height: 100 }; 48937 } 48938 48939 if (is$1(semantic, 'bpmn:DataObjectReference')) { 48940 return { width: 36, height: 50 }; 48941 } 48942 48943 if (is$1(semantic, 'bpmn:DataStoreReference')) { 48944 return { width: 50, height: 50 }; 48945 } 48946 48947 if (is$1(semantic, 'bpmn:TextAnnotation')) { 48948 return { width: 100, height: 30 }; 48949 } 48950 48951 if (is$1(semantic, 'bpmn:Group')) { 48952 return { width: 300, height: 300 }; 48953 } 48954 48955 return { width: 100, height: 80 }; 48956 }; 48957 48958 48959 /** 48960 * Create participant. 48961 * 48962 * @param {boolean|Object} [attrs] attrs 48963 * 48964 * @returns {djs.model.Shape} 48965 */ 48966 ElementFactory.prototype.createParticipantShape = function(attrs) { 48967 48968 if (!isObject(attrs)) { 48969 attrs = { isExpanded: attrs }; 48970 } 48971 48972 attrs = assign({ type: 'bpmn:Participant' }, attrs || {}); 48973 48974 // participants are expanded by default 48975 if (attrs.isExpanded !== false) { 48976 attrs.processRef = this._bpmnFactory.create('bpmn:Process'); 48977 } 48978 48979 return this.createShape(attrs); 48980 }; 48981 48982 48983 // helpers ////////////////////// 48984 48985 /** 48986 * Apply attributes from a map to the given element, 48987 * remove attribute from the map on application. 48988 * 48989 * @param {Base} element 48990 * @param {Object} attrs (in/out map of attributes) 48991 * @param {Array<string>} attributeNames name of attributes to apply 48992 */ 48993 function applyAttributes(element, attrs, attributeNames) { 48994 48995 forEach(attributeNames, function(property) { 48996 if (attrs[property] !== undefined) { 48997 applyAttribute(element, attrs, property); 48998 } 48999 }); 49000 } 49001 49002 /** 49003 * Apply named property to element and drain it from the attrs 49004 * collection. 49005 * 49006 * @param {Base} element 49007 * @param {Object} attrs (in/out map of attributes) 49008 * @param {string} attributeName to apply 49009 */ 49010 function applyAttribute(element, attrs, attributeName) { 49011 element[attributeName] = attrs[attributeName]; 49012 49013 delete attrs[attributeName]; 49014 } 49015 49016 /** 49017 * A handler that align elements in a certain way. 49018 * 49019 */ 49020 function AlignElements(modeling, canvas) { 49021 this._modeling = modeling; 49022 this._canvas = canvas; 49023 } 49024 49025 AlignElements.$inject = [ 'modeling', 'canvas' ]; 49026 49027 49028 AlignElements.prototype.preExecute = function(context) { 49029 var modeling = this._modeling; 49030 49031 var elements = context.elements, 49032 alignment = context.alignment; 49033 49034 49035 forEach(elements, function(element) { 49036 var delta = { 49037 x: 0, 49038 y: 0 49039 }; 49040 49041 if (alignment.left) { 49042 delta.x = alignment.left - element.x; 49043 49044 } else if (alignment.right) { 49045 delta.x = (alignment.right - element.width) - element.x; 49046 49047 } else if (alignment.center) { 49048 delta.x = (alignment.center - Math.round(element.width / 2)) - element.x; 49049 49050 } else if (alignment.top) { 49051 delta.y = alignment.top - element.y; 49052 49053 } else if (alignment.bottom) { 49054 delta.y = (alignment.bottom - element.height) - element.y; 49055 49056 } else if (alignment.middle) { 49057 delta.y = (alignment.middle - Math.round(element.height / 2)) - element.y; 49058 } 49059 49060 modeling.moveElements([ element ], delta, element.parent); 49061 }); 49062 }; 49063 49064 AlignElements.prototype.postExecute = function(context) { 49065 49066 }; 49067 49068 /** 49069 * A handler that implements reversible appending of shapes 49070 * to a source shape. 49071 * 49072 * @param {canvas} Canvas 49073 * @param {elementFactory} ElementFactory 49074 * @param {modeling} Modeling 49075 */ 49076 function AppendShapeHandler(modeling) { 49077 this._modeling = modeling; 49078 } 49079 49080 AppendShapeHandler.$inject = [ 'modeling' ]; 49081 49082 49083 // api ////////////////////// 49084 49085 49086 /** 49087 * Creates a new shape 49088 * 49089 * @param {Object} context 49090 * @param {ElementDescriptor} context.shape the new shape 49091 * @param {ElementDescriptor} context.source the source object 49092 * @param {ElementDescriptor} context.parent the parent object 49093 * @param {Point} context.position position of the new element 49094 */ 49095 AppendShapeHandler.prototype.preExecute = function(context) { 49096 49097 var source = context.source; 49098 49099 if (!source) { 49100 throw new Error('source required'); 49101 } 49102 49103 var target = context.target || source.parent, 49104 shape = context.shape, 49105 hints = context.hints || {}; 49106 49107 shape = context.shape = 49108 this._modeling.createShape( 49109 shape, 49110 context.position, 49111 target, { attach: hints.attach }); 49112 49113 context.shape = shape; 49114 }; 49115 49116 AppendShapeHandler.prototype.postExecute = function(context) { 49117 var hints = context.hints || {}; 49118 49119 if (!existsConnection(context.source, context.shape)) { 49120 49121 // create connection 49122 if (hints.connectionTarget === context.source) { 49123 this._modeling.connect(context.shape, context.source, context.connection); 49124 } else { 49125 this._modeling.connect(context.source, context.shape, context.connection); 49126 } 49127 } 49128 }; 49129 49130 49131 function existsConnection(source, target) { 49132 return some(source.outgoing, function(c) { 49133 return c.target === target; 49134 }); 49135 } 49136 49137 function CreateConnectionHandler(canvas, layouter) { 49138 this._canvas = canvas; 49139 this._layouter = layouter; 49140 } 49141 49142 CreateConnectionHandler.$inject = [ 'canvas', 'layouter' ]; 49143 49144 49145 // api ////////////////////// 49146 49147 49148 /** 49149 * Appends a shape to a target shape 49150 * 49151 * @param {Object} context 49152 * @param {djs.element.Base} context.source the source object 49153 * @param {djs.element.Base} context.target the parent object 49154 * @param {Point} context.position position of the new element 49155 */ 49156 CreateConnectionHandler.prototype.execute = function(context) { 49157 49158 var connection = context.connection, 49159 source = context.source, 49160 target = context.target, 49161 parent = context.parent, 49162 parentIndex = context.parentIndex, 49163 hints = context.hints; 49164 49165 if (!source || !target) { 49166 throw new Error('source and target required'); 49167 } 49168 49169 if (!parent) { 49170 throw new Error('parent required'); 49171 } 49172 49173 connection.source = source; 49174 connection.target = target; 49175 49176 if (!connection.waypoints) { 49177 connection.waypoints = this._layouter.layoutConnection(connection, hints); 49178 } 49179 49180 // add connection 49181 this._canvas.addConnection(connection, parent, parentIndex); 49182 49183 return connection; 49184 }; 49185 49186 CreateConnectionHandler.prototype.revert = function(context) { 49187 var connection = context.connection; 49188 49189 this._canvas.removeConnection(connection); 49190 49191 connection.source = null; 49192 connection.target = null; 49193 49194 return connection; 49195 }; 49196 49197 var round$3 = Math.round; 49198 49199 function CreateElementsHandler(modeling) { 49200 this._modeling = modeling; 49201 } 49202 49203 CreateElementsHandler.$inject = [ 49204 'modeling' 49205 ]; 49206 49207 CreateElementsHandler.prototype.preExecute = function(context) { 49208 var elements = context.elements, 49209 parent = context.parent, 49210 parentIndex = context.parentIndex, 49211 position = context.position, 49212 hints = context.hints; 49213 49214 var modeling = this._modeling; 49215 49216 // make sure each element has x and y 49217 forEach(elements, function(element) { 49218 if (!isNumber(element.x)) { 49219 element.x = 0; 49220 } 49221 49222 if (!isNumber(element.y)) { 49223 element.y = 0; 49224 } 49225 }); 49226 49227 var bbox = getBBox(elements); 49228 49229 // center elements around position 49230 forEach(elements, function(element) { 49231 if (isConnection$5(element)) { 49232 element.waypoints = map$1(element.waypoints, function(waypoint) { 49233 return { 49234 x: round$3(waypoint.x - bbox.x - bbox.width / 2 + position.x), 49235 y: round$3(waypoint.y - bbox.y - bbox.height / 2 + position.y) 49236 }; 49237 }); 49238 } 49239 49240 assign(element, { 49241 x: round$3(element.x - bbox.x - bbox.width / 2 + position.x), 49242 y: round$3(element.y - bbox.y - bbox.height / 2 + position.y) 49243 }); 49244 }); 49245 49246 var parents = getParents$1(elements); 49247 49248 var cache = {}; 49249 49250 forEach(elements, function(element) { 49251 if (isConnection$5(element)) { 49252 cache[ element.id ] = isNumber(parentIndex) ? 49253 modeling.createConnection( 49254 cache[ element.source.id ], 49255 cache[ element.target.id ], 49256 parentIndex, 49257 element, 49258 element.parent || parent, 49259 hints 49260 ) : 49261 modeling.createConnection( 49262 cache[ element.source.id ], 49263 cache[ element.target.id ], 49264 element, 49265 element.parent || parent, 49266 hints 49267 ); 49268 49269 return; 49270 } 49271 49272 var createShapeHints = assign({}, hints); 49273 49274 if (parents.indexOf(element) === -1) { 49275 createShapeHints.autoResize = false; 49276 } 49277 49278 cache[ element.id ] = isNumber(parentIndex) ? 49279 modeling.createShape( 49280 element, 49281 pick(element, [ 'x', 'y', 'width', 'height' ]), 49282 element.parent || parent, 49283 parentIndex, 49284 createShapeHints 49285 ) : 49286 modeling.createShape( 49287 element, 49288 pick(element, [ 'x', 'y', 'width', 'height' ]), 49289 element.parent || parent, 49290 createShapeHints 49291 ); 49292 }); 49293 49294 context.elements = values(cache); 49295 }; 49296 49297 // helpers ////////// 49298 49299 function isConnection$5(element) { 49300 return !!element.waypoints; 49301 } 49302 49303 var round$2 = Math.round; 49304 49305 49306 /** 49307 * A handler that implements reversible addition of shapes. 49308 * 49309 * @param {canvas} Canvas 49310 */ 49311 function CreateShapeHandler(canvas) { 49312 this._canvas = canvas; 49313 } 49314 49315 CreateShapeHandler.$inject = [ 'canvas' ]; 49316 49317 49318 // api ////////////////////// 49319 49320 49321 /** 49322 * Appends a shape to a target shape 49323 * 49324 * @param {Object} context 49325 * @param {djs.model.Base} context.parent the parent object 49326 * @param {Point} context.position position of the new element 49327 */ 49328 CreateShapeHandler.prototype.execute = function(context) { 49329 49330 var shape = context.shape, 49331 positionOrBounds = context.position, 49332 parent = context.parent, 49333 parentIndex = context.parentIndex; 49334 49335 if (!parent) { 49336 throw new Error('parent required'); 49337 } 49338 49339 if (!positionOrBounds) { 49340 throw new Error('position required'); 49341 } 49342 49343 // (1) add at event center position _or_ at given bounds 49344 if (positionOrBounds.width !== undefined) { 49345 assign(shape, positionOrBounds); 49346 } else { 49347 assign(shape, { 49348 x: positionOrBounds.x - round$2(shape.width / 2), 49349 y: positionOrBounds.y - round$2(shape.height / 2) 49350 }); 49351 } 49352 49353 // (2) add to canvas 49354 this._canvas.addShape(shape, parent, parentIndex); 49355 49356 return shape; 49357 }; 49358 49359 49360 /** 49361 * Undo append by removing the shape 49362 */ 49363 CreateShapeHandler.prototype.revert = function(context) { 49364 49365 var shape = context.shape; 49366 49367 // (3) remove form canvas 49368 this._canvas.removeShape(shape); 49369 49370 return shape; 49371 }; 49372 49373 /** 49374 * A handler that attaches a label to a given target shape. 49375 * 49376 * @param {Canvas} canvas 49377 */ 49378 function CreateLabelHandler(canvas) { 49379 CreateShapeHandler.call(this, canvas); 49380 } 49381 49382 inherits$1(CreateLabelHandler, CreateShapeHandler); 49383 49384 CreateLabelHandler.$inject = [ 'canvas' ]; 49385 49386 49387 // api ////////////////////// 49388 49389 49390 var originalExecute = CreateShapeHandler.prototype.execute; 49391 49392 /** 49393 * Appends a label to a target shape. 49394 * 49395 * @method CreateLabelHandler#execute 49396 * 49397 * @param {Object} context 49398 * @param {ElementDescriptor} context.target the element the label is attached to 49399 * @param {ElementDescriptor} context.parent the parent object 49400 * @param {Point} context.position position of the new element 49401 */ 49402 CreateLabelHandler.prototype.execute = function(context) { 49403 49404 var label = context.shape; 49405 49406 ensureValidDimensions(label); 49407 49408 label.labelTarget = context.labelTarget; 49409 49410 return originalExecute.call(this, context); 49411 }; 49412 49413 var originalRevert = CreateShapeHandler.prototype.revert; 49414 49415 /** 49416 * Undo append by removing the shape 49417 */ 49418 CreateLabelHandler.prototype.revert = function(context) { 49419 context.shape.labelTarget = null; 49420 49421 return originalRevert.call(this, context); 49422 }; 49423 49424 49425 // helpers ////////////////////// 49426 49427 function ensureValidDimensions(label) { 49428 49429 // make sure a label has valid { width, height } dimensions 49430 [ 'width', 'height' ].forEach(function(prop) { 49431 if (typeof label[prop] === 'undefined') { 49432 label[prop] = 0; 49433 } 49434 }); 49435 } 49436 49437 /** 49438 * A handler that implements reversible deletion of Connections. 49439 */ 49440 function DeleteConnectionHandler(canvas, modeling) { 49441 this._canvas = canvas; 49442 this._modeling = modeling; 49443 } 49444 49445 DeleteConnectionHandler.$inject = [ 49446 'canvas', 49447 'modeling' 49448 ]; 49449 49450 49451 DeleteConnectionHandler.prototype.execute = function(context) { 49452 49453 var connection = context.connection, 49454 parent = connection.parent; 49455 49456 context.parent = parent; 49457 49458 // remember containment 49459 context.parentIndex = indexOf(parent.children, connection); 49460 49461 context.source = connection.source; 49462 context.target = connection.target; 49463 49464 this._canvas.removeConnection(connection); 49465 49466 connection.source = null; 49467 connection.target = null; 49468 49469 return connection; 49470 }; 49471 49472 /** 49473 * Command revert implementation. 49474 */ 49475 DeleteConnectionHandler.prototype.revert = function(context) { 49476 49477 var connection = context.connection, 49478 parent = context.parent, 49479 parentIndex = context.parentIndex; 49480 49481 connection.source = context.source; 49482 connection.target = context.target; 49483 49484 // restore containment 49485 add(parent.children, connection, parentIndex); 49486 49487 this._canvas.addConnection(connection, parent); 49488 49489 return connection; 49490 }; 49491 49492 function DeleteElementsHandler(modeling, elementRegistry) { 49493 this._modeling = modeling; 49494 this._elementRegistry = elementRegistry; 49495 } 49496 49497 DeleteElementsHandler.$inject = [ 49498 'modeling', 49499 'elementRegistry' 49500 ]; 49501 49502 49503 DeleteElementsHandler.prototype.postExecute = function(context) { 49504 49505 var modeling = this._modeling, 49506 elementRegistry = this._elementRegistry, 49507 elements = context.elements; 49508 49509 forEach(elements, function(element) { 49510 49511 // element may have been removed with previous 49512 // remove operations already (e.g. in case of nesting) 49513 if (!elementRegistry.get(element.id)) { 49514 return; 49515 } 49516 49517 if (element.waypoints) { 49518 modeling.removeConnection(element); 49519 } else { 49520 modeling.removeShape(element); 49521 } 49522 }); 49523 }; 49524 49525 /** 49526 * A handler that implements reversible deletion of shapes. 49527 * 49528 */ 49529 function DeleteShapeHandler(canvas, modeling) { 49530 this._canvas = canvas; 49531 this._modeling = modeling; 49532 } 49533 49534 DeleteShapeHandler.$inject = [ 'canvas', 'modeling' ]; 49535 49536 49537 /** 49538 * - Remove connections 49539 * - Remove all direct children 49540 */ 49541 DeleteShapeHandler.prototype.preExecute = function(context) { 49542 49543 var modeling = this._modeling; 49544 49545 var shape = context.shape; 49546 49547 // remove connections 49548 saveClear(shape.incoming, function(connection) { 49549 49550 // To make sure that the connection isn't removed twice 49551 // For example if a container is removed 49552 modeling.removeConnection(connection, { nested: true }); 49553 }); 49554 49555 saveClear(shape.outgoing, function(connection) { 49556 modeling.removeConnection(connection, { nested: true }); 49557 }); 49558 49559 // remove child shapes and connections 49560 saveClear(shape.children, function(child) { 49561 if (isConnection$4(child)) { 49562 modeling.removeConnection(child, { nested: true }); 49563 } else { 49564 modeling.removeShape(child, { nested: true }); 49565 } 49566 }); 49567 }; 49568 49569 /** 49570 * Remove shape and remember the parent 49571 */ 49572 DeleteShapeHandler.prototype.execute = function(context) { 49573 var canvas = this._canvas; 49574 49575 var shape = context.shape, 49576 oldParent = shape.parent; 49577 49578 context.oldParent = oldParent; 49579 49580 // remove containment 49581 context.oldParentIndex = indexOf(oldParent.children, shape); 49582 49583 // remove shape 49584 canvas.removeShape(shape); 49585 49586 return shape; 49587 }; 49588 49589 49590 /** 49591 * Command revert implementation 49592 */ 49593 DeleteShapeHandler.prototype.revert = function(context) { 49594 49595 var canvas = this._canvas; 49596 49597 var shape = context.shape, 49598 oldParent = context.oldParent, 49599 oldParentIndex = context.oldParentIndex; 49600 49601 // restore containment 49602 add(oldParent.children, shape, oldParentIndex); 49603 49604 canvas.addShape(shape, oldParent); 49605 49606 return shape; 49607 }; 49608 49609 function isConnection$4(element) { 49610 return element.waypoints; 49611 } 49612 49613 /** 49614 * A handler that distributes elements evenly. 49615 */ 49616 function DistributeElements(modeling) { 49617 this._modeling = modeling; 49618 } 49619 49620 DistributeElements.$inject = [ 'modeling' ]; 49621 49622 var OFF_AXIS = { 49623 x: 'y', 49624 y: 'x' 49625 }; 49626 49627 DistributeElements.prototype.preExecute = function(context) { 49628 var modeling = this._modeling; 49629 49630 var groups = context.groups, 49631 axis = context.axis, 49632 dimension = context.dimension; 49633 49634 function updateRange(group, element) { 49635 group.range.min = Math.min(element[axis], group.range.min); 49636 group.range.max = Math.max(element[axis] + element[dimension], group.range.max); 49637 } 49638 49639 function center(element) { 49640 return element[axis] + element[dimension] / 2; 49641 } 49642 49643 function lastIdx(arr) { 49644 return arr.length - 1; 49645 } 49646 49647 function rangeDiff(range) { 49648 return range.max - range.min; 49649 } 49650 49651 function centerElement(refCenter, element) { 49652 var delta = { y: 0 }; 49653 49654 delta[axis] = refCenter - center(element); 49655 49656 if (delta[axis]) { 49657 49658 delta[OFF_AXIS[axis]] = 0; 49659 49660 modeling.moveElements([ element ], delta, element.parent); 49661 } 49662 } 49663 49664 var firstGroup = groups[0], 49665 lastGroupIdx = lastIdx(groups), 49666 lastGroup = groups[ lastGroupIdx ]; 49667 49668 var margin, 49669 spaceInBetween, 49670 groupsSize = 0; // the size of each range 49671 49672 forEach(groups, function(group, idx) { 49673 var sortedElements, 49674 refElem, 49675 refCenter; 49676 49677 if (group.elements.length < 2) { 49678 if (idx && idx !== groups.length - 1) { 49679 updateRange(group, group.elements[0]); 49680 49681 groupsSize += rangeDiff(group.range); 49682 } 49683 return; 49684 } 49685 49686 sortedElements = sortBy(group.elements, axis); 49687 49688 refElem = sortedElements[0]; 49689 49690 if (idx === lastGroupIdx) { 49691 refElem = sortedElements[lastIdx(sortedElements)]; 49692 } 49693 49694 refCenter = center(refElem); 49695 49696 // wanna update the ranges after the shapes have been centered 49697 group.range = null; 49698 49699 forEach(sortedElements, function(element) { 49700 49701 centerElement(refCenter, element); 49702 49703 if (group.range === null) { 49704 group.range = { 49705 min: element[axis], 49706 max: element[axis] + element[dimension] 49707 }; 49708 49709 return; 49710 } 49711 49712 // update group's range after centering the range elements 49713 updateRange(group, element); 49714 }); 49715 49716 if (idx && idx !== groups.length - 1) { 49717 groupsSize += rangeDiff(group.range); 49718 } 49719 }); 49720 49721 spaceInBetween = Math.abs(lastGroup.range.min - firstGroup.range.max); 49722 49723 margin = Math.round((spaceInBetween - groupsSize) / (groups.length - 1)); 49724 49725 if (margin < groups.length - 1) { 49726 return; 49727 } 49728 49729 forEach(groups, function(group, groupIdx) { 49730 var delta = {}, 49731 prevGroup; 49732 49733 if (group === firstGroup || group === lastGroup) { 49734 return; 49735 } 49736 49737 prevGroup = groups[groupIdx - 1]; 49738 49739 group.range.max = 0; 49740 49741 forEach(group.elements, function(element, idx) { 49742 delta[OFF_AXIS[axis]] = 0; 49743 delta[axis] = (prevGroup.range.max - element[axis]) + margin; 49744 49745 if (group.range.min !== element[axis]) { 49746 delta[axis] += element[axis] - group.range.min; 49747 } 49748 49749 if (delta[axis]) { 49750 modeling.moveElements([ element ], delta, element.parent); 49751 } 49752 49753 group.range.max = Math.max(element[axis] + element[dimension], idx ? group.range.max : 0); 49754 }); 49755 }); 49756 }; 49757 49758 DistributeElements.prototype.postExecute = function(context) { 49759 49760 }; 49761 49762 /** 49763 * A handler that implements reversible moving of shapes. 49764 */ 49765 function LayoutConnectionHandler(layouter, canvas) { 49766 this._layouter = layouter; 49767 this._canvas = canvas; 49768 } 49769 49770 LayoutConnectionHandler.$inject = [ 'layouter', 'canvas' ]; 49771 49772 LayoutConnectionHandler.prototype.execute = function(context) { 49773 49774 var connection = context.connection; 49775 49776 var oldWaypoints = connection.waypoints; 49777 49778 assign(context, { 49779 oldWaypoints: oldWaypoints 49780 }); 49781 49782 connection.waypoints = this._layouter.layoutConnection(connection, context.hints); 49783 49784 return connection; 49785 }; 49786 49787 LayoutConnectionHandler.prototype.revert = function(context) { 49788 49789 var connection = context.connection; 49790 49791 connection.waypoints = context.oldWaypoints; 49792 49793 return connection; 49794 }; 49795 49796 /** 49797 * A handler that implements reversible moving of connections. 49798 * 49799 * The handler differs from the layout connection handler in a sense 49800 * that it preserves the connection layout. 49801 */ 49802 function MoveConnectionHandler() { } 49803 49804 49805 MoveConnectionHandler.prototype.execute = function(context) { 49806 49807 var connection = context.connection, 49808 delta = context.delta; 49809 49810 var newParent = context.newParent || connection.parent, 49811 newParentIndex = context.newParentIndex, 49812 oldParent = connection.parent; 49813 49814 // save old parent in context 49815 context.oldParent = oldParent; 49816 context.oldParentIndex = remove(oldParent.children, connection); 49817 49818 // add to new parent at position 49819 add(newParent.children, connection, newParentIndex); 49820 49821 // update parent 49822 connection.parent = newParent; 49823 49824 // update waypoint positions 49825 forEach(connection.waypoints, function(p) { 49826 p.x += delta.x; 49827 p.y += delta.y; 49828 49829 if (p.original) { 49830 p.original.x += delta.x; 49831 p.original.y += delta.y; 49832 } 49833 }); 49834 49835 return connection; 49836 }; 49837 49838 MoveConnectionHandler.prototype.revert = function(context) { 49839 49840 var connection = context.connection, 49841 newParent = connection.parent, 49842 oldParent = context.oldParent, 49843 oldParentIndex = context.oldParentIndex, 49844 delta = context.delta; 49845 49846 // remove from newParent 49847 remove(newParent.children, connection); 49848 49849 // restore previous location in old parent 49850 add(oldParent.children, connection, oldParentIndex); 49851 49852 // restore parent 49853 connection.parent = oldParent; 49854 49855 // revert to old waypoint positions 49856 forEach(connection.waypoints, function(p) { 49857 p.x -= delta.x; 49858 p.y -= delta.y; 49859 49860 if (p.original) { 49861 p.original.x -= delta.x; 49862 p.original.y -= delta.y; 49863 } 49864 }); 49865 49866 return connection; 49867 }; 49868 49869 function MoveClosure() { 49870 49871 this.allShapes = {}; 49872 this.allConnections = {}; 49873 49874 this.enclosedElements = {}; 49875 this.enclosedConnections = {}; 49876 49877 this.topLevel = {}; 49878 } 49879 49880 49881 MoveClosure.prototype.add = function(element, isTopLevel) { 49882 return this.addAll([ element ], isTopLevel); 49883 }; 49884 49885 49886 MoveClosure.prototype.addAll = function(elements, isTopLevel) { 49887 49888 var newClosure = getClosure(elements, !!isTopLevel, this); 49889 49890 assign(this, newClosure); 49891 49892 return this; 49893 }; 49894 49895 /** 49896 * A helper that is able to carry out serialized move 49897 * operations on multiple elements. 49898 * 49899 * @param {Modeling} modeling 49900 */ 49901 function MoveHelper(modeling) { 49902 this._modeling = modeling; 49903 } 49904 49905 /** 49906 * Move the specified elements and all children by the given delta. 49907 * 49908 * This moves all enclosed connections, too and layouts all affected 49909 * external connections. 49910 * 49911 * @param {Array<djs.model.Base>} elements 49912 * @param {Point} delta 49913 * @param {djs.model.Base} newParent applied to the first level of shapes 49914 * 49915 * @return {Array<djs.model.Base>} list of touched elements 49916 */ 49917 MoveHelper.prototype.moveRecursive = function(elements, delta, newParent) { 49918 if (!elements) { 49919 return []; 49920 } else { 49921 return this.moveClosure(this.getClosure(elements), delta, newParent); 49922 } 49923 }; 49924 49925 /** 49926 * Move the given closure of elmements. 49927 * 49928 * @param {Object} closure 49929 * @param {Point} delta 49930 * @param {djs.model.Base} [newParent] 49931 * @param {djs.model.Base} [newHost] 49932 */ 49933 MoveHelper.prototype.moveClosure = function(closure, delta, newParent, newHost, primaryShape) { 49934 var modeling = this._modeling; 49935 49936 var allShapes = closure.allShapes, 49937 allConnections = closure.allConnections, 49938 enclosedConnections = closure.enclosedConnections, 49939 topLevel = closure.topLevel, 49940 keepParent = false; 49941 49942 if (primaryShape && primaryShape.parent === newParent) { 49943 keepParent = true; 49944 } 49945 49946 // move all shapes 49947 forEach(allShapes, function(shape) { 49948 49949 // move the element according to the given delta 49950 modeling.moveShape(shape, delta, topLevel[shape.id] && !keepParent && newParent, { 49951 recurse: false, 49952 layout: false 49953 }); 49954 }); 49955 49956 // move all child connections / layout external connections 49957 forEach(allConnections, function(c) { 49958 49959 var sourceMoved = !!allShapes[c.source.id], 49960 targetMoved = !!allShapes[c.target.id]; 49961 49962 if (enclosedConnections[c.id] && sourceMoved && targetMoved) { 49963 modeling.moveConnection(c, delta, topLevel[c.id] && !keepParent && newParent); 49964 } else { 49965 modeling.layoutConnection(c, { 49966 connectionStart: sourceMoved && getMovedSourceAnchor(c, c.source, delta), 49967 connectionEnd: targetMoved && getMovedTargetAnchor(c, c.target, delta) 49968 }); 49969 } 49970 }); 49971 }; 49972 49973 /** 49974 * Returns the closure for the selected elements 49975 * 49976 * @param {Array<djs.model.Base>} elements 49977 * @return {MoveClosure} closure 49978 */ 49979 MoveHelper.prototype.getClosure = function(elements) { 49980 return new MoveClosure().addAll(elements, true); 49981 }; 49982 49983 /** 49984 * A handler that implements reversible moving of shapes. 49985 */ 49986 function MoveElementsHandler(modeling) { 49987 this._helper = new MoveHelper(modeling); 49988 } 49989 49990 MoveElementsHandler.$inject = [ 'modeling' ]; 49991 49992 MoveElementsHandler.prototype.preExecute = function(context) { 49993 context.closure = this._helper.getClosure(context.shapes); 49994 }; 49995 49996 MoveElementsHandler.prototype.postExecute = function(context) { 49997 49998 var hints = context.hints, 49999 primaryShape; 50000 50001 if (hints && hints.primaryShape) { 50002 primaryShape = hints.primaryShape; 50003 hints.oldParent = primaryShape.parent; 50004 } 50005 50006 this._helper.moveClosure( 50007 context.closure, 50008 context.delta, 50009 context.newParent, 50010 context.newHost, 50011 primaryShape 50012 ); 50013 }; 50014 50015 /** 50016 * A handler that implements reversible moving of shapes. 50017 */ 50018 function MoveShapeHandler(modeling) { 50019 this._modeling = modeling; 50020 50021 this._helper = new MoveHelper(modeling); 50022 } 50023 50024 MoveShapeHandler.$inject = [ 'modeling' ]; 50025 50026 50027 MoveShapeHandler.prototype.execute = function(context) { 50028 50029 var shape = context.shape, 50030 delta = context.delta, 50031 newParent = context.newParent || shape.parent, 50032 newParentIndex = context.newParentIndex, 50033 oldParent = shape.parent; 50034 50035 context.oldBounds = pick(shape, [ 'x', 'y', 'width', 'height']); 50036 50037 // save old parent in context 50038 context.oldParent = oldParent; 50039 context.oldParentIndex = remove(oldParent.children, shape); 50040 50041 // add to new parent at position 50042 add(newParent.children, shape, newParentIndex); 50043 50044 // update shape parent + position 50045 assign(shape, { 50046 parent: newParent, 50047 x: shape.x + delta.x, 50048 y: shape.y + delta.y 50049 }); 50050 50051 return shape; 50052 }; 50053 50054 MoveShapeHandler.prototype.postExecute = function(context) { 50055 50056 var shape = context.shape, 50057 delta = context.delta, 50058 hints = context.hints; 50059 50060 var modeling = this._modeling; 50061 50062 if (hints.layout !== false) { 50063 50064 forEach(shape.incoming, function(c) { 50065 modeling.layoutConnection(c, { 50066 connectionEnd: getMovedTargetAnchor(c, shape, delta) 50067 }); 50068 }); 50069 50070 forEach(shape.outgoing, function(c) { 50071 modeling.layoutConnection(c, { 50072 connectionStart: getMovedSourceAnchor(c, shape, delta) 50073 }); 50074 }); 50075 } 50076 50077 if (hints.recurse !== false) { 50078 this.moveChildren(context); 50079 } 50080 }; 50081 50082 MoveShapeHandler.prototype.revert = function(context) { 50083 50084 var shape = context.shape, 50085 oldParent = context.oldParent, 50086 oldParentIndex = context.oldParentIndex, 50087 delta = context.delta; 50088 50089 // restore previous location in old parent 50090 add(oldParent.children, shape, oldParentIndex); 50091 50092 // revert to old position and parent 50093 assign(shape, { 50094 parent: oldParent, 50095 x: shape.x - delta.x, 50096 y: shape.y - delta.y 50097 }); 50098 50099 return shape; 50100 }; 50101 50102 MoveShapeHandler.prototype.moveChildren = function(context) { 50103 50104 var delta = context.delta, 50105 shape = context.shape; 50106 50107 this._helper.moveRecursive(shape.children, delta, null); 50108 }; 50109 50110 MoveShapeHandler.prototype.getNewParent = function(context) { 50111 return context.newParent || context.shape.parent; 50112 }; 50113 50114 /** 50115 * Reconnect connection handler 50116 */ 50117 function ReconnectConnectionHandler(modeling) { 50118 this._modeling = modeling; 50119 } 50120 50121 ReconnectConnectionHandler.$inject = [ 'modeling' ]; 50122 50123 ReconnectConnectionHandler.prototype.execute = function(context) { 50124 var newSource = context.newSource, 50125 newTarget = context.newTarget, 50126 connection = context.connection, 50127 dockingOrPoints = context.dockingOrPoints; 50128 50129 if (!newSource && !newTarget) { 50130 throw new Error('newSource or newTarget required'); 50131 } 50132 50133 if (isArray$2(dockingOrPoints)) { 50134 context.oldWaypoints = connection.waypoints; 50135 connection.waypoints = dockingOrPoints; 50136 } 50137 50138 if (newSource) { 50139 context.oldSource = connection.source; 50140 connection.source = newSource; 50141 } 50142 50143 if (newTarget) { 50144 context.oldTarget = connection.target; 50145 connection.target = newTarget; 50146 } 50147 50148 return connection; 50149 }; 50150 50151 ReconnectConnectionHandler.prototype.postExecute = function(context) { 50152 var connection = context.connection, 50153 newSource = context.newSource, 50154 newTarget = context.newTarget, 50155 dockingOrPoints = context.dockingOrPoints, 50156 hints = context.hints || {}; 50157 50158 var layoutConnectionHints = {}; 50159 50160 if (hints.connectionStart) { 50161 layoutConnectionHints.connectionStart = hints.connectionStart; 50162 } 50163 50164 if (hints.connectionEnd) { 50165 layoutConnectionHints.connectionEnd = hints.connectionEnd; 50166 } 50167 50168 if (hints.layoutConnection === false) { 50169 return; 50170 } 50171 50172 if (newSource && (!newTarget || hints.docking === 'source')) { 50173 layoutConnectionHints.connectionStart = layoutConnectionHints.connectionStart 50174 || getDocking(isArray$2(dockingOrPoints) ? dockingOrPoints[ 0 ] : dockingOrPoints); 50175 } 50176 50177 if (newTarget && (!newSource || hints.docking === 'target')) { 50178 layoutConnectionHints.connectionEnd = layoutConnectionHints.connectionEnd 50179 || getDocking(isArray$2(dockingOrPoints) ? dockingOrPoints[ dockingOrPoints.length - 1 ] : dockingOrPoints); 50180 } 50181 50182 if (hints.newWaypoints) { 50183 layoutConnectionHints.waypoints = hints.newWaypoints; 50184 } 50185 50186 this._modeling.layoutConnection(connection, layoutConnectionHints); 50187 }; 50188 50189 ReconnectConnectionHandler.prototype.revert = function(context) { 50190 var oldSource = context.oldSource, 50191 oldTarget = context.oldTarget, 50192 oldWaypoints = context.oldWaypoints, 50193 connection = context.connection; 50194 50195 if (oldSource) { 50196 connection.source = oldSource; 50197 } 50198 50199 if (oldTarget) { 50200 connection.target = oldTarget; 50201 } 50202 50203 if (oldWaypoints) { 50204 connection.waypoints = oldWaypoints; 50205 } 50206 50207 return connection; 50208 }; 50209 50210 50211 50212 // helpers ////////// 50213 50214 function getDocking(point) { 50215 return point.original || point; 50216 } 50217 50218 /** 50219 * Replace shape by adding new shape and removing old shape. Incoming and outgoing connections will 50220 * be kept if possible. 50221 * 50222 * @class 50223 * @constructor 50224 * 50225 * @param {Modeling} modeling 50226 * @param {Rules} rules 50227 */ 50228 function ReplaceShapeHandler(modeling, rules) { 50229 this._modeling = modeling; 50230 this._rules = rules; 50231 } 50232 50233 ReplaceShapeHandler.$inject = [ 'modeling', 'rules' ]; 50234 50235 50236 /** 50237 * Add new shape. 50238 * 50239 * @param {Object} context 50240 * @param {djs.model.Shape} context.oldShape 50241 * @param {Object} context.newData 50242 * @param {string} context.newData.type 50243 * @param {number} context.newData.x 50244 * @param {number} context.newData.y 50245 * @param {Object} [hints] 50246 */ 50247 ReplaceShapeHandler.prototype.preExecute = function(context) { 50248 var self = this, 50249 modeling = this._modeling, 50250 rules = this._rules; 50251 50252 var oldShape = context.oldShape, 50253 newData = context.newData, 50254 hints = context.hints || {}, 50255 newShape; 50256 50257 function canReconnect(source, target, connection) { 50258 return rules.allowed('connection.reconnect', { 50259 connection: connection, 50260 source: source, 50261 target: target 50262 }); 50263 } 50264 50265 // (1) add new shape at given position 50266 var position = { 50267 x: newData.x, 50268 y: newData.y 50269 }; 50270 50271 var oldBounds = { 50272 x: oldShape.x, 50273 y: oldShape.y, 50274 width: oldShape.width, 50275 height: oldShape.height 50276 }; 50277 50278 newShape = context.newShape = 50279 context.newShape || 50280 self.createShape(newData, position, oldShape.parent, hints); 50281 50282 // (2) update host 50283 if (oldShape.host) { 50284 modeling.updateAttachment(newShape, oldShape.host); 50285 } 50286 50287 // (3) adopt all children from old shape 50288 var children; 50289 50290 if (hints.moveChildren !== false) { 50291 children = oldShape.children.slice(); 50292 50293 modeling.moveElements(children, { x: 0, y: 0 }, newShape, hints); 50294 } 50295 50296 // (4) reconnect connections to new shape if possible 50297 var incoming = oldShape.incoming.slice(), 50298 outgoing = oldShape.outgoing.slice(); 50299 50300 forEach(incoming, function(connection) { 50301 var source = connection.source, 50302 allowed = canReconnect(source, newShape, connection); 50303 50304 if (allowed) { 50305 self.reconnectEnd( 50306 connection, newShape, 50307 getResizedTargetAnchor(connection, newShape, oldBounds), 50308 hints 50309 ); 50310 } 50311 }); 50312 50313 forEach(outgoing, function(connection) { 50314 var target = connection.target, 50315 allowed = canReconnect(newShape, target, connection); 50316 50317 if (allowed) { 50318 self.reconnectStart( 50319 connection, newShape, 50320 getResizedSourceAnchor(connection, newShape, oldBounds), 50321 hints 50322 ); 50323 } 50324 }); 50325 }; 50326 50327 50328 /** 50329 * Remove old shape. 50330 */ 50331 ReplaceShapeHandler.prototype.postExecute = function(context) { 50332 var oldShape = context.oldShape; 50333 50334 this._modeling.removeShape(oldShape); 50335 }; 50336 50337 50338 ReplaceShapeHandler.prototype.execute = function(context) {}; 50339 50340 50341 ReplaceShapeHandler.prototype.revert = function(context) {}; 50342 50343 50344 ReplaceShapeHandler.prototype.createShape = function(shape, position, target, hints) { 50345 return this._modeling.createShape(shape, position, target, hints); 50346 }; 50347 50348 50349 ReplaceShapeHandler.prototype.reconnectStart = function(connection, newSource, dockingPoint, hints) { 50350 this._modeling.reconnectStart(connection, newSource, dockingPoint, hints); 50351 }; 50352 50353 50354 ReplaceShapeHandler.prototype.reconnectEnd = function(connection, newTarget, dockingPoint, hints) { 50355 this._modeling.reconnectEnd(connection, newTarget, dockingPoint, hints); 50356 }; 50357 50358 /** 50359 * A handler that implements reversible resizing of shapes. 50360 * 50361 * @param {Modeling} modeling 50362 */ 50363 function ResizeShapeHandler(modeling) { 50364 this._modeling = modeling; 50365 } 50366 50367 ResizeShapeHandler.$inject = [ 'modeling' ]; 50368 50369 /** 50370 * { 50371 * shape: {....} 50372 * newBounds: { 50373 * width: 20, 50374 * height: 40, 50375 * x: 5, 50376 * y: 10 50377 * } 50378 * 50379 * } 50380 */ 50381 ResizeShapeHandler.prototype.execute = function(context) { 50382 var shape = context.shape, 50383 newBounds = context.newBounds, 50384 minBounds = context.minBounds; 50385 50386 if (newBounds.x === undefined || newBounds.y === undefined || 50387 newBounds.width === undefined || newBounds.height === undefined) { 50388 throw new Error('newBounds must have {x, y, width, height} properties'); 50389 } 50390 50391 if (minBounds && (newBounds.width < minBounds.width 50392 || newBounds.height < minBounds.height)) { 50393 throw new Error('width and height cannot be less than minimum height and width'); 50394 } else if (!minBounds 50395 && newBounds.width < 10 || newBounds.height < 10) { 50396 throw new Error('width and height cannot be less than 10px'); 50397 } 50398 50399 // save old bbox in context 50400 context.oldBounds = { 50401 width: shape.width, 50402 height: shape.height, 50403 x: shape.x, 50404 y: shape.y 50405 }; 50406 50407 // update shape 50408 assign(shape, { 50409 width: newBounds.width, 50410 height: newBounds.height, 50411 x: newBounds.x, 50412 y: newBounds.y 50413 }); 50414 50415 return shape; 50416 }; 50417 50418 ResizeShapeHandler.prototype.postExecute = function(context) { 50419 var modeling = this._modeling; 50420 50421 var shape = context.shape, 50422 oldBounds = context.oldBounds, 50423 hints = context.hints || {}; 50424 50425 if (hints.layout === false) { 50426 return; 50427 } 50428 50429 forEach(shape.incoming, function(c) { 50430 modeling.layoutConnection(c, { 50431 connectionEnd: getResizedTargetAnchor(c, shape, oldBounds) 50432 }); 50433 }); 50434 50435 forEach(shape.outgoing, function(c) { 50436 modeling.layoutConnection(c, { 50437 connectionStart: getResizedSourceAnchor(c, shape, oldBounds) 50438 }); 50439 }); 50440 50441 }; 50442 50443 ResizeShapeHandler.prototype.revert = function(context) { 50444 50445 var shape = context.shape, 50446 oldBounds = context.oldBounds; 50447 50448 // restore previous bbox 50449 assign(shape, { 50450 width: oldBounds.width, 50451 height: oldBounds.height, 50452 x: oldBounds.x, 50453 y: oldBounds.y 50454 }); 50455 50456 return shape; 50457 }; 50458 50459 /** 50460 * Add or remove space by moving and resizing shapes and updating connection waypoints. 50461 */ 50462 function SpaceToolHandler(modeling) { 50463 this._modeling = modeling; 50464 } 50465 50466 SpaceToolHandler.$inject = [ 'modeling' ]; 50467 50468 SpaceToolHandler.prototype.preExecute = function(context) { 50469 var delta = context.delta, 50470 direction = context.direction, 50471 movingShapes = context.movingShapes, 50472 resizingShapes = context.resizingShapes, 50473 start = context.start, 50474 oldBounds = {}; 50475 50476 // (1) move shapes 50477 this.moveShapes(movingShapes, delta); 50478 50479 // (2a) save old bounds of resized shapes 50480 forEach(resizingShapes, function(shape) { 50481 oldBounds[shape.id] = getBounds(shape); 50482 }); 50483 50484 // (2b) resize shapes 50485 this.resizeShapes(resizingShapes, delta, direction); 50486 50487 // (3) update connection waypoints 50488 this.updateConnectionWaypoints( 50489 getWaypointsUpdatingConnections(movingShapes, resizingShapes), 50490 delta, 50491 direction, 50492 start, 50493 movingShapes, 50494 resizingShapes, 50495 oldBounds 50496 ); 50497 }; 50498 50499 SpaceToolHandler.prototype.execute = function() {}; 50500 SpaceToolHandler.prototype.revert = function() {}; 50501 50502 SpaceToolHandler.prototype.moveShapes = function(shapes, delta) { 50503 var self = this; 50504 50505 forEach(shapes, function(element) { 50506 self._modeling.moveShape(element, delta, null, { 50507 autoResize: false, 50508 layout: false, 50509 recurse: false 50510 }); 50511 }); 50512 }; 50513 50514 SpaceToolHandler.prototype.resizeShapes = function(shapes, delta, direction) { 50515 var self = this; 50516 50517 forEach(shapes, function(shape) { 50518 var newBounds = resizeBounds(shape, direction, delta); 50519 50520 self._modeling.resizeShape(shape, newBounds, null, { 50521 attachSupport: false, 50522 autoResize: false, 50523 layout: false 50524 }); 50525 }); 50526 }; 50527 50528 /** 50529 * Update connections waypoints according to the rules: 50530 * 1. Both source and target are moved/resized => move waypoints by the delta 50531 * 2. Only one of source and target is moved/resized => re-layout connection with moved start/end 50532 */ 50533 SpaceToolHandler.prototype.updateConnectionWaypoints = function( 50534 connections, 50535 delta, 50536 direction, 50537 start, 50538 movingShapes, 50539 resizingShapes, 50540 oldBounds 50541 ) { 50542 var self = this, 50543 affectedShapes = movingShapes.concat(resizingShapes); 50544 50545 forEach(connections, function(connection) { 50546 var source = connection.source, 50547 target = connection.target, 50548 waypoints = copyWaypoints(connection), 50549 axis = getAxisFromDirection(direction), 50550 layoutHints = { 50551 labelBehavior: false 50552 }; 50553 50554 if (includes$1(affectedShapes, source) && includes$1(affectedShapes, target)) { 50555 50556 // move waypoints 50557 waypoints = map$1(waypoints, function(waypoint) { 50558 if (shouldMoveWaypoint(waypoint, start, direction)) { 50559 50560 // move waypoint 50561 waypoint[ axis ] = waypoint[ axis ] + delta[ axis ]; 50562 } 50563 50564 if (waypoint.original && shouldMoveWaypoint(waypoint.original, start, direction)) { 50565 50566 // move waypoint original 50567 waypoint.original[ axis ] = waypoint.original[ axis ] + delta[ axis ]; 50568 } 50569 50570 return waypoint; 50571 }); 50572 50573 self._modeling.updateWaypoints(connection, waypoints, { 50574 labelBehavior: false 50575 }); 50576 } else if (includes$1(affectedShapes, source) || includes$1(affectedShapes, target)) { 50577 50578 // re-layout connection with moved start/end 50579 if (includes$1(movingShapes, source)) { 50580 layoutHints.connectionStart = getMovedSourceAnchor(connection, source, delta); 50581 } else if (includes$1(movingShapes, target)) { 50582 layoutHints.connectionEnd = getMovedTargetAnchor(connection, target, delta); 50583 } else if (includes$1(resizingShapes, source)) { 50584 layoutHints.connectionStart = getResizedSourceAnchor( 50585 connection, source, oldBounds[source.id] 50586 ); 50587 } else if (includes$1(resizingShapes, target)) { 50588 layoutHints.connectionEnd = getResizedTargetAnchor( 50589 connection, target, oldBounds[target.id] 50590 ); 50591 } 50592 50593 self._modeling.layoutConnection(connection, layoutHints); 50594 } 50595 }); 50596 }; 50597 50598 50599 // helpers ////////// 50600 50601 function copyWaypoint(waypoint) { 50602 return assign({}, waypoint); 50603 } 50604 50605 function copyWaypoints(connection) { 50606 return map$1(connection.waypoints, function(waypoint) { 50607 50608 waypoint = copyWaypoint(waypoint); 50609 50610 if (waypoint.original) { 50611 waypoint.original = copyWaypoint(waypoint.original); 50612 } 50613 50614 return waypoint; 50615 }); 50616 } 50617 50618 function getAxisFromDirection(direction) { 50619 switch (direction) { 50620 case 'n': 50621 return 'y'; 50622 case 'w': 50623 return 'x'; 50624 case 's': 50625 return 'y'; 50626 case 'e': 50627 return 'x'; 50628 } 50629 } 50630 50631 function shouldMoveWaypoint(waypoint, start, direction) { 50632 var relevantAxis = getAxisFromDirection(direction); 50633 50634 if (/e|s/.test(direction)) { 50635 return waypoint[ relevantAxis ] > start; 50636 } else if (/n|w/.test(direction)) { 50637 return waypoint[ relevantAxis ] < start; 50638 } 50639 } 50640 50641 function includes$1(array, item) { 50642 return array.indexOf(item) !== -1; 50643 } 50644 50645 function getBounds(shape) { 50646 return { 50647 x: shape.x, 50648 y: shape.y, 50649 height: shape.height, 50650 width: shape.width 50651 }; 50652 } 50653 50654 /** 50655 * A handler that toggles the collapsed state of an element 50656 * and the visibility of all its children. 50657 * 50658 * @param {Modeling} modeling 50659 */ 50660 function ToggleShapeCollapseHandler(modeling) { 50661 this._modeling = modeling; 50662 } 50663 50664 ToggleShapeCollapseHandler.$inject = [ 'modeling' ]; 50665 50666 50667 ToggleShapeCollapseHandler.prototype.execute = function(context) { 50668 50669 var shape = context.shape, 50670 children = shape.children; 50671 50672 // recursively remember previous visibility of children 50673 context.oldChildrenVisibility = getElementsVisibilityRecursive(children); 50674 50675 // toggle state 50676 shape.collapsed = !shape.collapsed; 50677 50678 // recursively hide/show children 50679 var result = setHiddenRecursive(children, shape.collapsed); 50680 50681 return [shape].concat(result); 50682 }; 50683 50684 50685 ToggleShapeCollapseHandler.prototype.revert = function(context) { 50686 50687 var shape = context.shape, 50688 oldChildrenVisibility = context.oldChildrenVisibility; 50689 50690 var children = shape.children; 50691 50692 // recursively set old visability of children 50693 var result = restoreVisibilityRecursive(children, oldChildrenVisibility); 50694 50695 // retoggle state 50696 shape.collapsed = !shape.collapsed; 50697 50698 return [shape].concat(result); 50699 }; 50700 50701 50702 // helpers ////////////////////// 50703 50704 /** 50705 * Return a map { elementId -> hiddenState}. 50706 * 50707 * @param {Array<djs.model.Shape>} elements 50708 * 50709 * @return {Object} 50710 */ 50711 function getElementsVisibilityRecursive(elements) { 50712 50713 var result = {}; 50714 50715 forEach(elements, function(element) { 50716 result[element.id] = element.hidden; 50717 50718 if (element.children) { 50719 result = assign({}, result, getElementsVisibilityRecursive(element.children)); 50720 } 50721 }); 50722 50723 return result; 50724 } 50725 50726 50727 function setHiddenRecursive(elements, newHidden) { 50728 var result = []; 50729 forEach(elements, function(element) { 50730 element.hidden = newHidden; 50731 50732 result = result.concat(element); 50733 50734 if (element.children) { 50735 result = result.concat(setHiddenRecursive(element.children, element.collapsed || newHidden)); 50736 } 50737 }); 50738 50739 return result; 50740 } 50741 50742 function restoreVisibilityRecursive(elements, lastState) { 50743 var result = []; 50744 forEach(elements, function(element) { 50745 element.hidden = lastState[element.id]; 50746 50747 result = result.concat(element); 50748 50749 if (element.children) { 50750 result = result.concat(restoreVisibilityRecursive(element.children, lastState)); 50751 } 50752 }); 50753 50754 return result; 50755 } 50756 50757 /** 50758 * A handler that implements reversible attaching/detaching of shapes. 50759 */ 50760 function UpdateAttachmentHandler(modeling) { 50761 this._modeling = modeling; 50762 } 50763 50764 UpdateAttachmentHandler.$inject = [ 'modeling' ]; 50765 50766 50767 UpdateAttachmentHandler.prototype.execute = function(context) { 50768 var shape = context.shape, 50769 newHost = context.newHost, 50770 oldHost = shape.host; 50771 50772 // (0) detach from old host 50773 context.oldHost = oldHost; 50774 context.attacherIdx = removeAttacher(oldHost, shape); 50775 50776 // (1) attach to new host 50777 addAttacher(newHost, shape); 50778 50779 // (2) update host 50780 shape.host = newHost; 50781 50782 return shape; 50783 }; 50784 50785 UpdateAttachmentHandler.prototype.revert = function(context) { 50786 var shape = context.shape, 50787 newHost = context.newHost, 50788 oldHost = context.oldHost, 50789 attacherIdx = context.attacherIdx; 50790 50791 // (2) update host 50792 shape.host = oldHost; 50793 50794 // (1) attach to new host 50795 removeAttacher(newHost, shape); 50796 50797 // (0) detach from old host 50798 addAttacher(oldHost, shape, attacherIdx); 50799 50800 return shape; 50801 }; 50802 50803 50804 function removeAttacher(host, attacher) { 50805 50806 // remove attacher from host 50807 return remove(host && host.attachers, attacher); 50808 } 50809 50810 function addAttacher(host, attacher, idx) { 50811 50812 if (!host) { 50813 return; 50814 } 50815 50816 var attachers = host.attachers; 50817 50818 if (!attachers) { 50819 host.attachers = attachers = []; 50820 } 50821 50822 add(attachers, attacher, idx); 50823 } 50824 50825 function UpdateWaypointsHandler() { } 50826 50827 UpdateWaypointsHandler.prototype.execute = function(context) { 50828 50829 var connection = context.connection, 50830 newWaypoints = context.newWaypoints; 50831 50832 context.oldWaypoints = connection.waypoints; 50833 50834 connection.waypoints = newWaypoints; 50835 50836 return connection; 50837 }; 50838 50839 UpdateWaypointsHandler.prototype.revert = function(context) { 50840 50841 var connection = context.connection, 50842 oldWaypoints = context.oldWaypoints; 50843 50844 connection.waypoints = oldWaypoints; 50845 50846 return connection; 50847 }; 50848 50849 /** 50850 * The basic modeling entry point. 50851 * 50852 * @param {EventBus} eventBus 50853 * @param {ElementFactory} elementFactory 50854 * @param {CommandStack} commandStack 50855 */ 50856 function Modeling$1(eventBus, elementFactory, commandStack) { 50857 this._eventBus = eventBus; 50858 this._elementFactory = elementFactory; 50859 this._commandStack = commandStack; 50860 50861 var self = this; 50862 50863 eventBus.on('diagram.init', function() { 50864 50865 // register modeling handlers 50866 self.registerHandlers(commandStack); 50867 }); 50868 } 50869 50870 Modeling$1.$inject = [ 'eventBus', 'elementFactory', 'commandStack' ]; 50871 50872 50873 Modeling$1.prototype.getHandlers = function() { 50874 return { 50875 'shape.append': AppendShapeHandler, 50876 'shape.create': CreateShapeHandler, 50877 'shape.delete': DeleteShapeHandler, 50878 'shape.move': MoveShapeHandler, 50879 'shape.resize': ResizeShapeHandler, 50880 'shape.replace': ReplaceShapeHandler, 50881 'shape.toggleCollapse': ToggleShapeCollapseHandler, 50882 50883 'spaceTool': SpaceToolHandler, 50884 50885 'label.create': CreateLabelHandler, 50886 50887 'connection.create': CreateConnectionHandler, 50888 'connection.delete': DeleteConnectionHandler, 50889 'connection.move': MoveConnectionHandler, 50890 'connection.layout': LayoutConnectionHandler, 50891 50892 'connection.updateWaypoints': UpdateWaypointsHandler, 50893 50894 'connection.reconnect': ReconnectConnectionHandler, 50895 50896 'elements.create': CreateElementsHandler, 50897 'elements.move': MoveElementsHandler, 50898 'elements.delete': DeleteElementsHandler, 50899 50900 'elements.distribute': DistributeElements, 50901 'elements.align': AlignElements, 50902 50903 'element.updateAttachment': UpdateAttachmentHandler 50904 }; 50905 }; 50906 50907 /** 50908 * Register handlers with the command stack 50909 * 50910 * @param {CommandStack} commandStack 50911 */ 50912 Modeling$1.prototype.registerHandlers = function(commandStack) { 50913 forEach(this.getHandlers(), function(handler, id) { 50914 commandStack.registerHandler(id, handler); 50915 }); 50916 }; 50917 50918 50919 // modeling helpers ////////////////////// 50920 50921 Modeling$1.prototype.moveShape = function(shape, delta, newParent, newParentIndex, hints) { 50922 50923 if (typeof newParentIndex === 'object') { 50924 hints = newParentIndex; 50925 newParentIndex = null; 50926 } 50927 50928 var context = { 50929 shape: shape, 50930 delta: delta, 50931 newParent: newParent, 50932 newParentIndex: newParentIndex, 50933 hints: hints || {} 50934 }; 50935 50936 this._commandStack.execute('shape.move', context); 50937 }; 50938 50939 50940 /** 50941 * Update the attachment of the given shape. 50942 * 50943 * @param {djs.mode.Base} shape 50944 * @param {djs.model.Base} [newHost] 50945 */ 50946 Modeling$1.prototype.updateAttachment = function(shape, newHost) { 50947 var context = { 50948 shape: shape, 50949 newHost: newHost 50950 }; 50951 50952 this._commandStack.execute('element.updateAttachment', context); 50953 }; 50954 50955 50956 /** 50957 * Move a number of shapes to a new target, either setting it as 50958 * the new parent or attaching it. 50959 * 50960 * @param {Array<djs.mode.Base>} shapes 50961 * @param {Point} delta 50962 * @param {djs.model.Base} [target] 50963 * @param {Object} [hints] 50964 * @param {boolean} [hints.attach=false] 50965 */ 50966 Modeling$1.prototype.moveElements = function(shapes, delta, target, hints) { 50967 50968 hints = hints || {}; 50969 50970 var attach = hints.attach; 50971 50972 var newParent = target, 50973 newHost; 50974 50975 if (attach === true) { 50976 newHost = target; 50977 newParent = target.parent; 50978 } else 50979 50980 if (attach === false) { 50981 newHost = null; 50982 } 50983 50984 var context = { 50985 shapes: shapes, 50986 delta: delta, 50987 newParent: newParent, 50988 newHost: newHost, 50989 hints: hints 50990 }; 50991 50992 this._commandStack.execute('elements.move', context); 50993 }; 50994 50995 50996 Modeling$1.prototype.moveConnection = function(connection, delta, newParent, newParentIndex, hints) { 50997 50998 if (typeof newParentIndex === 'object') { 50999 hints = newParentIndex; 51000 newParentIndex = undefined; 51001 } 51002 51003 var context = { 51004 connection: connection, 51005 delta: delta, 51006 newParent: newParent, 51007 newParentIndex: newParentIndex, 51008 hints: hints || {} 51009 }; 51010 51011 this._commandStack.execute('connection.move', context); 51012 }; 51013 51014 51015 Modeling$1.prototype.layoutConnection = function(connection, hints) { 51016 var context = { 51017 connection: connection, 51018 hints: hints || {} 51019 }; 51020 51021 this._commandStack.execute('connection.layout', context); 51022 }; 51023 51024 51025 /** 51026 * Create connection. 51027 * 51028 * @param {djs.model.Base} source 51029 * @param {djs.model.Base} target 51030 * @param {number} [parentIndex] 51031 * @param {Object|djs.model.Connection} connection 51032 * @param {djs.model.Base} parent 51033 * @param {Object} hints 51034 * 51035 * @return {djs.model.Connection} the created connection. 51036 */ 51037 Modeling$1.prototype.createConnection = function(source, target, parentIndex, connection, parent, hints) { 51038 51039 if (typeof parentIndex === 'object') { 51040 hints = parent; 51041 parent = connection; 51042 connection = parentIndex; 51043 parentIndex = undefined; 51044 } 51045 51046 connection = this._create('connection', connection); 51047 51048 var context = { 51049 source: source, 51050 target: target, 51051 parent: parent, 51052 parentIndex: parentIndex, 51053 connection: connection, 51054 hints: hints 51055 }; 51056 51057 this._commandStack.execute('connection.create', context); 51058 51059 return context.connection; 51060 }; 51061 51062 51063 /** 51064 * Create a shape at the specified position. 51065 * 51066 * @param {djs.model.Shape|Object} shape 51067 * @param {Point} position 51068 * @param {djs.model.Shape|djs.model.Root} target 51069 * @param {number} [parentIndex] position in parents children list 51070 * @param {Object} [hints] 51071 * @param {boolean} [hints.attach] whether to attach to target or become a child 51072 * 51073 * @return {djs.model.Shape} the created shape 51074 */ 51075 Modeling$1.prototype.createShape = function(shape, position, target, parentIndex, hints) { 51076 51077 if (typeof parentIndex !== 'number') { 51078 hints = parentIndex; 51079 parentIndex = undefined; 51080 } 51081 51082 hints = hints || {}; 51083 51084 var attach = hints.attach, 51085 parent, 51086 host; 51087 51088 shape = this._create('shape', shape); 51089 51090 if (attach) { 51091 parent = target.parent; 51092 host = target; 51093 } else { 51094 parent = target; 51095 } 51096 51097 var context = { 51098 position: position, 51099 shape: shape, 51100 parent: parent, 51101 parentIndex: parentIndex, 51102 host: host, 51103 hints: hints 51104 }; 51105 51106 this._commandStack.execute('shape.create', context); 51107 51108 return context.shape; 51109 }; 51110 51111 51112 Modeling$1.prototype.createElements = function(elements, position, parent, parentIndex, hints) { 51113 if (!isArray$2(elements)) { 51114 elements = [ elements ]; 51115 } 51116 51117 if (typeof parentIndex !== 'number') { 51118 hints = parentIndex; 51119 parentIndex = undefined; 51120 } 51121 51122 hints = hints || {}; 51123 51124 var context = { 51125 position: position, 51126 elements: elements, 51127 parent: parent, 51128 parentIndex: parentIndex, 51129 hints: hints 51130 }; 51131 51132 this._commandStack.execute('elements.create', context); 51133 51134 return context.elements; 51135 }; 51136 51137 51138 Modeling$1.prototype.createLabel = function(labelTarget, position, label, parent) { 51139 51140 label = this._create('label', label); 51141 51142 var context = { 51143 labelTarget: labelTarget, 51144 position: position, 51145 parent: parent || labelTarget.parent, 51146 shape: label 51147 }; 51148 51149 this._commandStack.execute('label.create', context); 51150 51151 return context.shape; 51152 }; 51153 51154 51155 /** 51156 * Append shape to given source, drawing a connection 51157 * between source and the newly created shape. 51158 * 51159 * @param {djs.model.Shape} source 51160 * @param {djs.model.Shape|Object} shape 51161 * @param {Point} position 51162 * @param {djs.model.Shape} target 51163 * @param {Object} [hints] 51164 * @param {boolean} [hints.attach] 51165 * @param {djs.model.Connection|Object} [hints.connection] 51166 * @param {djs.model.Base} [hints.connectionParent] 51167 * 51168 * @return {djs.model.Shape} the newly created shape 51169 */ 51170 Modeling$1.prototype.appendShape = function(source, shape, position, target, hints) { 51171 51172 hints = hints || {}; 51173 51174 shape = this._create('shape', shape); 51175 51176 var context = { 51177 source: source, 51178 position: position, 51179 target: target, 51180 shape: shape, 51181 connection: hints.connection, 51182 connectionParent: hints.connectionParent, 51183 hints: hints 51184 }; 51185 51186 this._commandStack.execute('shape.append', context); 51187 51188 return context.shape; 51189 }; 51190 51191 51192 Modeling$1.prototype.removeElements = function(elements) { 51193 var context = { 51194 elements: elements 51195 }; 51196 51197 this._commandStack.execute('elements.delete', context); 51198 }; 51199 51200 51201 Modeling$1.prototype.distributeElements = function(groups, axis, dimension) { 51202 var context = { 51203 groups: groups, 51204 axis: axis, 51205 dimension: dimension 51206 }; 51207 51208 this._commandStack.execute('elements.distribute', context); 51209 }; 51210 51211 51212 Modeling$1.prototype.removeShape = function(shape, hints) { 51213 var context = { 51214 shape: shape, 51215 hints: hints || {} 51216 }; 51217 51218 this._commandStack.execute('shape.delete', context); 51219 }; 51220 51221 51222 Modeling$1.prototype.removeConnection = function(connection, hints) { 51223 var context = { 51224 connection: connection, 51225 hints: hints || {} 51226 }; 51227 51228 this._commandStack.execute('connection.delete', context); 51229 }; 51230 51231 Modeling$1.prototype.replaceShape = function(oldShape, newShape, hints) { 51232 var context = { 51233 oldShape: oldShape, 51234 newData: newShape, 51235 hints: hints || {} 51236 }; 51237 51238 this._commandStack.execute('shape.replace', context); 51239 51240 return context.newShape; 51241 }; 51242 51243 Modeling$1.prototype.alignElements = function(elements, alignment) { 51244 var context = { 51245 elements: elements, 51246 alignment: alignment 51247 }; 51248 51249 this._commandStack.execute('elements.align', context); 51250 }; 51251 51252 Modeling$1.prototype.resizeShape = function(shape, newBounds, minBounds, hints) { 51253 var context = { 51254 shape: shape, 51255 newBounds: newBounds, 51256 minBounds: minBounds, 51257 hints: hints 51258 }; 51259 51260 this._commandStack.execute('shape.resize', context); 51261 }; 51262 51263 Modeling$1.prototype.createSpace = function(movingShapes, resizingShapes, delta, direction, start) { 51264 var context = { 51265 delta: delta, 51266 direction: direction, 51267 movingShapes: movingShapes, 51268 resizingShapes: resizingShapes, 51269 start: start 51270 }; 51271 51272 this._commandStack.execute('spaceTool', context); 51273 }; 51274 51275 Modeling$1.prototype.updateWaypoints = function(connection, newWaypoints, hints) { 51276 var context = { 51277 connection: connection, 51278 newWaypoints: newWaypoints, 51279 hints: hints || {} 51280 }; 51281 51282 this._commandStack.execute('connection.updateWaypoints', context); 51283 }; 51284 51285 Modeling$1.prototype.reconnect = function(connection, source, target, dockingOrPoints, hints) { 51286 var context = { 51287 connection: connection, 51288 newSource: source, 51289 newTarget: target, 51290 dockingOrPoints: dockingOrPoints, 51291 hints: hints || {} 51292 }; 51293 51294 this._commandStack.execute('connection.reconnect', context); 51295 }; 51296 51297 Modeling$1.prototype.reconnectStart = function(connection, newSource, dockingOrPoints, hints) { 51298 if (!hints) { 51299 hints = {}; 51300 } 51301 51302 this.reconnect(connection, newSource, connection.target, dockingOrPoints, assign(hints, { 51303 docking: 'source' 51304 })); 51305 }; 51306 51307 Modeling$1.prototype.reconnectEnd = function(connection, newTarget, dockingOrPoints, hints) { 51308 if (!hints) { 51309 hints = {}; 51310 } 51311 51312 this.reconnect(connection, connection.source, newTarget, dockingOrPoints, assign(hints, { 51313 docking: 'target' 51314 })); 51315 }; 51316 51317 Modeling$1.prototype.connect = function(source, target, attrs, hints) { 51318 return this.createConnection(source, target, attrs || {}, source.parent, hints); 51319 }; 51320 51321 Modeling$1.prototype._create = function(type, attrs) { 51322 if (attrs instanceof Base$1) { 51323 return attrs; 51324 } else { 51325 return this._elementFactory.create(type, attrs); 51326 } 51327 }; 51328 51329 Modeling$1.prototype.toggleCollapse = function(shape, hints) { 51330 var context = { 51331 shape: shape, 51332 hints: hints || {} 51333 }; 51334 51335 this._commandStack.execute('shape.toggleCollapse', context); 51336 }; 51337 51338 function UpdateModdlePropertiesHandler(elementRegistry) { 51339 this._elementRegistry = elementRegistry; 51340 } 51341 51342 UpdateModdlePropertiesHandler.$inject = ['elementRegistry']; 51343 51344 UpdateModdlePropertiesHandler.prototype.execute = function(context) { 51345 51346 var element = context.element, 51347 moddleElement = context.moddleElement, 51348 properties = context.properties; 51349 51350 if (!moddleElement) { 51351 throw new Error('<moddleElement> required'); 51352 } 51353 51354 var changed = context.changed || this.getVisualReferences(moddleElement).concat(element); 51355 var oldProperties = context.oldProperties || getModdleProperties(moddleElement, keys(properties)); 51356 51357 setModdleProperties(moddleElement, properties); 51358 51359 context.oldProperties = oldProperties; 51360 context.changed = changed; 51361 51362 return changed; 51363 }; 51364 51365 UpdateModdlePropertiesHandler.prototype.revert = function(context) { 51366 var oldProperties = context.oldProperties, 51367 moddleElement = context.moddleElement, 51368 changed = context.changed; 51369 51370 setModdleProperties(moddleElement, oldProperties); 51371 51372 return changed; 51373 }; 51374 51375 /** 51376 * Return visual references of given moddle element within the diagram. 51377 * 51378 * @param {ModdleElement} moddleElement 51379 * 51380 * @return {Array<djs.model.Element>} 51381 */ 51382 UpdateModdlePropertiesHandler.prototype.getVisualReferences = function(moddleElement) { 51383 51384 var elementRegistry = this._elementRegistry; 51385 51386 if (is$1(moddleElement, 'bpmn:DataObject')) { 51387 return getAllDataObjectReferences(moddleElement, elementRegistry); 51388 } 51389 51390 return []; 51391 }; 51392 51393 51394 // helpers ///////////////// 51395 51396 function getModdleProperties(moddleElement, propertyNames) { 51397 return reduce(propertyNames, function(result, key) { 51398 result[key] = moddleElement.get(key); 51399 return result; 51400 }, {}); 51401 } 51402 51403 function setModdleProperties(moddleElement, properties) { 51404 forEach(properties, function(value, key) { 51405 moddleElement.set(key, value); 51406 }); 51407 } 51408 51409 function getAllDataObjectReferences(dataObject, elementRegistry) { 51410 return elementRegistry.filter(function(element) { 51411 return ( 51412 is$1(element, 'bpmn:DataObjectReference') && 51413 getBusinessObject(element).dataObjectRef === dataObject 51414 ); 51415 }); 51416 } 51417 51418 var DEFAULT_FLOW = 'default', 51419 ID = 'id', 51420 DI = 'di'; 51421 51422 var NULL_DIMENSIONS$1 = { 51423 width: 0, 51424 height: 0 51425 }; 51426 51427 /** 51428 * A handler that implements a BPMN 2.0 property update. 51429 * 51430 * This should be used to set simple properties on elements with 51431 * an underlying BPMN business object. 51432 * 51433 * Use respective diagram-js provided handlers if you would 51434 * like to perform automated modeling. 51435 */ 51436 function UpdatePropertiesHandler( 51437 elementRegistry, moddle, translate, 51438 modeling, textRenderer) { 51439 51440 this._elementRegistry = elementRegistry; 51441 this._moddle = moddle; 51442 this._translate = translate; 51443 this._modeling = modeling; 51444 this._textRenderer = textRenderer; 51445 } 51446 51447 UpdatePropertiesHandler.$inject = [ 51448 'elementRegistry', 51449 'moddle', 51450 'translate', 51451 'modeling', 51452 'textRenderer' 51453 ]; 51454 51455 51456 // api ////////////////////// 51457 51458 /** 51459 * Updates a BPMN element with a list of new properties 51460 * 51461 * @param {Object} context 51462 * @param {djs.model.Base} context.element the element to update 51463 * @param {Object} context.properties a list of properties to set on the element's 51464 * businessObject (the BPMN model element) 51465 * 51466 * @return {Array<djs.model.Base>} the updated element 51467 */ 51468 UpdatePropertiesHandler.prototype.execute = function(context) { 51469 51470 var element = context.element, 51471 changed = [ element ], 51472 translate = this._translate; 51473 51474 if (!element) { 51475 throw new Error(translate('element required')); 51476 } 51477 51478 var elementRegistry = this._elementRegistry, 51479 ids = this._moddle.ids; 51480 51481 var businessObject = element.businessObject, 51482 properties = unwrapBusinessObjects(context.properties), 51483 oldProperties = context.oldProperties || getProperties(businessObject, properties); 51484 51485 if (isIdChange(properties, businessObject)) { 51486 ids.unclaim(businessObject[ID]); 51487 51488 elementRegistry.updateId(element, properties[ID]); 51489 51490 ids.claim(properties[ID], businessObject); 51491 } 51492 51493 // correctly indicate visual changes on default flow updates 51494 if (DEFAULT_FLOW in properties) { 51495 51496 if (properties[DEFAULT_FLOW]) { 51497 changed.push(elementRegistry.get(properties[DEFAULT_FLOW].id)); 51498 } 51499 51500 if (businessObject[DEFAULT_FLOW]) { 51501 changed.push(elementRegistry.get(businessObject[DEFAULT_FLOW].id)); 51502 } 51503 } 51504 51505 // update properties 51506 setProperties(businessObject, properties); 51507 51508 // store old values 51509 context.oldProperties = oldProperties; 51510 context.changed = changed; 51511 51512 // indicate changed on objects affected by the update 51513 return changed; 51514 }; 51515 51516 51517 UpdatePropertiesHandler.prototype.postExecute = function(context) { 51518 var element = context.element, 51519 label = element.label; 51520 51521 var text = label && getBusinessObject(label).name; 51522 51523 if (!text) { 51524 return; 51525 } 51526 51527 // get layouted text bounds and resize external 51528 // external label accordingly 51529 var newLabelBounds = this._textRenderer.getExternalLabelBounds(label, text); 51530 51531 this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS$1); 51532 }; 51533 51534 /** 51535 * Reverts the update on a BPMN elements properties. 51536 * 51537 * @param {Object} context 51538 * 51539 * @return {djs.model.Base} the updated element 51540 */ 51541 UpdatePropertiesHandler.prototype.revert = function(context) { 51542 51543 var element = context.element, 51544 properties = context.properties, 51545 oldProperties = context.oldProperties, 51546 businessObject = element.businessObject, 51547 elementRegistry = this._elementRegistry, 51548 ids = this._moddle.ids; 51549 51550 // update properties 51551 setProperties(businessObject, oldProperties); 51552 51553 if (isIdChange(properties, businessObject)) { 51554 ids.unclaim(properties[ID]); 51555 51556 elementRegistry.updateId(element, oldProperties[ID]); 51557 51558 ids.claim(oldProperties[ID], businessObject); 51559 } 51560 51561 return context.changed; 51562 }; 51563 51564 51565 function isIdChange(properties, businessObject) { 51566 return ID in properties && properties[ID] !== businessObject[ID]; 51567 } 51568 51569 51570 function getProperties(businessObject, properties) { 51571 var propertyNames = keys(properties); 51572 51573 return reduce(propertyNames, function(result, key) { 51574 51575 // handle DI separately 51576 if (key !== DI) { 51577 result[key] = businessObject.get(key); 51578 } else { 51579 result[key] = getDiProperties(businessObject.di, keys(properties.di)); 51580 } 51581 51582 return result; 51583 }, {}); 51584 } 51585 51586 51587 function getDiProperties(di, propertyNames) { 51588 return reduce(propertyNames, function(result, key) { 51589 result[key] = di.get(key); 51590 51591 return result; 51592 }, {}); 51593 } 51594 51595 51596 function setProperties(businessObject, properties) { 51597 forEach(properties, function(value, key) { 51598 51599 if (key !== DI) { 51600 businessObject.set(key, value); 51601 } else { 51602 51603 // only update, if businessObject.di exists 51604 if (businessObject.di) { 51605 setDiProperties(businessObject.di, value); 51606 } 51607 } 51608 }); 51609 } 51610 51611 51612 function setDiProperties(di, properties) { 51613 forEach(properties, function(value, key) { 51614 di.set(key, value); 51615 }); 51616 } 51617 51618 51619 var referencePropertyNames = [ 'default' ]; 51620 51621 /** 51622 * Make sure we unwrap the actual business object 51623 * behind diagram element that may have been 51624 * passed as arguments. 51625 * 51626 * @param {Object} properties 51627 * 51628 * @return {Object} unwrappedProps 51629 */ 51630 function unwrapBusinessObjects(properties) { 51631 51632 var unwrappedProps = assign({}, properties); 51633 51634 referencePropertyNames.forEach(function(name) { 51635 if (name in properties) { 51636 unwrappedProps[name] = getBusinessObject(unwrappedProps[name]); 51637 } 51638 }); 51639 51640 return unwrappedProps; 51641 } 51642 51643 function UpdateCanvasRootHandler(canvas, modeling) { 51644 this._canvas = canvas; 51645 this._modeling = modeling; 51646 } 51647 51648 UpdateCanvasRootHandler.$inject = [ 51649 'canvas', 51650 'modeling' 51651 ]; 51652 51653 51654 UpdateCanvasRootHandler.prototype.execute = function(context) { 51655 51656 var canvas = this._canvas; 51657 51658 var newRoot = context.newRoot, 51659 newRootBusinessObject = newRoot.businessObject, 51660 oldRoot = canvas.getRootElement(), 51661 oldRootBusinessObject = oldRoot.businessObject, 51662 bpmnDefinitions = oldRootBusinessObject.$parent, 51663 diPlane = oldRootBusinessObject.di; 51664 51665 // (1) replace process old <> new root 51666 canvas.setRootElement(newRoot, true); 51667 51668 // (2) update root elements 51669 add(bpmnDefinitions.rootElements, newRootBusinessObject); 51670 newRootBusinessObject.$parent = bpmnDefinitions; 51671 51672 remove(bpmnDefinitions.rootElements, oldRootBusinessObject); 51673 oldRootBusinessObject.$parent = null; 51674 51675 // (3) wire di 51676 oldRootBusinessObject.di = null; 51677 51678 diPlane.bpmnElement = newRootBusinessObject; 51679 newRootBusinessObject.di = diPlane; 51680 51681 context.oldRoot = oldRoot; 51682 51683 // TODO(nikku): return changed elements? 51684 // return [ newRoot, oldRoot ]; 51685 }; 51686 51687 51688 UpdateCanvasRootHandler.prototype.revert = function(context) { 51689 51690 var canvas = this._canvas; 51691 51692 var newRoot = context.newRoot, 51693 newRootBusinessObject = newRoot.businessObject, 51694 oldRoot = context.oldRoot, 51695 oldRootBusinessObject = oldRoot.businessObject, 51696 bpmnDefinitions = newRootBusinessObject.$parent, 51697 diPlane = newRootBusinessObject.di; 51698 51699 // (1) replace process old <> new root 51700 canvas.setRootElement(oldRoot, true); 51701 51702 // (2) update root elements 51703 remove(bpmnDefinitions.rootElements, newRootBusinessObject); 51704 newRootBusinessObject.$parent = null; 51705 51706 add(bpmnDefinitions.rootElements, oldRootBusinessObject); 51707 oldRootBusinessObject.$parent = bpmnDefinitions; 51708 51709 // (3) wire di 51710 newRootBusinessObject.di = null; 51711 51712 diPlane.bpmnElement = oldRootBusinessObject; 51713 oldRootBusinessObject.di = diPlane; 51714 51715 // TODO(nikku): return changed elements? 51716 // return [ newRoot, oldRoot ]; 51717 }; 51718 51719 /** 51720 * A handler that allows us to add a new lane 51721 * above or below an existing one. 51722 * 51723 * @param {Modeling} modeling 51724 * @param {SpaceTool} spaceTool 51725 */ 51726 function AddLaneHandler(modeling, spaceTool) { 51727 this._modeling = modeling; 51728 this._spaceTool = spaceTool; 51729 } 51730 51731 AddLaneHandler.$inject = [ 51732 'modeling', 51733 'spaceTool' 51734 ]; 51735 51736 51737 AddLaneHandler.prototype.preExecute = function(context) { 51738 51739 var spaceTool = this._spaceTool, 51740 modeling = this._modeling; 51741 51742 var shape = context.shape, 51743 location = context.location; 51744 51745 var lanesRoot = getLanesRoot(shape); 51746 51747 var isRoot = lanesRoot === shape, 51748 laneParent = isRoot ? shape : shape.parent; 51749 51750 var existingChildLanes = getChildLanes(laneParent); 51751 51752 // (0) add a lane if we currently got none and are adding to root 51753 if (!existingChildLanes.length) { 51754 modeling.createShape({ type: 'bpmn:Lane' }, { 51755 x: shape.x + LANE_INDENTATION, 51756 y: shape.y, 51757 width: shape.width - LANE_INDENTATION, 51758 height: shape.height 51759 }, laneParent); 51760 } 51761 51762 // (1) collect affected elements to create necessary space 51763 var allAffected = []; 51764 51765 eachElement(lanesRoot, function(element) { 51766 allAffected.push(element); 51767 51768 // handle element labels in the diagram root 51769 if (element.label) { 51770 allAffected.push(element.label); 51771 } 51772 51773 if (element === shape) { 51774 return []; 51775 } 51776 51777 return filter(element.children, function(c) { 51778 return c !== shape; 51779 }); 51780 }); 51781 51782 var offset = location === 'top' ? -120 : 120, 51783 lanePosition = location === 'top' ? shape.y : shape.y + shape.height, 51784 spacePos = lanePosition + (location === 'top' ? 10 : -10), 51785 direction = location === 'top' ? 'n' : 's'; 51786 51787 var adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos); 51788 51789 spaceTool.makeSpace( 51790 adjustments.movingShapes, 51791 adjustments.resizingShapes, 51792 { x: 0, y: offset }, 51793 direction, 51794 spacePos 51795 ); 51796 51797 // (2) create new lane at open space 51798 context.newLane = modeling.createShape({ type: 'bpmn:Lane' }, { 51799 x: shape.x + (isRoot ? LANE_INDENTATION : 0), 51800 y: lanePosition - (location === 'top' ? 120 : 0), 51801 width: shape.width - (isRoot ? LANE_INDENTATION : 0), 51802 height: 120 51803 }, laneParent); 51804 }; 51805 51806 /** 51807 * A handler that splits a lane into a number of sub-lanes, 51808 * creating new sub lanes, if necessary. 51809 * 51810 * @param {Modeling} modeling 51811 */ 51812 function SplitLaneHandler(modeling, translate) { 51813 this._modeling = modeling; 51814 this._translate = translate; 51815 } 51816 51817 SplitLaneHandler.$inject = [ 51818 'modeling', 51819 'translate' 51820 ]; 51821 51822 51823 SplitLaneHandler.prototype.preExecute = function(context) { 51824 51825 var modeling = this._modeling, 51826 translate = this._translate; 51827 51828 var shape = context.shape, 51829 newLanesCount = context.count; 51830 51831 var childLanes = getChildLanes(shape), 51832 existingLanesCount = childLanes.length; 51833 51834 if (existingLanesCount > newLanesCount) { 51835 throw new Error(translate('more than {count} child lanes', { count: newLanesCount })); 51836 } 51837 51838 var newLanesHeight = Math.round(shape.height / newLanesCount); 51839 51840 // Iterate from top to bottom in child lane order, 51841 // resizing existing lanes and creating new ones 51842 // so that they split the parent proportionally. 51843 // 51844 // Due to rounding related errors, the bottom lane 51845 // needs to take up all the remaining space. 51846 var laneY, 51847 laneHeight, 51848 laneBounds, 51849 newLaneAttrs, 51850 idx; 51851 51852 for (idx = 0; idx < newLanesCount; idx++) { 51853 51854 laneY = shape.y + idx * newLanesHeight; 51855 51856 // if bottom lane 51857 if (idx === newLanesCount - 1) { 51858 laneHeight = shape.height - (newLanesHeight * idx); 51859 } else { 51860 laneHeight = newLanesHeight; 51861 } 51862 51863 laneBounds = { 51864 x: shape.x + LANE_INDENTATION, 51865 y: laneY, 51866 width: shape.width - LANE_INDENTATION, 51867 height: laneHeight 51868 }; 51869 51870 if (idx < existingLanesCount) { 51871 51872 // resize existing lane 51873 modeling.resizeShape(childLanes[idx], laneBounds); 51874 } else { 51875 51876 // create a new lane at position 51877 newLaneAttrs = { 51878 type: 'bpmn:Lane' 51879 }; 51880 51881 modeling.createShape(newLaneAttrs, laneBounds, shape); 51882 } 51883 } 51884 }; 51885 51886 /** 51887 * A handler that resizes a lane. 51888 * 51889 * @param {Modeling} modeling 51890 */ 51891 function ResizeLaneHandler(modeling, spaceTool) { 51892 this._modeling = modeling; 51893 this._spaceTool = spaceTool; 51894 } 51895 51896 ResizeLaneHandler.$inject = [ 51897 'modeling', 51898 'spaceTool' 51899 ]; 51900 51901 51902 ResizeLaneHandler.prototype.preExecute = function(context) { 51903 51904 var shape = context.shape, 51905 newBounds = context.newBounds, 51906 balanced = context.balanced; 51907 51908 if (balanced !== false) { 51909 this.resizeBalanced(shape, newBounds); 51910 } else { 51911 this.resizeSpace(shape, newBounds); 51912 } 51913 }; 51914 51915 51916 /** 51917 * Resize balanced, adjusting next / previous lane sizes. 51918 * 51919 * @param {djs.model.Shape} shape 51920 * @param {Bounds} newBounds 51921 */ 51922 ResizeLaneHandler.prototype.resizeBalanced = function(shape, newBounds) { 51923 51924 var modeling = this._modeling; 51925 51926 var resizeNeeded = computeLanesResize(shape, newBounds); 51927 51928 // resize the lane 51929 modeling.resizeShape(shape, newBounds); 51930 51931 // resize other lanes as needed 51932 resizeNeeded.forEach(function(r) { 51933 modeling.resizeShape(r.shape, r.newBounds); 51934 }); 51935 }; 51936 51937 51938 /** 51939 * Resize, making actual space and moving below / above elements. 51940 * 51941 * @param {djs.model.Shape} shape 51942 * @param {Bounds} newBounds 51943 */ 51944 ResizeLaneHandler.prototype.resizeSpace = function(shape, newBounds) { 51945 var spaceTool = this._spaceTool; 51946 51947 var shapeTrbl = asTRBL(shape), 51948 newTrbl = asTRBL(newBounds); 51949 51950 var trblDiff = substractTRBL(newTrbl, shapeTrbl); 51951 51952 var lanesRoot = getLanesRoot(shape); 51953 51954 var allAffected = [], 51955 allLanes = []; 51956 51957 eachElement(lanesRoot, function(element) { 51958 allAffected.push(element); 51959 51960 if (is$1(element, 'bpmn:Lane') || is$1(element, 'bpmn:Participant')) { 51961 allLanes.push(element); 51962 } 51963 51964 return element.children; 51965 }); 51966 51967 var change, 51968 spacePos, 51969 direction, 51970 offset, 51971 adjustments; 51972 51973 if (trblDiff.bottom || trblDiff.top) { 51974 51975 change = trblDiff.bottom || trblDiff.top; 51976 spacePos = shape.y + (trblDiff.bottom ? shape.height : 0) + (trblDiff.bottom ? -10 : 10); 51977 direction = trblDiff.bottom ? 's' : 'n'; 51978 51979 offset = trblDiff.top > 0 || trblDiff.bottom < 0 ? -change : change; 51980 51981 adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos); 51982 51983 spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: 0, y: change }, direction); 51984 } 51985 51986 51987 if (trblDiff.left || trblDiff.right) { 51988 51989 change = trblDiff.right || trblDiff.left; 51990 spacePos = shape.x + (trblDiff.right ? shape.width : 0) + (trblDiff.right ? -10 : 100); 51991 direction = trblDiff.right ? 'e' : 'w'; 51992 51993 offset = trblDiff.left > 0 || trblDiff.right < 0 ? -change : change; 51994 51995 adjustments = spaceTool.calculateAdjustments(allLanes, 'x', offset, spacePos); 51996 51997 spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: change, y: 0 }, direction); 51998 } 51999 }; 52000 52001 var FLOW_NODE_REFS_ATTR = 'flowNodeRef', 52002 LANES_ATTR = 'lanes'; 52003 52004 52005 /** 52006 * A handler that updates lane refs on changed elements 52007 */ 52008 function UpdateFlowNodeRefsHandler(elementRegistry) { 52009 this._elementRegistry = elementRegistry; 52010 } 52011 52012 UpdateFlowNodeRefsHandler.$inject = [ 52013 'elementRegistry' 52014 ]; 52015 52016 52017 UpdateFlowNodeRefsHandler.prototype.computeUpdates = function(flowNodeShapes, laneShapes) { 52018 52019 var handledNodes = []; 52020 52021 var updates = []; 52022 52023 var participantCache = {}; 52024 52025 var allFlowNodeShapes = []; 52026 52027 function isInLaneShape(element, laneShape) { 52028 52029 var laneTrbl = asTRBL(laneShape); 52030 52031 var elementMid = { 52032 x: element.x + element.width / 2, 52033 y: element.y + element.height / 2 52034 }; 52035 52036 return elementMid.x > laneTrbl.left && 52037 elementMid.x < laneTrbl.right && 52038 elementMid.y > laneTrbl.top && 52039 elementMid.y < laneTrbl.bottom; 52040 } 52041 52042 function addFlowNodeShape(flowNodeShape) { 52043 if (handledNodes.indexOf(flowNodeShape) === -1) { 52044 allFlowNodeShapes.push(flowNodeShape); 52045 handledNodes.push(flowNodeShape); 52046 } 52047 } 52048 52049 function getAllLaneShapes(flowNodeShape) { 52050 52051 var root = getLanesRoot(flowNodeShape); 52052 52053 if (!participantCache[root.id]) { 52054 participantCache[root.id] = collectLanes(root); 52055 } 52056 52057 return participantCache[root.id]; 52058 } 52059 52060 function getNewLanes(flowNodeShape) { 52061 if (!flowNodeShape.parent) { 52062 return []; 52063 } 52064 52065 var allLaneShapes = getAllLaneShapes(flowNodeShape); 52066 52067 return allLaneShapes.filter(function(l) { 52068 return isInLaneShape(flowNodeShape, l); 52069 }).map(function(shape) { 52070 return shape.businessObject; 52071 }); 52072 } 52073 52074 laneShapes.forEach(function(laneShape) { 52075 var root = getLanesRoot(laneShape); 52076 52077 if (!root || handledNodes.indexOf(root) !== -1) { 52078 return; 52079 } 52080 52081 var children = root.children.filter(function(c) { 52082 return is$1(c, 'bpmn:FlowNode'); 52083 }); 52084 52085 children.forEach(addFlowNodeShape); 52086 52087 handledNodes.push(root); 52088 }); 52089 52090 flowNodeShapes.forEach(addFlowNodeShape); 52091 52092 52093 allFlowNodeShapes.forEach(function(flowNodeShape) { 52094 52095 var flowNode = flowNodeShape.businessObject; 52096 52097 var lanes = flowNode.get(LANES_ATTR), 52098 remove = lanes.slice(), 52099 add = getNewLanes(flowNodeShape); 52100 52101 updates.push({ flowNode: flowNode, remove: remove, add: add }); 52102 }); 52103 52104 laneShapes.forEach(function(laneShape) { 52105 52106 var lane = laneShape.businessObject; 52107 52108 // lane got removed XX-) 52109 if (!laneShape.parent) { 52110 lane.get(FLOW_NODE_REFS_ATTR).forEach(function(flowNode) { 52111 updates.push({ flowNode: flowNode, remove: [ lane ], add: [] }); 52112 }); 52113 } 52114 }); 52115 52116 return updates; 52117 }; 52118 52119 UpdateFlowNodeRefsHandler.prototype.execute = function(context) { 52120 52121 var updates = context.updates; 52122 52123 if (!updates) { 52124 updates = context.updates = this.computeUpdates(context.flowNodeShapes, context.laneShapes); 52125 } 52126 52127 52128 updates.forEach(function(update) { 52129 52130 var flowNode = update.flowNode, 52131 lanes = flowNode.get(LANES_ATTR); 52132 52133 // unwire old 52134 update.remove.forEach(function(oldLane) { 52135 remove(lanes, oldLane); 52136 remove(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode); 52137 }); 52138 52139 // wire new 52140 update.add.forEach(function(newLane) { 52141 add(lanes, newLane); 52142 add(newLane.get(FLOW_NODE_REFS_ATTR), flowNode); 52143 }); 52144 }); 52145 52146 // TODO(nikku): return changed elements 52147 // return [ ... ]; 52148 }; 52149 52150 52151 UpdateFlowNodeRefsHandler.prototype.revert = function(context) { 52152 52153 var updates = context.updates; 52154 52155 updates.forEach(function(update) { 52156 52157 var flowNode = update.flowNode, 52158 lanes = flowNode.get(LANES_ATTR); 52159 52160 // unwire new 52161 update.add.forEach(function(newLane) { 52162 remove(lanes, newLane); 52163 remove(newLane.get(FLOW_NODE_REFS_ATTR), flowNode); 52164 }); 52165 52166 // wire old 52167 update.remove.forEach(function(oldLane) { 52168 add(lanes, oldLane); 52169 add(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode); 52170 }); 52171 }); 52172 52173 // TODO(nikku): return changed elements 52174 // return [ ... ]; 52175 }; 52176 52177 function IdClaimHandler(moddle) { 52178 this._moddle = moddle; 52179 } 52180 52181 IdClaimHandler.$inject = [ 'moddle' ]; 52182 52183 52184 IdClaimHandler.prototype.execute = function(context) { 52185 var ids = this._moddle.ids, 52186 id = context.id, 52187 element = context.element, 52188 claiming = context.claiming; 52189 52190 if (claiming) { 52191 ids.claim(id, element); 52192 } else { 52193 ids.unclaim(id); 52194 } 52195 }; 52196 52197 /** 52198 * Command revert implementation. 52199 */ 52200 IdClaimHandler.prototype.revert = function(context) { 52201 var ids = this._moddle.ids, 52202 id = context.id, 52203 element = context.element, 52204 claiming = context.claiming; 52205 52206 if (claiming) { 52207 ids.unclaim(id); 52208 } else { 52209 ids.claim(id, element); 52210 } 52211 }; 52212 52213 var DEFAULT_COLORS = { 52214 fill: undefined, 52215 stroke: undefined 52216 }; 52217 52218 52219 function SetColorHandler(commandStack) { 52220 this._commandStack = commandStack; 52221 52222 this._normalizeColor = function(color) { 52223 52224 // Remove color for falsy values. 52225 if (!color) { 52226 return undefined; 52227 } 52228 52229 if (isString(color)) { 52230 var hexColor = colorToHex(color); 52231 52232 if (hexColor) { 52233 return hexColor; 52234 } 52235 } 52236 52237 throw new Error('invalid color value: ' + color); 52238 }; 52239 } 52240 52241 SetColorHandler.$inject = [ 52242 'commandStack' 52243 ]; 52244 52245 52246 SetColorHandler.prototype.postExecute = function(context) { 52247 var elements = context.elements, 52248 colors = context.colors || DEFAULT_COLORS; 52249 52250 var self = this; 52251 52252 var di = {}; 52253 52254 if ('fill' in colors) { 52255 assign(di, { 52256 'background-color': this._normalizeColor(colors.fill) }); 52257 } 52258 52259 if ('stroke' in colors) { 52260 assign(di, { 52261 'border-color': this._normalizeColor(colors.stroke) }); 52262 } 52263 52264 forEach(elements, function(element) { 52265 var assignedDi = isConnection$3(element) ? pick(di, [ 'border-color' ]) : di; 52266 52267 // TODO @barmac: remove once we drop bpmn.io properties 52268 ensureLegacySupport(assignedDi); 52269 52270 self._commandStack.execute('element.updateProperties', { 52271 element: element, 52272 properties: { 52273 di: assignedDi 52274 } 52275 }); 52276 }); 52277 52278 }; 52279 52280 /** 52281 * Convert color from rgb(a)/hsl to hex. Returns `null` for unknown color names and for colors 52282 * with alpha less than 1.0. This depends on `<canvas>` serialization of the `context.fillStyle`. 52283 * Cf. https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle 52284 * 52285 * @example 52286 * ```js 52287 * var color = 'fuchsia'; 52288 * console.log(colorToHex(color)); 52289 * // "#ff00ff" 52290 * color = 'rgba(1,2,3,0.4)'; 52291 * console.log(colorToHex(color)); 52292 * // null 52293 * ``` 52294 * 52295 * @param {string} color 52296 * @returns {string|null} 52297 */ 52298 function colorToHex(color) { 52299 var context = document.createElement('canvas').getContext('2d'); 52300 52301 // (0) Start with transparent to account for browser default values. 52302 context.fillStyle = 'transparent'; 52303 52304 // (1) Assign color so that it's serialized. 52305 context.fillStyle = color; 52306 52307 // (2) Return null for non-hex serialization result. 52308 return /^#[0-9a-fA-F]{6}$/.test(context.fillStyle) ? context.fillStyle : null; 52309 } 52310 52311 function isConnection$3(element) { 52312 return !!element.waypoints; 52313 } 52314 52315 /** 52316 * Add legacy properties if required. 52317 * @param {{ 'border-color': string?, 'background-color': string? }} di 52318 */ 52319 function ensureLegacySupport(di) { 52320 if ('border-color' in di) { 52321 di.stroke = di['border-color']; 52322 } 52323 52324 if ('background-color' in di) { 52325 di.fill = di['background-color']; 52326 } 52327 } 52328 52329 var NULL_DIMENSIONS = { 52330 width: 0, 52331 height: 0 52332 }; 52333 52334 52335 /** 52336 * A handler that updates the text of a BPMN element. 52337 */ 52338 function UpdateLabelHandler(modeling, textRenderer) { 52339 52340 /** 52341 * Set the label and return the changed elements. 52342 * 52343 * Element parameter can be label itself or connection (i.e. sequence flow). 52344 * 52345 * @param {djs.model.Base} element 52346 * @param {string} text 52347 */ 52348 function setText(element, text) { 52349 52350 // external label if present 52351 var label = element.label || element; 52352 52353 var labelTarget = element.labelTarget || element; 52354 52355 setLabel(label, text); 52356 52357 return [ label, labelTarget ]; 52358 } 52359 52360 function preExecute(ctx) { 52361 var element = ctx.element, 52362 businessObject = element.businessObject, 52363 newLabel = ctx.newLabel; 52364 52365 if (!isLabel$6(element) 52366 && isLabelExternal(element) 52367 && !hasExternalLabel(element) 52368 && !isEmptyText(newLabel)) { 52369 52370 // create label 52371 var paddingTop = 7; 52372 52373 var labelCenter = getExternalLabelMid(element); 52374 52375 labelCenter = { 52376 x: labelCenter.x, 52377 y: labelCenter.y + paddingTop 52378 }; 52379 52380 modeling.createLabel(element, labelCenter, { 52381 id: businessObject.id + '_label', 52382 businessObject: businessObject 52383 }); 52384 } 52385 } 52386 52387 function execute(ctx) { 52388 ctx.oldLabel = getLabel(ctx.element); 52389 return setText(ctx.element, ctx.newLabel); 52390 } 52391 52392 function revert(ctx) { 52393 return setText(ctx.element, ctx.oldLabel); 52394 } 52395 52396 function postExecute(ctx) { 52397 var element = ctx.element, 52398 label = element.label || element, 52399 newLabel = ctx.newLabel, 52400 newBounds = ctx.newBounds, 52401 hints = ctx.hints || {}; 52402 52403 // ignore internal labels for elements except text annotations 52404 if (!isLabel$6(label) && !is$1(label, 'bpmn:TextAnnotation')) { 52405 return; 52406 } 52407 52408 if (isLabel$6(label) && isEmptyText(newLabel)) { 52409 52410 if (hints.removeShape !== false) { 52411 modeling.removeShape(label, { unsetLabel: false }); 52412 } 52413 52414 return; 52415 } 52416 52417 var text = getLabel(label); 52418 52419 // resize element based on label _or_ pre-defined bounds 52420 if (typeof newBounds === 'undefined') { 52421 newBounds = textRenderer.getExternalLabelBounds(label, text); 52422 } 52423 52424 // setting newBounds to false or _null_ will 52425 // disable the postExecute resize operation 52426 if (newBounds) { 52427 modeling.resizeShape(label, newBounds, NULL_DIMENSIONS); 52428 } 52429 } 52430 52431 // API 52432 52433 this.preExecute = preExecute; 52434 this.execute = execute; 52435 this.revert = revert; 52436 this.postExecute = postExecute; 52437 } 52438 52439 UpdateLabelHandler.$inject = [ 52440 'modeling', 52441 'textRenderer' 52442 ]; 52443 52444 52445 // helpers /////////////////////// 52446 52447 function isEmptyText(label) { 52448 return !label || !label.trim(); 52449 } 52450 52451 /** 52452 * BPMN 2.0 modeling features activator 52453 * 52454 * @param {EventBus} eventBus 52455 * @param {ElementFactory} elementFactory 52456 * @param {CommandStack} commandStack 52457 * @param {BpmnRules} bpmnRules 52458 */ 52459 function Modeling( 52460 eventBus, elementFactory, commandStack, 52461 bpmnRules) { 52462 52463 Modeling$1.call(this, eventBus, elementFactory, commandStack); 52464 52465 this._bpmnRules = bpmnRules; 52466 } 52467 52468 inherits$1(Modeling, Modeling$1); 52469 52470 Modeling.$inject = [ 52471 'eventBus', 52472 'elementFactory', 52473 'commandStack', 52474 'bpmnRules' 52475 ]; 52476 52477 52478 Modeling.prototype.getHandlers = function() { 52479 var handlers = Modeling$1.prototype.getHandlers.call(this); 52480 52481 handlers['element.updateModdleProperties'] = UpdateModdlePropertiesHandler; 52482 handlers['element.updateProperties'] = UpdatePropertiesHandler; 52483 handlers['canvas.updateRoot'] = UpdateCanvasRootHandler; 52484 handlers['lane.add'] = AddLaneHandler; 52485 handlers['lane.resize'] = ResizeLaneHandler; 52486 handlers['lane.split'] = SplitLaneHandler; 52487 handlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler; 52488 handlers['id.updateClaim'] = IdClaimHandler; 52489 handlers['element.setColor'] = SetColorHandler; 52490 handlers['element.updateLabel'] = UpdateLabelHandler; 52491 52492 return handlers; 52493 }; 52494 52495 52496 Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) { 52497 this._commandStack.execute('element.updateLabel', { 52498 element: element, 52499 newLabel: newLabel, 52500 newBounds: newBounds, 52501 hints: hints || {} 52502 }); 52503 }; 52504 52505 52506 Modeling.prototype.connect = function(source, target, attrs, hints) { 52507 52508 var bpmnRules = this._bpmnRules; 52509 52510 if (!attrs) { 52511 attrs = bpmnRules.canConnect(source, target); 52512 } 52513 52514 if (!attrs) { 52515 return; 52516 } 52517 52518 return this.createConnection(source, target, attrs, source.parent, hints); 52519 }; 52520 52521 52522 Modeling.prototype.updateModdleProperties = function(element, moddleElement, properties) { 52523 this._commandStack.execute('element.updateModdleProperties', { 52524 element: element, 52525 moddleElement: moddleElement, 52526 properties: properties 52527 }); 52528 }; 52529 52530 Modeling.prototype.updateProperties = function(element, properties) { 52531 this._commandStack.execute('element.updateProperties', { 52532 element: element, 52533 properties: properties 52534 }); 52535 }; 52536 52537 Modeling.prototype.resizeLane = function(laneShape, newBounds, balanced) { 52538 this._commandStack.execute('lane.resize', { 52539 shape: laneShape, 52540 newBounds: newBounds, 52541 balanced: balanced 52542 }); 52543 }; 52544 52545 Modeling.prototype.addLane = function(targetLaneShape, location) { 52546 var context = { 52547 shape: targetLaneShape, 52548 location: location 52549 }; 52550 52551 this._commandStack.execute('lane.add', context); 52552 52553 return context.newLane; 52554 }; 52555 52556 Modeling.prototype.splitLane = function(targetLane, count) { 52557 this._commandStack.execute('lane.split', { 52558 shape: targetLane, 52559 count: count 52560 }); 52561 }; 52562 52563 /** 52564 * Transform the current diagram into a collaboration. 52565 * 52566 * @return {djs.model.Root} the new root element 52567 */ 52568 Modeling.prototype.makeCollaboration = function() { 52569 52570 var collaborationElement = this._create('root', { 52571 type: 'bpmn:Collaboration' 52572 }); 52573 52574 var context = { 52575 newRoot: collaborationElement 52576 }; 52577 52578 this._commandStack.execute('canvas.updateRoot', context); 52579 52580 return collaborationElement; 52581 }; 52582 52583 Modeling.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) { 52584 52585 this._commandStack.execute('lane.updateRefs', { 52586 flowNodeShapes: flowNodeShapes, 52587 laneShapes: laneShapes 52588 }); 52589 }; 52590 52591 /** 52592 * Transform the current diagram into a process. 52593 * 52594 * @return {djs.model.Root} the new root element 52595 */ 52596 Modeling.prototype.makeProcess = function() { 52597 52598 var processElement = this._create('root', { 52599 type: 'bpmn:Process' 52600 }); 52601 52602 var context = { 52603 newRoot: processElement 52604 }; 52605 52606 this._commandStack.execute('canvas.updateRoot', context); 52607 }; 52608 52609 52610 Modeling.prototype.claimId = function(id, moddleElement) { 52611 this._commandStack.execute('id.updateClaim', { 52612 id: id, 52613 element: moddleElement, 52614 claiming: true 52615 }); 52616 }; 52617 52618 52619 Modeling.prototype.unclaimId = function(id, moddleElement) { 52620 this._commandStack.execute('id.updateClaim', { 52621 id: id, 52622 element: moddleElement 52623 }); 52624 }; 52625 52626 Modeling.prototype.setColor = function(elements, colors) { 52627 if (!elements.length) { 52628 elements = [ elements ]; 52629 } 52630 52631 this._commandStack.execute('element.setColor', { 52632 elements: elements, 52633 colors: colors 52634 }); 52635 }; 52636 52637 /** 52638 * A base connection layouter implementation 52639 * that layouts the connection by directly connecting 52640 * mid(source) + mid(target). 52641 */ 52642 function BaseLayouter() {} 52643 52644 52645 /** 52646 * Return the new layouted waypoints for the given connection. 52647 * 52648 * The connection passed is still unchanged; you may figure out about 52649 * the new connection start / end via the layout hints provided. 52650 * 52651 * @param {djs.model.Connection} connection 52652 * @param {Object} [hints] 52653 * @param {Point} [hints.connectionStart] 52654 * @param {Point} [hints.connectionEnd] 52655 * @param {Point} [hints.source] 52656 * @param {Point} [hints.target] 52657 * 52658 * @return {Array<Point>} the layouted connection waypoints 52659 */ 52660 BaseLayouter.prototype.layoutConnection = function(connection, hints) { 52661 52662 hints = hints || {}; 52663 52664 return [ 52665 hints.connectionStart || getMid(hints.source || connection.source), 52666 hints.connectionEnd || getMid(hints.target || connection.target) 52667 ]; 52668 }; 52669 52670 var MIN_SEGMENT_LENGTH = 20, 52671 POINT_ORIENTATION_PADDING = 5; 52672 52673 var round$1 = Math.round; 52674 52675 var INTERSECTION_THRESHOLD = 20, 52676 ORIENTATION_THRESHOLD = { 52677 'h:h': 20, 52678 'v:v': 20, 52679 'h:v': -10, 52680 'v:h': -10 52681 }; 52682 52683 function needsTurn(orientation, startDirection) { 52684 return !{ 52685 t: /top/, 52686 r: /right/, 52687 b: /bottom/, 52688 l: /left/, 52689 h: /./, 52690 v: /./ 52691 }[startDirection].test(orientation); 52692 } 52693 52694 function canLayoutStraight(direction, targetOrientation) { 52695 return { 52696 t: /top/, 52697 r: /right/, 52698 b: /bottom/, 52699 l: /left/, 52700 h: /left|right/, 52701 v: /top|bottom/ 52702 }[direction].test(targetOrientation); 52703 } 52704 52705 function getSegmentBendpoints(a, b, directions) { 52706 var orientation = getOrientation(b, a, POINT_ORIENTATION_PADDING); 52707 52708 var startDirection = directions.split(':')[0]; 52709 52710 var xmid = round$1((b.x - a.x) / 2 + a.x), 52711 ymid = round$1((b.y - a.y) / 2 + a.y); 52712 52713 var segmentEnd, segmentDirections; 52714 52715 var layoutStraight = canLayoutStraight(startDirection, orientation), 52716 layoutHorizontal = /h|r|l/.test(startDirection), 52717 layoutTurn = false; 52718 52719 var turnNextDirections = false; 52720 52721 if (layoutStraight) { 52722 segmentEnd = layoutHorizontal ? { x: xmid, y: a.y } : { x: a.x, y: ymid }; 52723 52724 segmentDirections = layoutHorizontal ? 'h:h' : 'v:v'; 52725 } else { 52726 layoutTurn = needsTurn(orientation, startDirection); 52727 52728 segmentDirections = layoutHorizontal ? 'h:v' : 'v:h'; 52729 52730 if (layoutTurn) { 52731 52732 if (layoutHorizontal) { 52733 turnNextDirections = ymid === a.y; 52734 52735 segmentEnd = { 52736 x: a.x + MIN_SEGMENT_LENGTH * (/l/.test(startDirection) ? -1 : 1), 52737 y: turnNextDirections ? ymid + MIN_SEGMENT_LENGTH : ymid 52738 }; 52739 } else { 52740 turnNextDirections = xmid === a.x; 52741 52742 segmentEnd = { 52743 x: turnNextDirections ? xmid + MIN_SEGMENT_LENGTH : xmid, 52744 y: a.y + MIN_SEGMENT_LENGTH * (/t/.test(startDirection) ? -1 : 1) 52745 }; 52746 } 52747 52748 } else { 52749 segmentEnd = { 52750 x: xmid, 52751 y: ymid 52752 }; 52753 } 52754 } 52755 52756 return { 52757 waypoints: getBendpoints(a, segmentEnd, segmentDirections).concat(segmentEnd), 52758 directions: segmentDirections, 52759 turnNextDirections: turnNextDirections 52760 }; 52761 } 52762 52763 function getStartSegment(a, b, directions) { 52764 return getSegmentBendpoints(a, b, directions); 52765 } 52766 52767 function getEndSegment(a, b, directions) { 52768 var invertedSegment = getSegmentBendpoints(b, a, invertDirections(directions)); 52769 52770 return { 52771 waypoints: invertedSegment.waypoints.slice().reverse(), 52772 directions: invertDirections(invertedSegment.directions), 52773 turnNextDirections: invertedSegment.turnNextDirections 52774 }; 52775 } 52776 52777 function getMidSegment(startSegment, endSegment) { 52778 52779 var startDirection = startSegment.directions.split(':')[1], 52780 endDirection = endSegment.directions.split(':')[0]; 52781 52782 if (startSegment.turnNextDirections) { 52783 startDirection = startDirection == 'h' ? 'v' : 'h'; 52784 } 52785 52786 if (endSegment.turnNextDirections) { 52787 endDirection = endDirection == 'h' ? 'v' : 'h'; 52788 } 52789 52790 var directions = startDirection + ':' + endDirection; 52791 52792 var bendpoints = getBendpoints( 52793 startSegment.waypoints[startSegment.waypoints.length - 1], 52794 endSegment.waypoints[0], 52795 directions 52796 ); 52797 52798 return { 52799 waypoints: bendpoints, 52800 directions: directions 52801 }; 52802 } 52803 52804 function invertDirections(directions) { 52805 return directions.split(':').reverse().join(':'); 52806 } 52807 52808 /** 52809 * Handle simple layouts with maximum two bendpoints. 52810 */ 52811 function getSimpleBendpoints(a, b, directions) { 52812 52813 var xmid = round$1((b.x - a.x) / 2 + a.x), 52814 ymid = round$1((b.y - a.y) / 2 + a.y); 52815 52816 // one point, right or left from a 52817 if (directions === 'h:v') { 52818 return [ { x: b.x, y: a.y } ]; 52819 } 52820 52821 // one point, above or below a 52822 if (directions === 'v:h') { 52823 return [ { x: a.x, y: b.y } ]; 52824 } 52825 52826 // vertical segment between a and b 52827 if (directions === 'h:h') { 52828 return [ 52829 { x: xmid, y: a.y }, 52830 { x: xmid, y: b.y } 52831 ]; 52832 } 52833 52834 // horizontal segment between a and b 52835 if (directions === 'v:v') { 52836 return [ 52837 { x: a.x, y: ymid }, 52838 { x: b.x, y: ymid } 52839 ]; 52840 } 52841 52842 throw new Error('invalid directions: can only handle varians of [hv]:[hv]'); 52843 } 52844 52845 52846 /** 52847 * Returns the mid points for a manhattan connection between two points. 52848 * 52849 * @example h:h (horizontal:horizontal) 52850 * 52851 * [a]----[x] 52852 * | 52853 * [x]----[b] 52854 * 52855 * @example h:v (horizontal:vertical) 52856 * 52857 * [a]----[x] 52858 * | 52859 * [b] 52860 * 52861 * @example h:r (horizontal:right) 52862 * 52863 * [a]----[x] 52864 * | 52865 * [b]-[x] 52866 * 52867 * @param {Point} a 52868 * @param {Point} b 52869 * @param {string} directions 52870 * 52871 * @return {Array<Point>} 52872 */ 52873 function getBendpoints(a, b, directions) { 52874 directions = directions || 'h:h'; 52875 52876 if (!isValidDirections(directions)) { 52877 throw new Error( 52878 'unknown directions: <' + directions + '>: ' + 52879 'must be specified as <start>:<end> ' + 52880 'with start/end in { h,v,t,r,b,l }' 52881 ); 52882 } 52883 52884 // compute explicit directions, involving trbl dockings 52885 // using a three segmented layouting algorithm 52886 if (isExplicitDirections(directions)) { 52887 var startSegment = getStartSegment(a, b, directions), 52888 endSegment = getEndSegment(a, b, directions), 52889 midSegment = getMidSegment(startSegment, endSegment); 52890 52891 return [].concat( 52892 startSegment.waypoints, 52893 midSegment.waypoints, 52894 endSegment.waypoints 52895 ); 52896 } 52897 52898 // handle simple [hv]:[hv] cases that can be easily computed 52899 return getSimpleBendpoints(a, b, directions); 52900 } 52901 52902 /** 52903 * Create a connection between the two points according 52904 * to the manhattan layout (only horizontal and vertical) edges. 52905 * 52906 * @param {Point} a 52907 * @param {Point} b 52908 * 52909 * @param {string} [directions='h:h'] specifies manhattan directions for each point as {adirection}:{bdirection}. 52910 A directionfor a point is either `h` (horizontal) or `v` (vertical) 52911 * 52912 * @return {Array<Point>} 52913 */ 52914 function connectPoints(a, b, directions) { 52915 52916 var points = getBendpoints(a, b, directions); 52917 52918 points.unshift(a); 52919 points.push(b); 52920 52921 return withoutRedundantPoints(points); 52922 } 52923 52924 52925 /** 52926 * Connect two rectangles using a manhattan layouted connection. 52927 * 52928 * @param {Bounds} source source rectangle 52929 * @param {Bounds} target target rectangle 52930 * @param {Point} [start] source docking 52931 * @param {Point} [end] target docking 52932 * 52933 * @param {Object} [hints] 52934 * @param {string} [hints.preserveDocking=source] preserve docking on selected side 52935 * @param {Array<string>} [hints.preferredLayouts] 52936 * @param {Point|boolean} [hints.connectionStart] whether the start changed 52937 * @param {Point|boolean} [hints.connectionEnd] whether the end changed 52938 * 52939 * @return {Array<Point>} connection points 52940 */ 52941 function connectRectangles(source, target, start, end, hints) { 52942 52943 var preferredLayouts = hints && hints.preferredLayouts || []; 52944 52945 var preferredLayout = without(preferredLayouts, 'straight')[0] || 'h:h'; 52946 52947 var threshold = ORIENTATION_THRESHOLD[preferredLayout] || 0; 52948 52949 var orientation = getOrientation(source, target, threshold); 52950 52951 var directions = getDirections(orientation, preferredLayout); 52952 52953 start = start || getMid(source); 52954 end = end || getMid(target); 52955 52956 var directionSplit = directions.split(':'); 52957 52958 // compute actual docking points for start / end 52959 // this ensures we properly layout only parts of the 52960 // connection that lies in between the two rectangles 52961 var startDocking = getDockingPoint(start, source, directionSplit[0], invertOrientation(orientation)), 52962 endDocking = getDockingPoint(end, target, directionSplit[1], orientation); 52963 52964 return connectPoints(startDocking, endDocking, directions); 52965 } 52966 52967 52968 /** 52969 * Repair the connection between two rectangles, of which one has been updated. 52970 * 52971 * @param {Bounds} source 52972 * @param {Bounds} target 52973 * @param {Point} [start] 52974 * @param {Point} [end] 52975 * @param {Array<Point>} [waypoints] 52976 * @param {Object} [hints] 52977 * @param {Array<string>} [hints.preferredLayouts] list of preferred layouts 52978 * @param {boolean} [hints.connectionStart] 52979 * @param {boolean} [hints.connectionEnd] 52980 * 52981 * @return {Array<Point>} repaired waypoints 52982 */ 52983 function repairConnection(source, target, start, end, waypoints, hints) { 52984 52985 if (isArray$2(start)) { 52986 waypoints = start; 52987 hints = end; 52988 52989 start = getMid(source); 52990 end = getMid(target); 52991 } 52992 52993 hints = assign({ preferredLayouts: [] }, hints); 52994 waypoints = waypoints || []; 52995 52996 var preferredLayouts = hints.preferredLayouts, 52997 preferStraight = preferredLayouts.indexOf('straight') !== -1, 52998 repairedWaypoints; 52999 53000 // just layout non-existing or simple connections 53001 // attempt to render straight lines, if required 53002 53003 // attempt to layout a straight line 53004 repairedWaypoints = preferStraight && tryLayoutStraight(source, target, start, end, hints); 53005 53006 if (repairedWaypoints) { 53007 return repairedWaypoints; 53008 } 53009 53010 // try to layout from end 53011 repairedWaypoints = hints.connectionEnd && tryRepairConnectionEnd(target, source, end, waypoints); 53012 53013 if (repairedWaypoints) { 53014 return repairedWaypoints; 53015 } 53016 53017 // try to layout from start 53018 repairedWaypoints = hints.connectionStart && tryRepairConnectionStart(source, target, start, waypoints); 53019 53020 if (repairedWaypoints) { 53021 return repairedWaypoints; 53022 } 53023 53024 // or whether nothing seems to have changed 53025 if (!hints.connectionStart && !hints.connectionEnd && waypoints && waypoints.length) { 53026 return waypoints; 53027 } 53028 53029 // simply reconnect if nothing else worked 53030 return connectRectangles(source, target, start, end, hints); 53031 } 53032 53033 53034 function inRange(a, start, end) { 53035 return a >= start && a <= end; 53036 } 53037 53038 function isInRange(axis, a, b) { 53039 var size = { 53040 x: 'width', 53041 y: 'height' 53042 }; 53043 53044 return inRange(a[axis], b[axis], b[axis] + b[size[axis]]); 53045 } 53046 53047 /** 53048 * Layout a straight connection 53049 * 53050 * @param {Bounds} source 53051 * @param {Bounds} target 53052 * @param {Point} start 53053 * @param {Point} end 53054 * @param {Object} [hints] 53055 * 53056 * @return {Array<Point>|null} waypoints if straight layout worked 53057 */ 53058 function tryLayoutStraight(source, target, start, end, hints) { 53059 var axis = {}, 53060 primaryAxis, 53061 orientation; 53062 53063 orientation = getOrientation(source, target); 53064 53065 // only layout a straight connection if shapes are 53066 // horizontally or vertically aligned 53067 if (!/^(top|bottom|left|right)$/.test(orientation)) { 53068 return null; 53069 } 53070 53071 if (/top|bottom/.test(orientation)) { 53072 primaryAxis = 'x'; 53073 } 53074 53075 if (/left|right/.test(orientation)) { 53076 primaryAxis = 'y'; 53077 } 53078 53079 if (hints.preserveDocking === 'target') { 53080 53081 if (!isInRange(primaryAxis, end, source)) { 53082 return null; 53083 } 53084 53085 axis[primaryAxis] = end[primaryAxis]; 53086 53087 return [ 53088 { 53089 x: axis.x !== undefined ? axis.x : start.x, 53090 y: axis.y !== undefined ? axis.y : start.y, 53091 original: { 53092 x: axis.x !== undefined ? axis.x : start.x, 53093 y: axis.y !== undefined ? axis.y : start.y 53094 } 53095 }, 53096 { 53097 x: end.x, 53098 y: end.y 53099 } 53100 ]; 53101 53102 } else { 53103 53104 if (!isInRange(primaryAxis, start, target)) { 53105 return null; 53106 } 53107 53108 axis[primaryAxis] = start[primaryAxis]; 53109 53110 return [ 53111 { 53112 x: start.x, 53113 y: start.y 53114 }, 53115 { 53116 x: axis.x !== undefined ? axis.x : end.x, 53117 y: axis.y !== undefined ? axis.y : end.y, 53118 original: { 53119 x: axis.x !== undefined ? axis.x : end.x, 53120 y: axis.y !== undefined ? axis.y : end.y 53121 } 53122 } 53123 ]; 53124 } 53125 53126 } 53127 53128 /** 53129 * Repair a connection from start. 53130 * 53131 * @param {Bounds} moved 53132 * @param {Bounds} other 53133 * @param {Point} newDocking 53134 * @param {Array<Point>} points originalPoints from moved to other 53135 * 53136 * @return {Array<Point>|null} the repaired points between the two rectangles 53137 */ 53138 function tryRepairConnectionStart(moved, other, newDocking, points) { 53139 return _tryRepairConnectionSide(moved, other, newDocking, points); 53140 } 53141 53142 /** 53143 * Repair a connection from end. 53144 * 53145 * @param {Bounds} moved 53146 * @param {Bounds} other 53147 * @param {Point} newDocking 53148 * @param {Array<Point>} points originalPoints from moved to other 53149 * 53150 * @return {Array<Point>|null} the repaired points between the two rectangles 53151 */ 53152 function tryRepairConnectionEnd(moved, other, newDocking, points) { 53153 var waypoints = points.slice().reverse(); 53154 53155 waypoints = _tryRepairConnectionSide(moved, other, newDocking, waypoints); 53156 53157 return waypoints ? waypoints.reverse() : null; 53158 } 53159 53160 /** 53161 * Repair a connection from one side that moved. 53162 * 53163 * @param {Bounds} moved 53164 * @param {Bounds} other 53165 * @param {Point} newDocking 53166 * @param {Array<Point>} points originalPoints from moved to other 53167 * 53168 * @return {Array<Point>} the repaired points between the two rectangles 53169 */ 53170 function _tryRepairConnectionSide(moved, other, newDocking, points) { 53171 53172 function needsRelayout(points) { 53173 if (points.length < 3) { 53174 return true; 53175 } 53176 53177 if (points.length > 4) { 53178 return false; 53179 } 53180 53181 // relayout if two points overlap 53182 // this is most likely due to 53183 return !!find(points, function(p, idx) { 53184 var q = points[idx - 1]; 53185 53186 return q && pointDistance(p, q) < 3; 53187 }); 53188 } 53189 53190 function repairBendpoint(candidate, oldPeer, newPeer) { 53191 53192 var alignment = pointsAligned(oldPeer, candidate); 53193 53194 switch (alignment) { 53195 case 'v': 53196 53197 // repair horizontal alignment 53198 return { x: newPeer.x, y: candidate.y }; 53199 case 'h': 53200 53201 // repair vertical alignment 53202 return { x: candidate.x, y: newPeer.y }; 53203 } 53204 53205 return { x: candidate.x, y: candidate. y }; 53206 } 53207 53208 function removeOverlapping(points, a, b) { 53209 var i; 53210 53211 for (i = points.length - 2; i !== 0; i--) { 53212 53213 // intersects (?) break, remove all bendpoints up to this one and relayout 53214 if (pointInRect(points[i], a, INTERSECTION_THRESHOLD) || 53215 pointInRect(points[i], b, INTERSECTION_THRESHOLD)) { 53216 53217 // return sliced old connection 53218 return points.slice(i); 53219 } 53220 } 53221 53222 return points; 53223 } 53224 53225 // (0) only repair what has layoutable bendpoints 53226 53227 // (1) if only one bendpoint and on shape moved onto other shapes axis 53228 // (horizontally / vertically), relayout 53229 53230 if (needsRelayout(points)) { 53231 return null; 53232 } 53233 53234 var oldDocking = points[0], 53235 newPoints = points.slice(), 53236 slicedPoints; 53237 53238 // (2) repair only last line segment and only if it was layouted before 53239 53240 newPoints[0] = newDocking; 53241 newPoints[1] = repairBendpoint(newPoints[1], oldDocking, newDocking); 53242 53243 53244 // (3) if shape intersects with any bendpoint after repair, 53245 // remove all segments up to this bendpoint and repair from there 53246 slicedPoints = removeOverlapping(newPoints, moved, other); 53247 53248 if (slicedPoints !== newPoints) { 53249 newPoints = _tryRepairConnectionSide(moved, other, newDocking, slicedPoints); 53250 } 53251 53252 // (4) do NOT repair if repaired bendpoints are aligned 53253 if (newPoints && pointsAligned(newPoints)) { 53254 return null; 53255 } 53256 53257 return newPoints; 53258 } 53259 53260 53261 /** 53262 * Returns the manhattan directions connecting two rectangles 53263 * with the given orientation. 53264 * 53265 * Will always return the default layout, if it is specific 53266 * regarding sides already (trbl). 53267 * 53268 * @example 53269 * 53270 * getDirections('top'); // -> 'v:v' 53271 * getDirections('intersect'); // -> 't:t' 53272 * 53273 * getDirections('top-right', 'v:h'); // -> 'v:h' 53274 * getDirections('top-right', 'h:h'); // -> 'h:h' 53275 * 53276 * 53277 * @param {string} orientation 53278 * @param {string} defaultLayout 53279 * 53280 * @return {string} 53281 */ 53282 function getDirections(orientation, defaultLayout) { 53283 53284 // don't override specific trbl directions 53285 if (isExplicitDirections(defaultLayout)) { 53286 return defaultLayout; 53287 } 53288 53289 switch (orientation) { 53290 case 'intersect': 53291 return 't:t'; 53292 53293 case 'top': 53294 case 'bottom': 53295 return 'v:v'; 53296 53297 case 'left': 53298 case 'right': 53299 return 'h:h'; 53300 53301 // 'top-left' 53302 // 'top-right' 53303 // 'bottom-left' 53304 // 'bottom-right' 53305 default: 53306 return defaultLayout; 53307 } 53308 } 53309 53310 function isValidDirections(directions) { 53311 return directions && /^h|v|t|r|b|l:h|v|t|r|b|l$/.test(directions); 53312 } 53313 53314 function isExplicitDirections(directions) { 53315 return directions && /t|r|b|l/.test(directions); 53316 } 53317 53318 function invertOrientation(orientation) { 53319 return { 53320 'top': 'bottom', 53321 'bottom': 'top', 53322 'left': 'right', 53323 'right': 'left', 53324 'top-left': 'bottom-right', 53325 'bottom-right': 'top-left', 53326 'top-right': 'bottom-left', 53327 'bottom-left': 'top-right', 53328 }[orientation]; 53329 } 53330 53331 function getDockingPoint(point, rectangle, dockingDirection, targetOrientation) { 53332 53333 // ensure we end up with a specific docking direction 53334 // based on the targetOrientation, if <h|v> is being passed 53335 53336 if (dockingDirection === 'h') { 53337 dockingDirection = /left/.test(targetOrientation) ? 'l' : 'r'; 53338 } 53339 53340 if (dockingDirection === 'v') { 53341 dockingDirection = /top/.test(targetOrientation) ? 't' : 'b'; 53342 } 53343 53344 if (dockingDirection === 't') { 53345 return { original: point, x: point.x, y: rectangle.y }; 53346 } 53347 53348 if (dockingDirection === 'r') { 53349 return { original: point, x: rectangle.x + rectangle.width, y: point.y }; 53350 } 53351 53352 if (dockingDirection === 'b') { 53353 return { original: point, x: point.x, y: rectangle.y + rectangle.height }; 53354 } 53355 53356 if (dockingDirection === 'l') { 53357 return { original: point, x: rectangle.x, y: point.y }; 53358 } 53359 53360 throw new Error('unexpected dockingDirection: <' + dockingDirection + '>'); 53361 } 53362 53363 53364 /** 53365 * Return list of waypoints with redundant ones filtered out. 53366 * 53367 * @example 53368 * 53369 * Original points: 53370 * 53371 * [x] ----- [x] ------ [x] 53372 * | 53373 * [x] ----- [x] - [x] 53374 * 53375 * Filtered: 53376 * 53377 * [x] ---------------- [x] 53378 * | 53379 * [x] ----------- [x] 53380 * 53381 * @param {Array<Point>} waypoints 53382 * 53383 * @return {Array<Point>} 53384 */ 53385 function withoutRedundantPoints(waypoints) { 53386 return waypoints.reduce(function(points, p, idx) { 53387 53388 var previous = points[points.length - 1], 53389 next = waypoints[idx + 1]; 53390 53391 if (!pointsOnLine(previous, next, p, 0)) { 53392 points.push(p); 53393 } 53394 53395 return points; 53396 }, []); 53397 } 53398 53399 var ATTACH_ORIENTATION_PADDING = -10, 53400 BOUNDARY_TO_HOST_THRESHOLD$1 = 40; 53401 53402 var oppositeOrientationMapping = { 53403 'top': 'bottom', 53404 'top-right': 'bottom-left', 53405 'top-left': 'bottom-right', 53406 'right': 'left', 53407 'bottom': 'top', 53408 'bottom-right': 'top-left', 53409 'bottom-left': 'top-right', 53410 'left': 'right' 53411 }; 53412 53413 var orientationDirectionMapping = { 53414 top: 't', 53415 right: 'r', 53416 bottom: 'b', 53417 left: 'l' 53418 }; 53419 53420 53421 function BpmnLayouter() {} 53422 53423 inherits$1(BpmnLayouter, BaseLayouter); 53424 53425 53426 BpmnLayouter.prototype.layoutConnection = function(connection, hints) { 53427 if (!hints) { 53428 hints = {}; 53429 } 53430 53431 var source = hints.source || connection.source, 53432 target = hints.target || connection.target, 53433 waypoints = hints.waypoints || connection.waypoints, 53434 connectionStart = hints.connectionStart, 53435 connectionEnd = hints.connectionEnd; 53436 53437 var manhattanOptions, 53438 updatedWaypoints; 53439 53440 if (!connectionStart) { 53441 connectionStart = getConnectionDocking(waypoints && waypoints[ 0 ], source); 53442 } 53443 53444 if (!connectionEnd) { 53445 connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target); 53446 } 53447 53448 // TODO(nikku): support vertical modeling 53449 // and invert preferredLayouts accordingly 53450 53451 if (is$1(connection, 'bpmn:Association') || 53452 is$1(connection, 'bpmn:DataAssociation')) { 53453 53454 if (waypoints && !isCompensationAssociation(source, target)) { 53455 return [].concat([ connectionStart ], waypoints.slice(1, -1), [ connectionEnd ]); 53456 } 53457 } 53458 53459 if (is$1(connection, 'bpmn:MessageFlow')) { 53460 manhattanOptions = getMessageFlowManhattanOptions(source, target); 53461 } else if (is$1(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) { 53462 53463 // layout all connection between flow elements h:h, except for 53464 // (1) outgoing of boundary events -> layout based on attach orientation and target orientation 53465 // (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming 53466 // (3) loops 53467 if (source === target) { 53468 manhattanOptions = { 53469 preferredLayouts: getLoopPreferredLayout(source, connection) 53470 }; 53471 } else if (is$1(source, 'bpmn:BoundaryEvent')) { 53472 manhattanOptions = { 53473 preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd) 53474 }; 53475 } else if (isExpandedSubProcess(source) || isExpandedSubProcess(target)) { 53476 manhattanOptions = getSubProcessManhattanOptions(source); 53477 } else if (is$1(source, 'bpmn:Gateway')) { 53478 manhattanOptions = { 53479 preferredLayouts: [ 'v:h' ] 53480 }; 53481 } else if (is$1(target, 'bpmn:Gateway')) { 53482 manhattanOptions = { 53483 preferredLayouts: [ 'h:v' ] 53484 }; 53485 } else { 53486 manhattanOptions = { 53487 preferredLayouts: [ 'h:h' ] 53488 }; 53489 } 53490 } 53491 53492 if (manhattanOptions) { 53493 manhattanOptions = assign(manhattanOptions, hints); 53494 53495 updatedWaypoints = withoutRedundantPoints(repairConnection( 53496 source, 53497 target, 53498 connectionStart, 53499 connectionEnd, 53500 waypoints, 53501 manhattanOptions 53502 )); 53503 } 53504 53505 return updatedWaypoints || [ connectionStart, connectionEnd ]; 53506 }; 53507 53508 53509 // helpers ////////// 53510 53511 function getAttachOrientation(attachedElement) { 53512 var hostElement = attachedElement.host; 53513 53514 return getOrientation(getMid(attachedElement), hostElement, ATTACH_ORIENTATION_PADDING); 53515 } 53516 53517 function getMessageFlowManhattanOptions(source, target) { 53518 return { 53519 preferredLayouts: [ 'straight', 'v:v' ], 53520 preserveDocking: getMessageFlowPreserveDocking(source, target) 53521 }; 53522 } 53523 53524 function getMessageFlowPreserveDocking(source, target) { 53525 53526 // (1) docking element connected to participant has precedence 53527 if (is$1(target, 'bpmn:Participant')) { 53528 return 'source'; 53529 } 53530 53531 if (is$1(source, 'bpmn:Participant')) { 53532 return 'target'; 53533 } 53534 53535 // (2) docking element connected to expanded sub-process has precedence 53536 if (isExpandedSubProcess(target)) { 53537 return 'source'; 53538 } 53539 53540 if (isExpandedSubProcess(source)) { 53541 return 'target'; 53542 } 53543 53544 // (3) docking event has precedence 53545 if (is$1(target, 'bpmn:Event')) { 53546 return 'target'; 53547 } 53548 53549 if (is$1(source, 'bpmn:Event')) { 53550 return 'source'; 53551 } 53552 53553 return null; 53554 } 53555 53556 function getSubProcessManhattanOptions(source) { 53557 return { 53558 preferredLayouts: [ 'straight', 'h:h' ], 53559 preserveDocking: getSubProcessPreserveDocking(source) 53560 }; 53561 } 53562 53563 function getSubProcessPreserveDocking(source) { 53564 return isExpandedSubProcess(source) ? 'target' : 'source'; 53565 } 53566 53567 function getConnectionDocking(point, shape) { 53568 return point ? (point.original || point) : getMid(shape); 53569 } 53570 53571 function isCompensationAssociation(source, target) { 53572 return is$1(target, 'bpmn:Activity') && 53573 is$1(source, 'bpmn:BoundaryEvent') && 53574 target.businessObject.isForCompensation; 53575 } 53576 53577 function isExpandedSubProcess(element) { 53578 return is$1(element, 'bpmn:SubProcess') && isExpanded(element); 53579 } 53580 53581 function isSame(a, b) { 53582 return a === b; 53583 } 53584 53585 function isAnyOrientation(orientation, orientations) { 53586 return orientations.indexOf(orientation) !== -1; 53587 } 53588 53589 function getHorizontalOrientation(orientation) { 53590 var matches = /right|left/.exec(orientation); 53591 53592 return matches && matches[0]; 53593 } 53594 53595 function getVerticalOrientation(orientation) { 53596 var matches = /top|bottom/.exec(orientation); 53597 53598 return matches && matches[0]; 53599 } 53600 53601 function isOppositeOrientation(a, b) { 53602 return oppositeOrientationMapping[a] === b; 53603 } 53604 53605 function isOppositeHorizontalOrientation(a, b) { 53606 var horizontalOrientation = getHorizontalOrientation(a); 53607 53608 var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation]; 53609 53610 return b.indexOf(oppositeHorizontalOrientation) !== -1; 53611 } 53612 53613 function isOppositeVerticalOrientation(a, b) { 53614 var verticalOrientation = getVerticalOrientation(a); 53615 53616 var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation]; 53617 53618 return b.indexOf(oppositeVerticalOrientation) !== -1; 53619 } 53620 53621 function isHorizontalOrientation(orientation) { 53622 return orientation === 'right' || orientation === 'left'; 53623 } 53624 53625 function getLoopPreferredLayout(source, connection) { 53626 var waypoints = connection.waypoints; 53627 53628 var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source); 53629 53630 if (orientation === 'top') { 53631 return [ 't:r' ]; 53632 } else if (orientation === 'right') { 53633 return [ 'r:b' ]; 53634 } else if (orientation === 'left') { 53635 return [ 'l:t' ]; 53636 } 53637 53638 return [ 'b:l' ]; 53639 } 53640 53641 function getBoundaryEventPreferredLayouts(source, target, end) { 53642 var sourceMid = getMid(source), 53643 targetMid = getMid(target), 53644 attachOrientation = getAttachOrientation(source), 53645 sourceLayout, 53646 targetLayout; 53647 53648 var isLoop = isSame(source.host, target); 53649 53650 var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]); 53651 53652 var targetOrientation = getOrientation(targetMid, sourceMid, { 53653 x: source.width / 2 + target.width / 2, 53654 y: source.height / 2 + target.height / 2 53655 }); 53656 53657 if (isLoop) { 53658 return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end); 53659 } 53660 53661 // source layout 53662 sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide); 53663 53664 // target layout 53665 targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide); 53666 53667 return [ sourceLayout + ':' + targetLayout ]; 53668 } 53669 53670 function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) { 53671 var orientation = attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation), 53672 sourceLayout = orientationDirectionMapping[ orientation ], 53673 targetLayout; 53674 53675 if (attachedToSide) { 53676 if (isHorizontalOrientation(attachOrientation)) { 53677 targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b'; 53678 } else { 53679 targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l'; 53680 } 53681 } else { 53682 targetLayout = 'v'; 53683 } 53684 53685 return [ sourceLayout + ':' + targetLayout ]; 53686 } 53687 53688 function shouldConnectToSameSide(axis, source, target, end) { 53689 var threshold = BOUNDARY_TO_HOST_THRESHOLD$1; 53690 53691 return !( 53692 areCloseOnAxis(axis, end, target, threshold) || 53693 areCloseOnAxis(axis, end, { 53694 x: target.x + target.width, 53695 y: target.y + target.height 53696 }, threshold) || 53697 areCloseOnAxis(axis, end, getMid(source), threshold) 53698 ); 53699 } 53700 53701 function areCloseOnAxis(axis, a, b, threshold) { 53702 return Math.abs(a[ axis ] - b[ axis ]) < threshold; 53703 } 53704 53705 function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) { 53706 53707 // attached to either top, right, bottom or left side 53708 if (attachedToSide) { 53709 return orientationDirectionMapping[ attachOrientation ]; 53710 } 53711 53712 // attached to either top-right, top-left, bottom-right or bottom-left corner 53713 53714 // same vertical or opposite horizontal orientation 53715 if (isSame( 53716 getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation) 53717 ) || isOppositeOrientation( 53718 getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation) 53719 )) { 53720 return orientationDirectionMapping[ getVerticalOrientation(attachOrientation) ]; 53721 } 53722 53723 // fallback 53724 return orientationDirectionMapping[ getHorizontalOrientation(attachOrientation) ]; 53725 } 53726 53727 function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) { 53728 53729 // attached to either top, right, bottom or left side 53730 if (attachedToSide) { 53731 if (isHorizontalOrientation(attachOrientation)) { 53732 53733 // orientation is right or left 53734 53735 // opposite horizontal orientation or same orientation 53736 if ( 53737 isOppositeHorizontalOrientation(attachOrientation, targetOrientation) || 53738 isSame(attachOrientation, targetOrientation) 53739 ) { 53740 return 'h'; 53741 } 53742 53743 // fallback 53744 return 'v'; 53745 } else { 53746 53747 // orientation is top or bottom 53748 53749 // opposite vertical orientation or same orientation 53750 if ( 53751 isOppositeVerticalOrientation(attachOrientation, targetOrientation) || 53752 isSame(attachOrientation, targetOrientation) 53753 ) { 53754 return 'v'; 53755 } 53756 53757 // fallback 53758 return 'h'; 53759 } 53760 } 53761 53762 // attached to either top-right, top-left, bottom-right or bottom-left corner 53763 53764 // orientation is right, left 53765 // or same vertical orientation but also right or left 53766 if (isHorizontalOrientation(targetOrientation) || 53767 (isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) && 53768 getHorizontalOrientation(targetOrientation))) { 53769 return 'h'; 53770 } else { 53771 return 'v'; 53772 } 53773 } 53774 53775 function dockingToPoint(docking) { 53776 53777 // use the dockings actual point and 53778 // retain the original docking 53779 return assign({ original: docking.point.original || docking.point }, docking.actual); 53780 } 53781 53782 53783 /** 53784 * A {@link ConnectionDocking} that crops connection waypoints based on 53785 * the path(s) of the connection source and target. 53786 * 53787 * @param {djs.core.ElementRegistry} elementRegistry 53788 */ 53789 function CroppingConnectionDocking(elementRegistry, graphicsFactory) { 53790 this._elementRegistry = elementRegistry; 53791 this._graphicsFactory = graphicsFactory; 53792 } 53793 53794 CroppingConnectionDocking.$inject = [ 'elementRegistry', 'graphicsFactory' ]; 53795 53796 53797 /** 53798 * @inheritDoc ConnectionDocking#getCroppedWaypoints 53799 */ 53800 CroppingConnectionDocking.prototype.getCroppedWaypoints = function(connection, source, target) { 53801 53802 source = source || connection.source; 53803 target = target || connection.target; 53804 53805 var sourceDocking = this.getDockingPoint(connection, source, true), 53806 targetDocking = this.getDockingPoint(connection, target); 53807 53808 var croppedWaypoints = connection.waypoints.slice(sourceDocking.idx + 1, targetDocking.idx); 53809 53810 croppedWaypoints.unshift(dockingToPoint(sourceDocking)); 53811 croppedWaypoints.push(dockingToPoint(targetDocking)); 53812 53813 return croppedWaypoints; 53814 }; 53815 53816 /** 53817 * Return the connection docking point on the specified shape 53818 * 53819 * @inheritDoc ConnectionDocking#getDockingPoint 53820 */ 53821 CroppingConnectionDocking.prototype.getDockingPoint = function(connection, shape, dockStart) { 53822 53823 var waypoints = connection.waypoints, 53824 dockingIdx, 53825 dockingPoint, 53826 croppedPoint; 53827 53828 dockingIdx = dockStart ? 0 : waypoints.length - 1; 53829 dockingPoint = waypoints[dockingIdx]; 53830 53831 croppedPoint = this._getIntersection(shape, connection, dockStart); 53832 53833 return { 53834 point: dockingPoint, 53835 actual: croppedPoint || dockingPoint, 53836 idx: dockingIdx 53837 }; 53838 }; 53839 53840 53841 // helpers ////////////////////// 53842 53843 CroppingConnectionDocking.prototype._getIntersection = function(shape, connection, takeFirst) { 53844 53845 var shapePath = this._getShapePath(shape), 53846 connectionPath = this._getConnectionPath(connection); 53847 53848 return getElementLineIntersection(shapePath, connectionPath, takeFirst); 53849 }; 53850 53851 CroppingConnectionDocking.prototype._getConnectionPath = function(connection) { 53852 return this._graphicsFactory.getConnectionPath(connection); 53853 }; 53854 53855 CroppingConnectionDocking.prototype._getShapePath = function(shape) { 53856 return this._graphicsFactory.getShapePath(shape); 53857 }; 53858 53859 CroppingConnectionDocking.prototype._getGfx = function(element) { 53860 return this._elementRegistry.getGraphics(element); 53861 }; 53862 53863 var ModelingModule = { 53864 __init__: [ 53865 'modeling', 53866 'bpmnUpdater' 53867 ], 53868 __depends__: [ 53869 BehaviorModule, 53870 RulesModule, 53871 DiOrderingModule, 53872 OrderingModule, 53873 ReplaceModule, 53874 CommandModule, 53875 TooltipsModule, 53876 LabelSupportModule, 53877 AttachSupportModule, 53878 SelectionModule, 53879 ChangeSupportModule, 53880 SpaceToolModule 53881 ], 53882 bpmnFactory: [ 'type', BpmnFactory ], 53883 bpmnUpdater: [ 'type', BpmnUpdater ], 53884 elementFactory: [ 'type', ElementFactory ], 53885 modeling: [ 'type', Modeling ], 53886 layouter: [ 'type', BpmnLayouter ], 53887 connectionDocking: [ 'type', CroppingConnectionDocking ] 53888 }; 53889 53890 var LOW_PRIORITY$2 = 500, 53891 MEDIUM_PRIORITY = 1250, 53892 HIGH_PRIORITY$2 = 1500; 53893 53894 var round = Math.round; 53895 53896 function mid(element) { 53897 return { 53898 x: element.x + round(element.width / 2), 53899 y: element.y + round(element.height / 2) 53900 }; 53901 } 53902 53903 /** 53904 * A plugin that makes shapes draggable / droppable. 53905 * 53906 * @param {EventBus} eventBus 53907 * @param {Dragging} dragging 53908 * @param {Modeling} modeling 53909 * @param {Selection} selection 53910 * @param {Rules} rules 53911 */ 53912 function MoveEvents( 53913 eventBus, dragging, modeling, 53914 selection, rules) { 53915 53916 // rules 53917 53918 function canMove(shapes, delta, position, target) { 53919 53920 return rules.allowed('elements.move', { 53921 shapes: shapes, 53922 delta: delta, 53923 position: position, 53924 target: target 53925 }); 53926 } 53927 53928 53929 // move events 53930 53931 // assign a high priority to this handler to setup the environment 53932 // others may hook up later, e.g. at default priority and modify 53933 // the move environment. 53934 // 53935 // This sets up the context with 53936 // 53937 // * shape: the primary shape being moved 53938 // * shapes: a list of shapes to be moved 53939 // * validatedShapes: a list of shapes that are being checked 53940 // against the rules before and during move 53941 // 53942 eventBus.on('shape.move.start', HIGH_PRIORITY$2, function(event) { 53943 53944 var context = event.context, 53945 shape = event.shape, 53946 shapes = selection.get().slice(); 53947 53948 // move only single shape if the dragged element 53949 // is not part of the current selection 53950 if (shapes.indexOf(shape) === -1) { 53951 shapes = [ shape ]; 53952 } 53953 53954 // ensure we remove nested elements in the collection 53955 // and add attachers for a proper dragger 53956 shapes = removeNested(shapes); 53957 53958 // attach shapes to drag context 53959 assign(context, { 53960 shapes: shapes, 53961 validatedShapes: shapes, 53962 shape: shape 53963 }); 53964 }); 53965 53966 53967 // assign a high priority to this handler to setup the environment 53968 // others may hook up later, e.g. at default priority and modify 53969 // the move environment 53970 // 53971 eventBus.on('shape.move.start', MEDIUM_PRIORITY, function(event) { 53972 53973 var context = event.context, 53974 validatedShapes = context.validatedShapes, 53975 canExecute; 53976 53977 canExecute = context.canExecute = canMove(validatedShapes); 53978 53979 // check if we can move the elements 53980 if (!canExecute) { 53981 return false; 53982 } 53983 }); 53984 53985 // assign a low priority to this handler 53986 // to let others modify the move event before we update 53987 // the context 53988 // 53989 eventBus.on('shape.move.move', LOW_PRIORITY$2, function(event) { 53990 53991 var context = event.context, 53992 validatedShapes = context.validatedShapes, 53993 hover = event.hover, 53994 delta = { x: event.dx, y: event.dy }, 53995 position = { x: event.x, y: event.y }, 53996 canExecute; 53997 53998 // check if we can move the elements 53999 canExecute = canMove(validatedShapes, delta, position, hover); 54000 54001 context.delta = delta; 54002 context.canExecute = canExecute; 54003 54004 // simply ignore move over 54005 if (canExecute === null) { 54006 context.target = null; 54007 54008 return; 54009 } 54010 54011 context.target = hover; 54012 }); 54013 54014 eventBus.on('shape.move.end', function(event) { 54015 54016 var context = event.context; 54017 54018 var delta = context.delta, 54019 canExecute = context.canExecute, 54020 isAttach = canExecute === 'attach', 54021 shapes = context.shapes; 54022 54023 if (canExecute === false) { 54024 return false; 54025 } 54026 54027 // ensure we have actual pixel values deltas 54028 // (important when zoom level was > 1 during move) 54029 delta.x = round(delta.x); 54030 delta.y = round(delta.y); 54031 54032 if (delta.x === 0 && delta.y === 0) { 54033 54034 // didn't move 54035 return; 54036 } 54037 54038 modeling.moveElements(shapes, delta, context.target, { 54039 primaryShape: context.shape, 54040 attach: isAttach 54041 }); 54042 }); 54043 54044 54045 // move activation 54046 54047 eventBus.on('element.mousedown', function(event) { 54048 54049 if (!isPrimaryButton(event)) { 54050 return; 54051 } 54052 54053 var originalEvent = getOriginal$1(event); 54054 54055 if (!originalEvent) { 54056 throw new Error('must supply DOM mousedown event'); 54057 } 54058 54059 return start(originalEvent, event.element); 54060 }); 54061 54062 /** 54063 * Start move. 54064 * 54065 * @param {MouseEvent} event 54066 * @param {djs.model.Shape} shape 54067 * @param {boolean} [activate] 54068 * @param {Object} [context] 54069 */ 54070 function start(event, element, activate, context) { 54071 if (isObject(activate)) { 54072 context = activate; 54073 activate = false; 54074 } 54075 54076 // do not move connections or the root element 54077 if (element.waypoints || !element.parent) { 54078 return; 54079 } 54080 54081 var referencePoint = mid(element); 54082 54083 dragging.init(event, referencePoint, 'shape.move', { 54084 cursor: 'grabbing', 54085 autoActivate: activate, 54086 data: { 54087 shape: element, 54088 context: context || {} 54089 } 54090 }); 54091 54092 // we've handled the event 54093 return true; 54094 } 54095 54096 // API 54097 54098 this.start = start; 54099 } 54100 54101 MoveEvents.$inject = [ 54102 'eventBus', 54103 'dragging', 54104 'modeling', 54105 'selection', 54106 'rules' 54107 ]; 54108 54109 54110 /** 54111 * Return a filtered list of elements that do not contain 54112 * those nested into others. 54113 * 54114 * @param {Array<djs.model.Base>} elements 54115 * 54116 * @return {Array<djs.model.Base>} filtered 54117 */ 54118 function removeNested(elements) { 54119 54120 var ids = groupBy(elements, 'id'); 54121 54122 return filter(elements, function(element) { 54123 while ((element = element.parent)) { 54124 54125 // parent in selection 54126 if (ids[element.id]) { 54127 return false; 54128 } 54129 } 54130 54131 return true; 54132 }); 54133 } 54134 54135 var LOW_PRIORITY$1 = 499; 54136 54137 var MARKER_DRAGGING = 'djs-dragging', 54138 MARKER_OK$1 = 'drop-ok', 54139 MARKER_NOT_OK$1 = 'drop-not-ok', 54140 MARKER_NEW_PARENT = 'new-parent', 54141 MARKER_ATTACH = 'attach-ok'; 54142 54143 54144 /** 54145 * Provides previews for moving shapes when moving. 54146 * 54147 * @param {EventBus} eventBus 54148 * @param {ElementRegistry} elementRegistry 54149 * @param {Canvas} canvas 54150 * @param {Styles} styles 54151 */ 54152 function MovePreview( 54153 eventBus, canvas, styles, previewSupport) { 54154 54155 function getVisualDragShapes(shapes) { 54156 var elements = getAllDraggedElements(shapes); 54157 54158 var filteredElements = removeEdges(elements); 54159 54160 return filteredElements; 54161 } 54162 54163 function getAllDraggedElements(shapes) { 54164 var allShapes = selfAndAllChildren(shapes, true); 54165 54166 var allConnections = map$1(allShapes, function(shape) { 54167 return (shape.incoming || []).concat(shape.outgoing || []); 54168 }); 54169 54170 return flatten(allShapes.concat(allConnections)); 54171 } 54172 54173 /** 54174 * Sets drop marker on an element. 54175 */ 54176 function setMarker(element, marker) { 54177 54178 [ MARKER_ATTACH, MARKER_OK$1, MARKER_NOT_OK$1, MARKER_NEW_PARENT ].forEach(function(m) { 54179 54180 if (m === marker) { 54181 canvas.addMarker(element, m); 54182 } else { 54183 canvas.removeMarker(element, m); 54184 } 54185 }); 54186 } 54187 54188 /** 54189 * Make an element draggable. 54190 * 54191 * @param {Object} context 54192 * @param {djs.model.Base} element 54193 * @param {boolean} addMarker 54194 */ 54195 function makeDraggable(context, element, addMarker) { 54196 54197 previewSupport.addDragger(element, context.dragGroup); 54198 54199 if (addMarker) { 54200 canvas.addMarker(element, MARKER_DRAGGING); 54201 } 54202 54203 if (context.allDraggedElements) { 54204 context.allDraggedElements.push(element); 54205 } else { 54206 context.allDraggedElements = [ element ]; 54207 } 54208 } 54209 54210 // assign a low priority to this handler 54211 // to let others modify the move context before 54212 // we draw things 54213 eventBus.on('shape.move.start', LOW_PRIORITY$1, function(event) { 54214 var context = event.context, 54215 dragShapes = context.shapes, 54216 allDraggedElements = context.allDraggedElements; 54217 54218 var visuallyDraggedShapes = getVisualDragShapes(dragShapes); 54219 54220 if (!context.dragGroup) { 54221 var dragGroup = create$1('g'); 54222 54223 attr(dragGroup, styles.cls('djs-drag-group', [ 'no-events' ])); 54224 54225 var activeLayer = canvas.getActiveLayer(); 54226 54227 append(activeLayer, dragGroup); 54228 54229 context.dragGroup = dragGroup; 54230 } 54231 54232 // add previews 54233 visuallyDraggedShapes.forEach(function(shape) { 54234 previewSupport.addDragger(shape, context.dragGroup); 54235 }); 54236 54237 // cache all dragged elements / gfx 54238 // so that we can quickly undo their state changes later 54239 if (!allDraggedElements) { 54240 allDraggedElements = getAllDraggedElements(dragShapes); 54241 } else { 54242 allDraggedElements = flatten([ 54243 allDraggedElements, 54244 getAllDraggedElements(dragShapes) 54245 ]); 54246 } 54247 54248 // add dragging marker 54249 forEach(allDraggedElements, function(e) { 54250 canvas.addMarker(e, MARKER_DRAGGING); 54251 }); 54252 54253 context.allDraggedElements = allDraggedElements; 54254 54255 // determine, if any of the dragged elements have different parents 54256 context.differentParents = haveDifferentParents(dragShapes); 54257 }); 54258 54259 // update previews 54260 eventBus.on('shape.move.move', LOW_PRIORITY$1, function(event) { 54261 54262 var context = event.context, 54263 dragGroup = context.dragGroup, 54264 target = context.target, 54265 parent = context.shape.parent, 54266 canExecute = context.canExecute; 54267 54268 if (target) { 54269 if (canExecute === 'attach') { 54270 setMarker(target, MARKER_ATTACH); 54271 } else if (context.canExecute && target && target.id !== parent.id) { 54272 setMarker(target, MARKER_NEW_PARENT); 54273 } else { 54274 setMarker(target, context.canExecute ? MARKER_OK$1 : MARKER_NOT_OK$1); 54275 } 54276 } 54277 54278 translate$2(dragGroup, event.dx, event.dy); 54279 }); 54280 54281 eventBus.on([ 'shape.move.out', 'shape.move.cleanup' ], function(event) { 54282 var context = event.context, 54283 target = context.target; 54284 54285 if (target) { 54286 setMarker(target, null); 54287 } 54288 }); 54289 54290 // remove previews 54291 eventBus.on('shape.move.cleanup', function(event) { 54292 54293 var context = event.context, 54294 allDraggedElements = context.allDraggedElements, 54295 dragGroup = context.dragGroup; 54296 54297 54298 // remove dragging marker 54299 forEach(allDraggedElements, function(e) { 54300 canvas.removeMarker(e, MARKER_DRAGGING); 54301 }); 54302 54303 if (dragGroup) { 54304 remove$1(dragGroup); 54305 } 54306 }); 54307 54308 54309 // API ////////////////////// 54310 54311 /** 54312 * Make an element draggable. 54313 * 54314 * @param {Object} context 54315 * @param {djs.model.Base} element 54316 * @param {boolean} addMarker 54317 */ 54318 this.makeDraggable = makeDraggable; 54319 } 54320 54321 MovePreview.$inject = [ 54322 'eventBus', 54323 'canvas', 54324 'styles', 54325 'previewSupport' 54326 ]; 54327 54328 54329 // helpers ////////////////////// 54330 54331 /** 54332 * returns elements minus all connections 54333 * where source or target is not elements 54334 */ 54335 function removeEdges(elements) { 54336 54337 var filteredElements = filter(elements, function(element) { 54338 54339 if (!isConnection$2(element)) { 54340 return true; 54341 } else { 54342 54343 return ( 54344 find(elements, matchPattern({ id: element.source.id })) && 54345 find(elements, matchPattern({ id: element.target.id })) 54346 ); 54347 } 54348 }); 54349 54350 return filteredElements; 54351 } 54352 54353 function haveDifferentParents(elements) { 54354 return size(groupBy(elements, function(e) { return e.parent && e.parent.id; })) !== 1; 54355 } 54356 54357 /** 54358 * Checks if an element is a connection. 54359 */ 54360 function isConnection$2(element) { 54361 return element.waypoints; 54362 } 54363 54364 var MoveModule = { 54365 __depends__: [ 54366 InteractionEventsModule$1, 54367 SelectionModule, 54368 OutlineModule, 54369 RulesModule$1, 54370 DraggingModule, 54371 PreviewSupportModule 54372 ], 54373 __init__: [ 54374 'move', 54375 'movePreview' 54376 ], 54377 move: [ 'type', MoveEvents ], 54378 movePreview: [ 'type', MovePreview ] 54379 }; 54380 54381 var TOGGLE_SELECTOR = '.djs-palette-toggle', 54382 ENTRY_SELECTOR = '.entry', 54383 ELEMENT_SELECTOR = TOGGLE_SELECTOR + ', ' + ENTRY_SELECTOR; 54384 54385 var PALETTE_OPEN_CLS = 'open', 54386 PALETTE_TWO_COLUMN_CLS = 'two-column'; 54387 54388 var DEFAULT_PRIORITY = 1000; 54389 54390 54391 /** 54392 * A palette containing modeling elements. 54393 */ 54394 function Palette(eventBus, canvas) { 54395 54396 this._eventBus = eventBus; 54397 this._canvas = canvas; 54398 54399 var self = this; 54400 54401 eventBus.on('tool-manager.update', function(event) { 54402 var tool = event.tool; 54403 54404 self.updateToolHighlight(tool); 54405 }); 54406 54407 eventBus.on('i18n.changed', function() { 54408 self._update(); 54409 }); 54410 54411 eventBus.on('diagram.init', function() { 54412 54413 self._diagramInitialized = true; 54414 54415 self._rebuild(); 54416 }); 54417 } 54418 54419 Palette.$inject = [ 'eventBus', 'canvas' ]; 54420 54421 54422 /** 54423 * Register a provider with the palette 54424 * 54425 * @param {number} [priority=1000] 54426 * @param {PaletteProvider} provider 54427 * 54428 * @example 54429 * const paletteProvider = { 54430 * getPaletteEntries: function() { 54431 * return function(entries) { 54432 * return { 54433 * ...entries, 54434 * 'entry-1': { 54435 * label: 'My Entry', 54436 * action: function() { alert("I have been clicked!"); } 54437 * } 54438 * }; 54439 * } 54440 * } 54441 * }; 54442 * 54443 * palette.registerProvider(800, paletteProvider); 54444 */ 54445 Palette.prototype.registerProvider = function(priority, provider) { 54446 if (!provider) { 54447 provider = priority; 54448 priority = DEFAULT_PRIORITY; 54449 } 54450 54451 this._eventBus.on('palette.getProviders', priority, function(event) { 54452 event.providers.push(provider); 54453 }); 54454 54455 this._rebuild(); 54456 }; 54457 54458 54459 /** 54460 * Returns the palette entries 54461 * 54462 * @return {Object<string, PaletteEntryDescriptor>} map of entries 54463 */ 54464 Palette.prototype.getEntries = function() { 54465 var providers = this._getProviders(); 54466 54467 return providers.reduce(addPaletteEntries, {}); 54468 }; 54469 54470 Palette.prototype._rebuild = function() { 54471 54472 if (!this._diagramInitialized) { 54473 return; 54474 } 54475 54476 var providers = this._getProviders(); 54477 54478 if (!providers.length) { 54479 return; 54480 } 54481 54482 if (!this._container) { 54483 this._init(); 54484 } 54485 54486 this._update(); 54487 }; 54488 54489 /** 54490 * Initialize 54491 */ 54492 Palette.prototype._init = function() { 54493 54494 var self = this; 54495 54496 var eventBus = this._eventBus; 54497 54498 var parentContainer = this._getParentContainer(); 54499 54500 var container = this._container = domify(Palette.HTML_MARKUP); 54501 54502 parentContainer.appendChild(container); 54503 54504 delegate.bind(container, ELEMENT_SELECTOR, 'click', function(event) { 54505 54506 var target = event.delegateTarget; 54507 54508 if (matchesSelector(target, TOGGLE_SELECTOR)) { 54509 return self.toggle(); 54510 } 54511 54512 self.trigger('click', event); 54513 }); 54514 54515 // prevent drag propagation 54516 componentEvent.bind(container, 'mousedown', function(event) { 54517 event.stopPropagation(); 54518 }); 54519 54520 // prevent drag propagation 54521 delegate.bind(container, ENTRY_SELECTOR, 'dragstart', function(event) { 54522 self.trigger('dragstart', event); 54523 }); 54524 54525 eventBus.on('canvas.resized', this._layoutChanged, this); 54526 54527 eventBus.fire('palette.create', { 54528 container: container 54529 }); 54530 }; 54531 54532 Palette.prototype._getProviders = function(id) { 54533 54534 var event = this._eventBus.createEvent({ 54535 type: 'palette.getProviders', 54536 providers: [] 54537 }); 54538 54539 this._eventBus.fire(event); 54540 54541 return event.providers; 54542 }; 54543 54544 /** 54545 * Update palette state. 54546 * 54547 * @param {Object} [state] { open, twoColumn } 54548 */ 54549 Palette.prototype._toggleState = function(state) { 54550 54551 state = state || {}; 54552 54553 var parent = this._getParentContainer(), 54554 container = this._container; 54555 54556 var eventBus = this._eventBus; 54557 54558 var twoColumn; 54559 54560 var cls = classes$1(container); 54561 54562 if ('twoColumn' in state) { 54563 twoColumn = state.twoColumn; 54564 } else { 54565 twoColumn = this._needsCollapse(parent.clientHeight, this._entries || {}); 54566 } 54567 54568 // always update two column 54569 cls.toggle(PALETTE_TWO_COLUMN_CLS, twoColumn); 54570 54571 if ('open' in state) { 54572 cls.toggle(PALETTE_OPEN_CLS, state.open); 54573 } 54574 54575 eventBus.fire('palette.changed', { 54576 twoColumn: twoColumn, 54577 open: this.isOpen() 54578 }); 54579 }; 54580 54581 Palette.prototype._update = function() { 54582 54583 var entriesContainer = query('.djs-palette-entries', this._container), 54584 entries = this._entries = this.getEntries(); 54585 54586 clear$1(entriesContainer); 54587 54588 forEach(entries, function(entry, id) { 54589 54590 var grouping = entry.group || 'default'; 54591 54592 var container = query('[data-group=' + grouping + ']', entriesContainer); 54593 if (!container) { 54594 container = domify('<div class="group" data-group="' + grouping + '"></div>'); 54595 entriesContainer.appendChild(container); 54596 } 54597 54598 var html = entry.html || ( 54599 entry.separator ? 54600 '<hr class="separator" />' : 54601 '<div class="entry" draggable="true"></div>'); 54602 54603 54604 var control = domify(html); 54605 container.appendChild(control); 54606 54607 if (!entry.separator) { 54608 attr$1(control, 'data-action', id); 54609 54610 if (entry.title) { 54611 attr$1(control, 'title', entry.title); 54612 } 54613 54614 if (entry.className) { 54615 addClasses(control, entry.className); 54616 } 54617 54618 if (entry.imageUrl) { 54619 control.appendChild(domify('<img src="' + entry.imageUrl + '">')); 54620 } 54621 } 54622 }); 54623 54624 // open after update 54625 this.open(); 54626 }; 54627 54628 54629 /** 54630 * Trigger an action available on the palette 54631 * 54632 * @param {string} action 54633 * @param {Event} event 54634 */ 54635 Palette.prototype.trigger = function(action, event, autoActivate) { 54636 var entries = this._entries, 54637 entry, 54638 handler, 54639 originalEvent, 54640 button = event.delegateTarget || event.target; 54641 54642 if (!button) { 54643 return event.preventDefault(); 54644 } 54645 54646 entry = entries[attr$1(button, 'data-action')]; 54647 54648 // when user clicks on the palette and not on an action 54649 if (!entry) { 54650 return; 54651 } 54652 54653 handler = entry.action; 54654 54655 originalEvent = event.originalEvent || event; 54656 54657 // simple action (via callback function) 54658 if (isFunction(handler)) { 54659 if (action === 'click') { 54660 handler(originalEvent, autoActivate); 54661 } 54662 } else { 54663 if (handler[action]) { 54664 handler[action](originalEvent, autoActivate); 54665 } 54666 } 54667 54668 // silence other actions 54669 event.preventDefault(); 54670 }; 54671 54672 Palette.prototype._layoutChanged = function() { 54673 this._toggleState({}); 54674 }; 54675 54676 /** 54677 * Do we need to collapse to two columns? 54678 * 54679 * @param {number} availableHeight 54680 * @param {Object} entries 54681 * 54682 * @return {boolean} 54683 */ 54684 Palette.prototype._needsCollapse = function(availableHeight, entries) { 54685 54686 // top margin + bottom toggle + bottom margin 54687 // implementors must override this method if they 54688 // change the palette styles 54689 var margin = 20 + 10 + 20; 54690 54691 var entriesHeight = Object.keys(entries).length * 46; 54692 54693 return availableHeight < entriesHeight + margin; 54694 }; 54695 54696 /** 54697 * Close the palette 54698 */ 54699 Palette.prototype.close = function() { 54700 54701 this._toggleState({ 54702 open: false, 54703 twoColumn: false 54704 }); 54705 }; 54706 54707 54708 /** 54709 * Open the palette 54710 */ 54711 Palette.prototype.open = function() { 54712 this._toggleState({ open: true }); 54713 }; 54714 54715 54716 Palette.prototype.toggle = function(open) { 54717 if (this.isOpen()) { 54718 this.close(); 54719 } else { 54720 this.open(); 54721 } 54722 }; 54723 54724 Palette.prototype.isActiveTool = function(tool) { 54725 return tool && this._activeTool === tool; 54726 }; 54727 54728 Palette.prototype.updateToolHighlight = function(name) { 54729 var entriesContainer, 54730 toolsContainer; 54731 54732 if (!this._toolsContainer) { 54733 entriesContainer = query('.djs-palette-entries', this._container); 54734 54735 this._toolsContainer = query('[data-group=tools]', entriesContainer); 54736 } 54737 54738 toolsContainer = this._toolsContainer; 54739 54740 forEach(toolsContainer.children, function(tool) { 54741 var actionName = tool.getAttribute('data-action'); 54742 54743 if (!actionName) { 54744 return; 54745 } 54746 54747 var toolClasses = classes$1(tool); 54748 54749 actionName = actionName.replace('-tool', ''); 54750 54751 if (toolClasses.contains('entry') && actionName === name) { 54752 toolClasses.add('highlighted-entry'); 54753 } else { 54754 toolClasses.remove('highlighted-entry'); 54755 } 54756 }); 54757 }; 54758 54759 54760 /** 54761 * Return true if the palette is opened. 54762 * 54763 * @example 54764 * 54765 * palette.open(); 54766 * 54767 * if (palette.isOpen()) { 54768 * // yes, we are open 54769 * } 54770 * 54771 * @return {boolean} true if palette is opened 54772 */ 54773 Palette.prototype.isOpen = function() { 54774 return classes$1(this._container).has(PALETTE_OPEN_CLS); 54775 }; 54776 54777 /** 54778 * Get container the palette lives in. 54779 * 54780 * @return {Element} 54781 */ 54782 Palette.prototype._getParentContainer = function() { 54783 return this._canvas.getContainer(); 54784 }; 54785 54786 54787 /* markup definition */ 54788 54789 Palette.HTML_MARKUP = 54790 '<div class="djs-palette">' + 54791 '<div class="djs-palette-entries"></div>' + 54792 '<div class="djs-palette-toggle"></div>' + 54793 '</div>'; 54794 54795 54796 // helpers ////////////////////// 54797 54798 function addClasses(element, classNames) { 54799 54800 var classes = classes$1(element); 54801 54802 var actualClassNames = isArray$2(classNames) ? classNames : classNames.split(/\s+/g); 54803 actualClassNames.forEach(function(cls) { 54804 classes.add(cls); 54805 }); 54806 } 54807 54808 function addPaletteEntries(entries, provider) { 54809 54810 var entriesOrUpdater = provider.getPaletteEntries(); 54811 54812 if (isFunction(entriesOrUpdater)) { 54813 return entriesOrUpdater(entries); 54814 } 54815 54816 forEach(entriesOrUpdater, function(entry, id) { 54817 entries[id] = entry; 54818 }); 54819 54820 return entries; 54821 } 54822 54823 var PaletteModule$1 = { 54824 __init__: [ 'palette' ], 54825 palette: [ 'type', Palette ] 54826 }; 54827 54828 var LASSO_TOOL_CURSOR = 'crosshair'; 54829 54830 54831 function LassoTool( 54832 eventBus, canvas, dragging, 54833 elementRegistry, selection, toolManager, 54834 mouse) { 54835 54836 this._selection = selection; 54837 this._dragging = dragging; 54838 this._mouse = mouse; 54839 54840 var self = this; 54841 54842 // lasso visuals implementation 54843 54844 /** 54845 * A helper that realizes the selection box visual 54846 */ 54847 var visuals = { 54848 54849 create: function(context) { 54850 var container = canvas.getActiveLayer(), 54851 frame; 54852 54853 frame = context.frame = create$1('rect'); 54854 attr(frame, { 54855 class: 'djs-lasso-overlay', 54856 width: 1, 54857 height: 1, 54858 x: 0, 54859 y: 0 54860 }); 54861 54862 append(container, frame); 54863 }, 54864 54865 update: function(context) { 54866 var frame = context.frame, 54867 bbox = context.bbox; 54868 54869 attr(frame, { 54870 x: bbox.x, 54871 y: bbox.y, 54872 width: bbox.width, 54873 height: bbox.height 54874 }); 54875 }, 54876 54877 remove: function(context) { 54878 54879 if (context.frame) { 54880 remove$1(context.frame); 54881 } 54882 } 54883 }; 54884 54885 toolManager.registerTool('lasso', { 54886 tool: 'lasso.selection', 54887 dragging: 'lasso' 54888 }); 54889 54890 eventBus.on('lasso.selection.end', function(event) { 54891 var target = event.originalEvent.target; 54892 54893 // only reactive on diagram click 54894 // on some occasions, event.hover is not set and we have to check if the target is an svg 54895 if (!event.hover && !(target instanceof SVGElement)) { 54896 return; 54897 } 54898 54899 eventBus.once('lasso.selection.ended', function() { 54900 self.activateLasso(event.originalEvent, true); 54901 }); 54902 }); 54903 54904 // lasso interaction implementation 54905 54906 eventBus.on('lasso.end', function(event) { 54907 54908 var bbox = toBBox(event); 54909 54910 var elements = elementRegistry.filter(function(element) { 54911 return element; 54912 }); 54913 54914 self.select(elements, bbox); 54915 }); 54916 54917 eventBus.on('lasso.start', function(event) { 54918 54919 var context = event.context; 54920 54921 context.bbox = toBBox(event); 54922 visuals.create(context); 54923 }); 54924 54925 eventBus.on('lasso.move', function(event) { 54926 54927 var context = event.context; 54928 54929 context.bbox = toBBox(event); 54930 visuals.update(context); 54931 }); 54932 54933 eventBus.on('lasso.cleanup', function(event) { 54934 54935 var context = event.context; 54936 54937 visuals.remove(context); 54938 }); 54939 54940 54941 // event integration 54942 54943 eventBus.on('element.mousedown', 1500, function(event) { 54944 54945 if (!hasSecondaryModifier(event)) { 54946 return; 54947 } 54948 54949 self.activateLasso(event.originalEvent); 54950 54951 // we've handled the event 54952 return true; 54953 }); 54954 } 54955 54956 LassoTool.$inject = [ 54957 'eventBus', 54958 'canvas', 54959 'dragging', 54960 'elementRegistry', 54961 'selection', 54962 'toolManager', 54963 'mouse' 54964 ]; 54965 54966 54967 LassoTool.prototype.activateLasso = function(event, autoActivate) { 54968 54969 this._dragging.init(event, 'lasso', { 54970 autoActivate: autoActivate, 54971 cursor: LASSO_TOOL_CURSOR, 54972 data: { 54973 context: {} 54974 } 54975 }); 54976 }; 54977 54978 LassoTool.prototype.activateSelection = function(event, autoActivate) { 54979 54980 this._dragging.init(event, 'lasso.selection', { 54981 trapClick: false, 54982 autoActivate: autoActivate, 54983 cursor: LASSO_TOOL_CURSOR, 54984 data: { 54985 context: {} 54986 } 54987 }); 54988 }; 54989 54990 LassoTool.prototype.select = function(elements, bbox) { 54991 var selectedElements = getEnclosedElements(elements, bbox); 54992 54993 this._selection.select(values(selectedElements)); 54994 }; 54995 54996 LassoTool.prototype.toggle = function() { 54997 if (this.isActive()) { 54998 return this._dragging.cancel(); 54999 } 55000 55001 var mouseEvent = this._mouse.getLastMoveEvent(); 55002 55003 this.activateSelection(mouseEvent, !!mouseEvent); 55004 }; 55005 55006 LassoTool.prototype.isActive = function() { 55007 var context = this._dragging.context(); 55008 55009 return context && /^lasso/.test(context.prefix); 55010 }; 55011 55012 55013 55014 function toBBox(event) { 55015 55016 var start = { 55017 55018 x: event.x - event.dx, 55019 y: event.y - event.dy 55020 }; 55021 55022 var end = { 55023 x: event.x, 55024 y: event.y 55025 }; 55026 55027 var bbox; 55028 55029 if ((start.x <= end.x && start.y < end.y) || 55030 (start.x < end.x && start.y <= end.y)) { 55031 55032 bbox = { 55033 x: start.x, 55034 y: start.y, 55035 width: end.x - start.x, 55036 height: end.y - start.y 55037 }; 55038 } else if ((start.x >= end.x && start.y < end.y) || 55039 (start.x > end.x && start.y <= end.y)) { 55040 55041 bbox = { 55042 x: end.x, 55043 y: start.y, 55044 width: start.x - end.x, 55045 height: end.y - start.y 55046 }; 55047 } else if ((start.x <= end.x && start.y > end.y) || 55048 (start.x < end.x && start.y >= end.y)) { 55049 55050 bbox = { 55051 x: start.x, 55052 y: end.y, 55053 width: end.x - start.x, 55054 height: start.y - end.y 55055 }; 55056 } else if ((start.x >= end.x && start.y > end.y) || 55057 (start.x > end.x && start.y >= end.y)) { 55058 55059 bbox = { 55060 x: end.x, 55061 y: end.y, 55062 width: start.x - end.x, 55063 height: start.y - end.y 55064 }; 55065 } else { 55066 55067 bbox = { 55068 x: end.x, 55069 y: end.y, 55070 width: 0, 55071 height: 0 55072 }; 55073 } 55074 return bbox; 55075 } 55076 55077 var LassoToolModule = { 55078 __depends__: [ 55079 ToolManagerModule, 55080 MouseModule 55081 ], 55082 __init__: [ 'lassoTool' ], 55083 lassoTool: [ 'type', LassoTool ] 55084 }; 55085 55086 var HIGH_PRIORITY$1 = 1500; 55087 var HAND_CURSOR = 'grab'; 55088 55089 55090 function HandTool( 55091 eventBus, canvas, dragging, 55092 injector, toolManager, mouse) { 55093 55094 this._dragging = dragging; 55095 this._mouse = mouse; 55096 55097 var self = this, 55098 keyboard = injector.get('keyboard', false); 55099 55100 toolManager.registerTool('hand', { 55101 tool: 'hand', 55102 dragging: 'hand.move' 55103 }); 55104 55105 eventBus.on('element.mousedown', HIGH_PRIORITY$1, function(event) { 55106 55107 if (!hasPrimaryModifier(event)) { 55108 return; 55109 } 55110 55111 self.activateMove(event.originalEvent, true); 55112 55113 return false; 55114 }); 55115 55116 keyboard && keyboard.addListener(HIGH_PRIORITY$1, function(e) { 55117 if (!isSpace(e.keyEvent) || self.isActive()) { 55118 return; 55119 } 55120 55121 var mouseEvent = self._mouse.getLastMoveEvent(); 55122 55123 self.activateMove(mouseEvent, !!mouseEvent); 55124 }, 'keyboard.keydown'); 55125 55126 keyboard && keyboard.addListener(HIGH_PRIORITY$1, function(e) { 55127 if (!isSpace(e.keyEvent) || !self.isActive()) { 55128 return; 55129 } 55130 55131 self.toggle(); 55132 }, 'keyboard.keyup'); 55133 55134 eventBus.on('hand.end', function(event) { 55135 var target = event.originalEvent.target; 55136 55137 // only reactive on diagram click 55138 // on some occasions, event.hover is not set and we have to check if the target is an svg 55139 if (!event.hover && !(target instanceof SVGElement)) { 55140 return false; 55141 } 55142 55143 eventBus.once('hand.ended', function() { 55144 self.activateMove(event.originalEvent, { reactivate: true }); 55145 }); 55146 55147 }); 55148 55149 eventBus.on('hand.move.move', function(event) { 55150 var scale = canvas.viewbox().scale; 55151 55152 canvas.scroll({ 55153 dx: event.dx * scale, 55154 dy: event.dy * scale 55155 }); 55156 }); 55157 55158 eventBus.on('hand.move.end', function(event) { 55159 var context = event.context, 55160 reactivate = context.reactivate; 55161 55162 // Don't reactivate if the user is using the keyboard keybinding 55163 if (!hasPrimaryModifier(event) && reactivate) { 55164 55165 eventBus.once('hand.move.ended', function(event) { 55166 self.activateHand(event.originalEvent, true, true); 55167 }); 55168 55169 } 55170 55171 return false; 55172 }); 55173 55174 } 55175 55176 HandTool.$inject = [ 55177 'eventBus', 55178 'canvas', 55179 'dragging', 55180 'injector', 55181 'toolManager', 55182 'mouse' 55183 ]; 55184 55185 55186 HandTool.prototype.activateMove = function(event, autoActivate, context) { 55187 if (typeof autoActivate === 'object') { 55188 context = autoActivate; 55189 autoActivate = false; 55190 } 55191 55192 this._dragging.init(event, 'hand.move', { 55193 autoActivate: autoActivate, 55194 cursor: HAND_CURSOR, 55195 data: { 55196 context: context || {} 55197 } 55198 }); 55199 }; 55200 55201 HandTool.prototype.activateHand = function(event, autoActivate, reactivate) { 55202 this._dragging.init(event, 'hand', { 55203 trapClick: false, 55204 autoActivate: autoActivate, 55205 cursor: HAND_CURSOR, 55206 data: { 55207 context: { 55208 reactivate: reactivate 55209 } 55210 } 55211 }); 55212 }; 55213 55214 HandTool.prototype.toggle = function() { 55215 if (this.isActive()) { 55216 return this._dragging.cancel(); 55217 } 55218 55219 var mouseEvent = this._mouse.getLastMoveEvent(); 55220 55221 this.activateHand(mouseEvent, !!mouseEvent); 55222 }; 55223 55224 HandTool.prototype.isActive = function() { 55225 var context = this._dragging.context(); 55226 55227 if (context) { 55228 return /^(hand|hand\.move)$/.test(context.prefix); 55229 } 55230 55231 return false; 55232 }; 55233 55234 // helpers ////////// 55235 55236 function isSpace(keyEvent) { 55237 return isKey(' ', keyEvent); 55238 } 55239 55240 var HandToolModule = { 55241 __depends__: [ 55242 ToolManagerModule, 55243 MouseModule 55244 ], 55245 __init__: [ 'handTool' ], 55246 handTool: [ 'type', HandTool ] 55247 }; 55248 55249 var MARKER_OK = 'connect-ok', 55250 MARKER_NOT_OK = 'connect-not-ok'; 55251 55252 /** 55253 * @class 55254 * @constructor 55255 * 55256 * @param {EventBus} eventBus 55257 * @param {Dragging} dragging 55258 * @param {Connect} connect 55259 * @param {Canvas} canvas 55260 * @param {ToolManager} toolManager 55261 * @param {Rules} rules 55262 * @param {Mouse} mouse 55263 */ 55264 function GlobalConnect( 55265 eventBus, dragging, connect, 55266 canvas, toolManager, rules, 55267 mouse) { 55268 55269 var self = this; 55270 55271 this._dragging = dragging; 55272 this._rules = rules; 55273 this._mouse = mouse; 55274 55275 toolManager.registerTool('global-connect', { 55276 tool: 'global-connect', 55277 dragging: 'global-connect.drag' 55278 }); 55279 55280 eventBus.on('global-connect.hover', function(event) { 55281 var context = event.context, 55282 startTarget = event.hover; 55283 55284 var canStartConnect = context.canStartConnect = self.canStartConnect(startTarget); 55285 55286 // simply ignore hover 55287 if (canStartConnect === null) { 55288 return; 55289 } 55290 55291 context.startTarget = startTarget; 55292 55293 canvas.addMarker(startTarget, canStartConnect ? MARKER_OK : MARKER_NOT_OK); 55294 }); 55295 55296 55297 eventBus.on([ 'global-connect.out', 'global-connect.cleanup' ], function(event) { 55298 var startTarget = event.context.startTarget, 55299 canStartConnect = event.context.canStartConnect; 55300 55301 if (startTarget) { 55302 canvas.removeMarker(startTarget, canStartConnect ? MARKER_OK : MARKER_NOT_OK); 55303 } 55304 }); 55305 55306 55307 eventBus.on([ 'global-connect.ended' ], function(event) { 55308 var context = event.context, 55309 startTarget = context.startTarget, 55310 startPosition = { 55311 x: event.x, 55312 y: event.y 55313 }; 55314 55315 var canStartConnect = self.canStartConnect(startTarget); 55316 55317 if (!canStartConnect) { 55318 return; 55319 } 55320 55321 eventBus.once('element.out', function() { 55322 eventBus.once([ 'connect.ended', 'connect.canceled' ], function() { 55323 eventBus.fire('global-connect.drag.ended'); 55324 }); 55325 55326 connect.start(null, startTarget, startPosition); 55327 }); 55328 55329 return false; 55330 }); 55331 } 55332 55333 GlobalConnect.$inject = [ 55334 'eventBus', 55335 'dragging', 55336 'connect', 55337 'canvas', 55338 'toolManager', 55339 'rules', 55340 'mouse' 55341 ]; 55342 55343 /** 55344 * Initiates tool activity. 55345 */ 55346 GlobalConnect.prototype.start = function(event, autoActivate) { 55347 this._dragging.init(event, 'global-connect', { 55348 autoActivate: autoActivate, 55349 trapClick: false, 55350 data: { 55351 context: {} 55352 } 55353 }); 55354 }; 55355 55356 GlobalConnect.prototype.toggle = function() { 55357 55358 if (this.isActive()) { 55359 return this._dragging.cancel(); 55360 } 55361 55362 var mouseEvent = this._mouse.getLastMoveEvent(); 55363 55364 return this.start(mouseEvent, !!mouseEvent); 55365 }; 55366 55367 GlobalConnect.prototype.isActive = function() { 55368 var context = this._dragging.context(); 55369 55370 return context && /^global-connect/.test(context.prefix); 55371 }; 55372 55373 /** 55374 * Check if source shape can initiate connection. 55375 * 55376 * @param {Shape} startTarget 55377 * @return {boolean} 55378 */ 55379 GlobalConnect.prototype.canStartConnect = function(startTarget) { 55380 return this._rules.allowed('connection.start', { source: startTarget }); 55381 }; 55382 55383 var GlobalConnectModule = { 55384 __depends__: [ 55385 ConnectModule, 55386 RulesModule$1, 55387 DraggingModule, 55388 ToolManagerModule, 55389 MouseModule 55390 ], 55391 globalConnect: [ 'type', GlobalConnect ] 55392 }; 55393 55394 /** 55395 * A palette provider for BPMN 2.0 elements. 55396 */ 55397 function PaletteProvider( 55398 palette, create, elementFactory, 55399 spaceTool, lassoTool, handTool, 55400 globalConnect, translate) { 55401 55402 this._palette = palette; 55403 this._create = create; 55404 this._elementFactory = elementFactory; 55405 this._spaceTool = spaceTool; 55406 this._lassoTool = lassoTool; 55407 this._handTool = handTool; 55408 this._globalConnect = globalConnect; 55409 this._translate = translate; 55410 55411 palette.registerProvider(this); 55412 } 55413 55414 PaletteProvider.$inject = [ 55415 'palette', 55416 'create', 55417 'elementFactory', 55418 'spaceTool', 55419 'lassoTool', 55420 'handTool', 55421 'globalConnect', 55422 'translate' 55423 ]; 55424 55425 55426 PaletteProvider.prototype.getPaletteEntries = function(element) { 55427 55428 var actions = {}, 55429 create = this._create, 55430 elementFactory = this._elementFactory, 55431 spaceTool = this._spaceTool, 55432 lassoTool = this._lassoTool, 55433 handTool = this._handTool, 55434 globalConnect = this._globalConnect, 55435 translate = this._translate; 55436 55437 function createAction(type, group, className, title, options) { 55438 55439 function createListener(event) { 55440 var shape = elementFactory.createShape(assign({ type: type }, options)); 55441 55442 if (options) { 55443 shape.businessObject.di.isExpanded = options.isExpanded; 55444 } 55445 55446 create.start(event, shape); 55447 } 55448 55449 var shortType = type.replace(/^bpmn:/, ''); 55450 55451 return { 55452 group: group, 55453 className: className, 55454 title: title || translate('Create {type}', { type: shortType }), 55455 action: { 55456 dragstart: createListener, 55457 click: createListener 55458 } 55459 }; 55460 } 55461 55462 function createSubprocess(event) { 55463 var subProcess = elementFactory.createShape({ 55464 type: 'bpmn:SubProcess', 55465 x: 0, 55466 y: 0, 55467 isExpanded: true 55468 }); 55469 55470 var startEvent = elementFactory.createShape({ 55471 type: 'bpmn:StartEvent', 55472 x: 40, 55473 y: 82, 55474 parent: subProcess 55475 }); 55476 55477 create.start(event, [ subProcess, startEvent ], { 55478 hints: { 55479 autoSelect: [ startEvent ] 55480 } 55481 }); 55482 } 55483 55484 function createParticipant(event) { 55485 create.start(event, elementFactory.createParticipantShape()); 55486 } 55487 55488 assign(actions, { 55489 'hand-tool': { 55490 group: 'tools', 55491 className: 'bpmn-icon-hand-tool', 55492 title: translate('Activate the hand tool'), 55493 action: { 55494 click: function(event) { 55495 handTool.activateHand(event); 55496 } 55497 } 55498 }, 55499 'lasso-tool': { 55500 group: 'tools', 55501 className: 'bpmn-icon-lasso-tool', 55502 title: translate('Activate the lasso tool'), 55503 action: { 55504 click: function(event) { 55505 lassoTool.activateSelection(event); 55506 } 55507 } 55508 }, 55509 'space-tool': { 55510 group: 'tools', 55511 className: 'bpmn-icon-space-tool', 55512 title: translate('Activate the create/remove space tool'), 55513 action: { 55514 click: function(event) { 55515 spaceTool.activateSelection(event); 55516 } 55517 } 55518 }, 55519 'global-connect-tool': { 55520 group: 'tools', 55521 className: 'bpmn-icon-connection-multi', 55522 title: translate('Activate the global connect tool'), 55523 action: { 55524 click: function(event) { 55525 globalConnect.start(event); 55526 } 55527 } 55528 }, 55529 'tool-separator': { 55530 group: 'tools', 55531 separator: true 55532 }, 55533 'create.start-event': createAction( 55534 'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none', 55535 translate('Create StartEvent') 55536 ), 55537 'create.intermediate-event': createAction( 55538 'bpmn:IntermediateThrowEvent', 'event', 'bpmn-icon-intermediate-event-none', 55539 translate('Create Intermediate/Boundary Event') 55540 ), 55541 'create.end-event': createAction( 55542 'bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none', 55543 translate('Create EndEvent') 55544 ), 55545 'create.exclusive-gateway': createAction( 55546 'bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none', 55547 translate('Create Gateway') 55548 ), 55549 'create.task': createAction( 55550 'bpmn:Task', 'activity', 'bpmn-icon-task', 55551 translate('Create Task') 55552 ), 55553 'create.data-object': createAction( 55554 'bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object', 55555 translate('Create DataObjectReference') 55556 ), 55557 'create.data-store': createAction( 55558 'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store', 55559 translate('Create DataStoreReference') 55560 ), 55561 'create.subprocess-expanded': { 55562 group: 'activity', 55563 className: 'bpmn-icon-subprocess-expanded', 55564 title: translate('Create expanded SubProcess'), 55565 action: { 55566 dragstart: createSubprocess, 55567 click: createSubprocess 55568 } 55569 }, 55570 'create.participant-expanded': { 55571 group: 'collaboration', 55572 className: 'bpmn-icon-participant', 55573 title: translate('Create Pool/Participant'), 55574 action: { 55575 dragstart: createParticipant, 55576 click: createParticipant 55577 } 55578 }, 55579 'create.group': createAction( 55580 'bpmn:Group', 'artifact', 'bpmn-icon-group', 55581 translate('Create Group') 55582 ), 55583 }); 55584 55585 return actions; 55586 }; 55587 55588 var PaletteModule = { 55589 __depends__: [ 55590 PaletteModule$1, 55591 CreateModule, 55592 SpaceToolModule, 55593 LassoToolModule, 55594 HandToolModule, 55595 GlobalConnectModule, 55596 translate 55597 ], 55598 __init__: [ 'paletteProvider' ], 55599 paletteProvider: [ 'type', PaletteProvider ] 55600 }; 55601 55602 var LOW_PRIORITY = 250; 55603 55604 55605 function BpmnReplacePreview( 55606 eventBus, elementRegistry, elementFactory, 55607 canvas, previewSupport) { 55608 55609 CommandInterceptor.call(this, eventBus); 55610 55611 /** 55612 * Replace the visuals of all elements in the context which can be replaced 55613 * 55614 * @param {Object} context 55615 */ 55616 function replaceVisual(context) { 55617 55618 var replacements = context.canExecute.replacements; 55619 55620 forEach(replacements, function(replacement) { 55621 55622 var id = replacement.oldElementId; 55623 55624 var newElement = { 55625 type: replacement.newElementType 55626 }; 55627 55628 // if the visual of the element is already replaced 55629 if (context.visualReplacements[id]) { 55630 return; 55631 } 55632 55633 var element = elementRegistry.get(id); 55634 55635 assign(newElement, { x: element.x, y: element.y }); 55636 55637 // create a temporary shape 55638 var tempShape = elementFactory.createShape(newElement); 55639 55640 canvas.addShape(tempShape, element.parent); 55641 55642 // select the original SVG element related to the element and hide it 55643 var gfx = query('[data-element-id="' + cssEscape(element.id) + '"]', context.dragGroup); 55644 55645 if (gfx) { 55646 attr(gfx, { display: 'none' }); 55647 } 55648 55649 // clone the gfx of the temporary shape and add it to the drag group 55650 var dragger = previewSupport.addDragger(tempShape, context.dragGroup); 55651 55652 context.visualReplacements[id] = dragger; 55653 55654 canvas.removeShape(tempShape); 55655 }); 55656 } 55657 55658 /** 55659 * Restore the original visuals of the previously replaced elements 55660 * 55661 * @param {Object} context 55662 */ 55663 function restoreVisual(context) { 55664 55665 var visualReplacements = context.visualReplacements; 55666 55667 forEach(visualReplacements, function(dragger, id) { 55668 55669 var originalGfx = query('[data-element-id="' + cssEscape(id) + '"]', context.dragGroup); 55670 55671 if (originalGfx) { 55672 attr(originalGfx, { display: 'inline' }); 55673 } 55674 55675 dragger.remove(); 55676 55677 if (visualReplacements[id]) { 55678 delete visualReplacements[id]; 55679 } 55680 }); 55681 } 55682 55683 eventBus.on('shape.move.move', LOW_PRIORITY, function(event) { 55684 55685 var context = event.context, 55686 canExecute = context.canExecute; 55687 55688 if (!context.visualReplacements) { 55689 context.visualReplacements = {}; 55690 } 55691 55692 if (canExecute && canExecute.replacements) { 55693 replaceVisual(context); 55694 } else { 55695 restoreVisual(context); 55696 } 55697 }); 55698 } 55699 55700 BpmnReplacePreview.$inject = [ 55701 'eventBus', 55702 'elementRegistry', 55703 'elementFactory', 55704 'canvas', 55705 'previewSupport' 55706 ]; 55707 55708 inherits$1(BpmnReplacePreview, CommandInterceptor); 55709 55710 var ReplacePreviewModule = { 55711 __depends__: [ 55712 PreviewSupportModule 55713 ], 55714 __init__: [ 'bpmnReplacePreview' ], 55715 bpmnReplacePreview: [ 'type', BpmnReplacePreview ] 55716 }; 55717 55718 var HIGHER_PRIORITY$2 = 1250; 55719 55720 var BOUNDARY_TO_HOST_THRESHOLD = 40; 55721 55722 var TARGET_BOUNDS_PADDING = 20, 55723 TASK_BOUNDS_PADDING = 10; 55724 55725 var TARGET_CENTER_PADDING = 20; 55726 55727 var AXES = [ 'x', 'y' ]; 55728 55729 var abs = Math.abs; 55730 55731 /** 55732 * Snap during connect. 55733 * 55734 * @param {EventBus} eventBus 55735 */ 55736 function BpmnConnectSnapping(eventBus) { 55737 eventBus.on([ 55738 'connect.hover', 55739 'connect.move', 55740 'connect.end', 55741 ], HIGHER_PRIORITY$2, function(event) { 55742 var context = event.context, 55743 canExecute = context.canExecute, 55744 start = context.start, 55745 hover = context.hover, 55746 source = context.source, 55747 target = context.target; 55748 55749 // do NOT snap on CMD 55750 if (event.originalEvent && isCmd(event.originalEvent)) { 55751 return; 55752 } 55753 55754 if (!context.initialConnectionStart) { 55755 context.initialConnectionStart = context.connectionStart; 55756 } 55757 55758 // snap hover 55759 if (canExecute && hover) { 55760 snapToShape(event, hover, getTargetBoundsPadding(hover)); 55761 } 55762 55763 if (hover && isAnyType(canExecute, [ 55764 'bpmn:Association', 55765 'bpmn:DataInputAssociation', 55766 'bpmn:DataOutputAssociation', 55767 'bpmn:SequenceFlow' 55768 ])) { 55769 context.connectionStart = mid$2(start); 55770 55771 // snap hover 55772 if (isAny(hover, [ 'bpmn:Event', 'bpmn:Gateway' ])) { 55773 snapToPosition(event, mid$2(hover)); 55774 } 55775 55776 // snap hover 55777 if (isAny(hover, [ 'bpmn:Task', 'bpmn:SubProcess' ])) { 55778 snapToTargetMid(event, hover); 55779 } 55780 55781 // snap source and target 55782 if (is$1(source, 'bpmn:BoundaryEvent') && target === source.host) { 55783 snapBoundaryEventLoop(event); 55784 } 55785 55786 } else if (isType(canExecute, 'bpmn:MessageFlow')) { 55787 55788 if (is$1(start, 'bpmn:Event')) { 55789 55790 // snap start 55791 context.connectionStart = mid$2(start); 55792 } 55793 55794 if (is$1(hover, 'bpmn:Event')) { 55795 55796 // snap hover 55797 snapToPosition(event, mid$2(hover)); 55798 } 55799 55800 } else { 55801 55802 // un-snap source 55803 context.connectionStart = context.initialConnectionStart; 55804 } 55805 }); 55806 } 55807 55808 BpmnConnectSnapping.$inject = [ 'eventBus' ]; 55809 55810 55811 // helpers ////////// 55812 55813 // snap to target if event in target 55814 function snapToShape(event, target, padding) { 55815 AXES.forEach(function(axis) { 55816 var dimensionForAxis = getDimensionForAxis(axis, target); 55817 55818 if (event[ axis ] < target[ axis ] + padding) { 55819 setSnapped(event, axis, target[ axis ] + padding); 55820 } else if (event[ axis ] > target[ axis ] + dimensionForAxis - padding) { 55821 setSnapped(event, axis, target[ axis ] + dimensionForAxis - padding); 55822 } 55823 }); 55824 } 55825 55826 // snap to target mid if event in target mid 55827 function snapToTargetMid(event, target) { 55828 var targetMid = mid$2(target); 55829 55830 AXES.forEach(function(axis) { 55831 if (isMid(event, target, axis)) { 55832 setSnapped(event, axis, targetMid[ axis ]); 55833 } 55834 }); 55835 } 55836 55837 // snap to prevent loop overlapping boundary event 55838 function snapBoundaryEventLoop(event) { 55839 var context = event.context, 55840 source = context.source, 55841 target = context.target; 55842 55843 if (isReverse(context)) { 55844 return; 55845 } 55846 55847 var sourceMid = mid$2(source), 55848 orientation = getOrientation(sourceMid, target, -10), 55849 axes = []; 55850 55851 if (/top|bottom/.test(orientation)) { 55852 axes.push('x'); 55853 } 55854 55855 if (/left|right/.test(orientation)) { 55856 axes.push('y'); 55857 } 55858 55859 axes.forEach(function(axis) { 55860 var coordinate = event[ axis ], newCoordinate; 55861 55862 if (abs(coordinate - sourceMid[ axis ]) < BOUNDARY_TO_HOST_THRESHOLD) { 55863 if (coordinate > sourceMid[ axis ]) { 55864 newCoordinate = sourceMid[ axis ] + BOUNDARY_TO_HOST_THRESHOLD; 55865 } 55866 else { 55867 newCoordinate = sourceMid[ axis ] - BOUNDARY_TO_HOST_THRESHOLD; 55868 } 55869 55870 setSnapped(event, axis, newCoordinate); 55871 } 55872 }); 55873 } 55874 55875 function snapToPosition(event, position) { 55876 setSnapped(event, 'x', position.x); 55877 setSnapped(event, 'y', position.y); 55878 } 55879 55880 function isType(attrs, type) { 55881 return attrs && attrs.type === type; 55882 } 55883 55884 function isAnyType(attrs, types) { 55885 return some(types, function(type) { 55886 return isType(attrs, type); 55887 }); 55888 } 55889 55890 function getDimensionForAxis(axis, element) { 55891 return axis === 'x' ? element.width : element.height; 55892 } 55893 55894 function getTargetBoundsPadding(target) { 55895 if (is$1(target, 'bpmn:Task')) { 55896 return TASK_BOUNDS_PADDING; 55897 } else { 55898 return TARGET_BOUNDS_PADDING; 55899 } 55900 } 55901 55902 function isMid(event, target, axis) { 55903 return event[ axis ] > target[ axis ] + TARGET_CENTER_PADDING 55904 && event[ axis ] < target[ axis ] + getDimensionForAxis(axis, target) - TARGET_CENTER_PADDING; 55905 } 55906 55907 function isReverse(context) { 55908 var hover = context.hover, 55909 source = context.source; 55910 55911 return hover && source && hover === source; 55912 } 55913 55914 /** 55915 * A snap context, containing the (possibly incomplete) 55916 * mappings of drop targets (to identify the snapping) 55917 * to computed snap points. 55918 */ 55919 function SnapContext() { 55920 55921 /** 55922 * Map<String, SnapPoints> mapping drop targets to 55923 * a list of possible snappings. 55924 * 55925 * @type {Object} 55926 */ 55927 this._targets = {}; 55928 55929 /** 55930 * Map<String, Point> initial positioning of element 55931 * regarding various snap directions. 55932 * 55933 * @type {Object} 55934 */ 55935 this._snapOrigins = {}; 55936 55937 /** 55938 * List of snap locations 55939 * 55940 * @type {Array<string>} 55941 */ 55942 this._snapLocations = []; 55943 55944 /** 55945 * Map<String, Array<Point>> of default snapping locations 55946 * 55947 * @type {Object} 55948 */ 55949 this._defaultSnaps = {}; 55950 } 55951 55952 55953 SnapContext.prototype.getSnapOrigin = function(snapLocation) { 55954 return this._snapOrigins[snapLocation]; 55955 }; 55956 55957 55958 SnapContext.prototype.setSnapOrigin = function(snapLocation, initialValue) { 55959 this._snapOrigins[snapLocation] = initialValue; 55960 55961 if (this._snapLocations.indexOf(snapLocation) === -1) { 55962 this._snapLocations.push(snapLocation); 55963 } 55964 }; 55965 55966 55967 SnapContext.prototype.addDefaultSnap = function(type, point) { 55968 55969 var snapValues = this._defaultSnaps[type]; 55970 55971 if (!snapValues) { 55972 snapValues = this._defaultSnaps[type] = []; 55973 } 55974 55975 snapValues.push(point); 55976 }; 55977 55978 /** 55979 * Return a number of initialized snaps, i.e. snap locations such as 55980 * top-left, mid, bottom-right and so forth. 55981 * 55982 * @return {Array<string>} snapLocations 55983 */ 55984 SnapContext.prototype.getSnapLocations = function() { 55985 return this._snapLocations; 55986 }; 55987 55988 /** 55989 * Set the snap locations for this context. 55990 * 55991 * The order of locations determines precedence. 55992 * 55993 * @param {Array<string>} snapLocations 55994 */ 55995 SnapContext.prototype.setSnapLocations = function(snapLocations) { 55996 this._snapLocations = snapLocations; 55997 }; 55998 55999 /** 56000 * Get snap points for a given target 56001 * 56002 * @param {Element|string} target 56003 */ 56004 SnapContext.prototype.pointsForTarget = function(target) { 56005 56006 var targetId = target.id || target; 56007 56008 var snapPoints = this._targets[targetId]; 56009 56010 if (!snapPoints) { 56011 snapPoints = this._targets[targetId] = new SnapPoints(); 56012 snapPoints.initDefaults(this._defaultSnaps); 56013 } 56014 56015 return snapPoints; 56016 }; 56017 56018 56019 /** 56020 * Creates the snap points and initializes them with the 56021 * given default values. 56022 * 56023 * @param {Object<string, Array<Point>>} [defaultPoints] 56024 */ 56025 function SnapPoints(defaultSnaps) { 56026 56027 /** 56028 * Map<String, Map<(x|y), Array<number>>> mapping snap locations, 56029 * i.e. top-left, bottom-right, center to actual snap values. 56030 * 56031 * @type {Object} 56032 */ 56033 this._snapValues = {}; 56034 } 56035 56036 SnapPoints.prototype.add = function(snapLocation, point) { 56037 56038 var snapValues = this._snapValues[snapLocation]; 56039 56040 if (!snapValues) { 56041 snapValues = this._snapValues[snapLocation] = { x: [], y: [] }; 56042 } 56043 56044 if (snapValues.x.indexOf(point.x) === -1) { 56045 snapValues.x.push(point.x); 56046 } 56047 56048 if (snapValues.y.indexOf(point.y) === -1) { 56049 snapValues.y.push(point.y); 56050 } 56051 }; 56052 56053 56054 SnapPoints.prototype.snap = function(point, snapLocation, axis, tolerance) { 56055 var snappingValues = this._snapValues[snapLocation]; 56056 56057 return snappingValues && snapTo(point[axis], snappingValues[axis], tolerance); 56058 }; 56059 56060 /** 56061 * Initialize a number of default snapping points. 56062 * 56063 * @param {Object} defaultSnaps 56064 */ 56065 SnapPoints.prototype.initDefaults = function(defaultSnaps) { 56066 56067 var self = this; 56068 56069 forEach(defaultSnaps || {}, function(snapPoints, snapLocation) { 56070 forEach(snapPoints, function(point) { 56071 self.add(snapLocation, point); 56072 }); 56073 }); 56074 }; 56075 56076 var HIGHER_PRIORITY$1 = 1250; 56077 56078 56079 /** 56080 * Snap during create and move. 56081 * 56082 * @param {EventBus} elementRegistry 56083 * @param {EventBus} eventBus 56084 * @param {Snapping} snapping 56085 */ 56086 function CreateMoveSnapping(elementRegistry, eventBus, snapping) { 56087 var self = this; 56088 56089 this._elementRegistry = elementRegistry; 56090 56091 eventBus.on([ 56092 'create.start', 56093 'shape.move.start' 56094 ], function(event) { 56095 self.initSnap(event); 56096 }); 56097 56098 eventBus.on([ 56099 'create.move', 56100 'create.end', 56101 'shape.move.move', 56102 'shape.move.end' 56103 ], HIGHER_PRIORITY$1, function(event) { 56104 var context = event.context, 56105 shape = context.shape, 56106 snapContext = context.snapContext, 56107 target = context.target; 56108 56109 if (event.originalEvent && isCmd(event.originalEvent)) { 56110 return; 56111 } 56112 56113 if (isSnapped(event) || !target) { 56114 return; 56115 } 56116 56117 var snapPoints = snapContext.pointsForTarget(target); 56118 56119 if (!snapPoints.initialized) { 56120 snapPoints = self.addSnapTargetPoints(snapPoints, shape, target); 56121 56122 snapPoints.initialized = true; 56123 } 56124 56125 snapping.snap(event, snapPoints); 56126 }); 56127 56128 eventBus.on([ 56129 'create.cleanup', 56130 'shape.move.cleanup' 56131 ], function() { 56132 snapping.hide(); 56133 }); 56134 } 56135 56136 CreateMoveSnapping.$inject = [ 56137 'elementRegistry', 56138 'eventBus', 56139 'snapping' 56140 ]; 56141 56142 CreateMoveSnapping.prototype.initSnap = function(event) { 56143 var elementRegistry = this._elementRegistry; 56144 56145 var context = event.context, 56146 shape = context.shape, 56147 snapContext = context.snapContext; 56148 56149 if (!snapContext) { 56150 snapContext = context.snapContext = new SnapContext(); 56151 } 56152 56153 var shapeMid; 56154 56155 if (elementRegistry.get(shape.id)) { 56156 56157 // move 56158 shapeMid = mid$2(shape, event); 56159 } else { 56160 56161 // create 56162 shapeMid = { 56163 x: event.x + mid$2(shape).x, 56164 y: event.y + mid$2(shape).y 56165 }; 56166 } 56167 56168 var shapeTopLeft = { 56169 x: shapeMid.x - shape.width / 2, 56170 y: shapeMid.y - shape.height / 2 56171 }, 56172 shapeBottomRight = { 56173 x: shapeMid.x + shape.width / 2, 56174 y: shapeMid.y + shape.height / 2 56175 }; 56176 56177 snapContext.setSnapOrigin('mid', { 56178 x: shapeMid.x - event.x, 56179 y: shapeMid.y - event.y 56180 }); 56181 56182 // snap labels to mid only 56183 if (isLabel$1(shape)) { 56184 return snapContext; 56185 } 56186 56187 snapContext.setSnapOrigin('top-left', { 56188 x: shapeTopLeft.x - event.x, 56189 y: shapeTopLeft.y - event.y 56190 }); 56191 56192 snapContext.setSnapOrigin('bottom-right', { 56193 x: shapeBottomRight.x - event.x, 56194 y: shapeBottomRight.y - event.y 56195 }); 56196 56197 return snapContext; 56198 }; 56199 56200 CreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) { 56201 var snapTargets = this.getSnapTargets(shape, target); 56202 56203 forEach(snapTargets, function(snapTarget) { 56204 56205 // handle labels 56206 if (isLabel$1(snapTarget)) { 56207 56208 if (isLabel$1(shape)) { 56209 snapPoints.add('mid', mid$2(snapTarget)); 56210 } 56211 56212 return; 56213 } 56214 56215 // handle connections 56216 if (isConnection$1(snapTarget)) { 56217 56218 // ignore single segment connections 56219 if (snapTarget.waypoints.length < 3) { 56220 return; 56221 } 56222 56223 // ignore first and last waypoint 56224 var waypoints = snapTarget.waypoints.slice(1, -1); 56225 56226 forEach(waypoints, function(waypoint) { 56227 snapPoints.add('mid', waypoint); 56228 }); 56229 56230 return; 56231 } 56232 56233 // handle shapes 56234 snapPoints.add('mid', mid$2(snapTarget)); 56235 }); 56236 56237 if (!isNumber(shape.x) || !isNumber(shape.y)) { 56238 return snapPoints; 56239 } 56240 56241 // snap to original position when moving 56242 if (this._elementRegistry.get(shape.id)) { 56243 snapPoints.add('mid', mid$2(shape)); 56244 } 56245 56246 return snapPoints; 56247 }; 56248 56249 CreateMoveSnapping.prototype.getSnapTargets = function(shape, target) { 56250 return getChildren(target).filter(function(child) { 56251 return !isHidden$1(child); 56252 }); 56253 }; 56254 56255 // helpers ////////// 56256 56257 function isConnection$1(element) { 56258 return !!element.waypoints; 56259 } 56260 56261 function isHidden$1(element) { 56262 return !!element.hidden; 56263 } 56264 56265 function isLabel$1(element) { 56266 return !!element.labelTarget; 56267 } 56268 56269 var HIGH_PRIORITY = 1500; 56270 56271 56272 /** 56273 * Snap during create and move. 56274 * 56275 * @param {EventBus} eventBus 56276 * @param {Injector} injector 56277 */ 56278 function BpmnCreateMoveSnapping(eventBus, injector) { 56279 injector.invoke(CreateMoveSnapping, this); 56280 56281 // creating first participant 56282 eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained); 56283 56284 // snap boundary events 56285 eventBus.on([ 56286 'create.move', 56287 'create.end', 56288 'shape.move.move', 56289 'shape.move.end' 56290 ], HIGH_PRIORITY, function(event) { 56291 var context = event.context, 56292 canExecute = context.canExecute, 56293 target = context.target; 56294 56295 var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach); 56296 56297 if (canAttach && !isSnapped(event)) { 56298 snapBoundaryEvent(event, target); 56299 } 56300 }); 56301 } 56302 56303 inherits$1(BpmnCreateMoveSnapping, CreateMoveSnapping); 56304 56305 BpmnCreateMoveSnapping.$inject = [ 56306 'eventBus', 56307 'injector' 56308 ]; 56309 56310 BpmnCreateMoveSnapping.prototype.initSnap = function(event) { 56311 var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event); 56312 56313 var shape = event.shape; 56314 56315 var isMove = !!this._elementRegistry.get(shape.id); 56316 56317 // snap to docking points 56318 forEach(shape.outgoing, function(connection) { 56319 var docking = connection.waypoints[0]; 56320 56321 docking = docking.original || docking; 56322 56323 snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event)); 56324 }); 56325 56326 forEach(shape.incoming, function(connection) { 56327 var docking = connection.waypoints[connection.waypoints.length - 1]; 56328 56329 docking = docking.original || docking; 56330 56331 snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event)); 56332 }); 56333 56334 if (is$1(shape, 'bpmn:Participant')) { 56335 56336 // snap to borders with higher priority 56337 snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]); 56338 } 56339 56340 return snapContext; 56341 }; 56342 56343 BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) { 56344 CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target); 56345 56346 var snapTargets = this.getSnapTargets(shape, target); 56347 56348 forEach(snapTargets, function(snapTarget) { 56349 56350 // handle TRBL alignment 56351 // 56352 // * with container elements 56353 // * with text annotations 56354 if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) { 56355 snapPoints.add('top-left', topLeft(snapTarget)); 56356 snapPoints.add('bottom-right', bottomRight(snapTarget)); 56357 } 56358 }); 56359 56360 var elementRegistry = this._elementRegistry; 56361 56362 // snap to docking points if not create mode 56363 forEach(shape.incoming, function(connection) { 56364 if (elementRegistry.get(shape.id)) { 56365 56366 if (!includes(snapTargets, connection.source)) { 56367 snapPoints.add('mid', getMid(connection.source)); 56368 } 56369 56370 var docking = connection.waypoints[0]; 56371 snapPoints.add(connection.id + '-docking', docking.original || docking); 56372 } 56373 }); 56374 56375 forEach(shape.outgoing, function(connection) { 56376 if (elementRegistry.get(shape.id)) { 56377 56378 if (!includes(snapTargets, connection.target)) { 56379 snapPoints.add('mid', getMid(connection.target)); 56380 } 56381 56382 var docking = connection.waypoints[ connection.waypoints.length - 1 ]; 56383 56384 snapPoints.add(connection.id + '-docking', docking.original || docking); 56385 } 56386 }); 56387 56388 // add sequence flow parents as snap targets 56389 if (is$1(target, 'bpmn:SequenceFlow')) { 56390 snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent); 56391 } 56392 56393 return snapPoints; 56394 }; 56395 56396 BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) { 56397 return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target) 56398 .filter(function(snapTarget) { 56399 56400 // do not snap to lanes 56401 return !is$1(snapTarget, 'bpmn:Lane'); 56402 }); 56403 }; 56404 56405 // helpers ////////// 56406 56407 function snapBoundaryEvent(event, target) { 56408 var targetTRBL = asTRBL(target); 56409 56410 var direction = getBoundaryAttachment(event, target); 56411 56412 var context = event.context, 56413 shape = context.shape; 56414 56415 var offset; 56416 56417 if (shape.parent) { 56418 offset = { x: 0, y: 0 }; 56419 } else { 56420 offset = getMid(shape); 56421 } 56422 56423 if (/top/.test(direction)) { 56424 setSnapped(event, 'y', targetTRBL.top - offset.y); 56425 } else if (/bottom/.test(direction)) { 56426 setSnapped(event, 'y', targetTRBL.bottom - offset.y); 56427 } 56428 56429 if (/left/.test(direction)) { 56430 setSnapped(event, 'x', targetTRBL.left - offset.x); 56431 } else if (/right/.test(direction)) { 56432 setSnapped(event, 'x', targetTRBL.right - offset.x); 56433 } 56434 } 56435 56436 function areAll(elements, type) { 56437 return elements.every(function(el) { 56438 return is$1(el, type); 56439 }); 56440 } 56441 56442 function isContainer(element) { 56443 if (is$1(element, 'bpmn:SubProcess') && isExpanded(element)) { 56444 return true; 56445 } 56446 56447 return is$1(element, 'bpmn:Participant'); 56448 } 56449 56450 56451 function setSnappedIfConstrained(event) { 56452 var context = event.context, 56453 createConstraints = context.createConstraints; 56454 56455 if (!createConstraints) { 56456 return; 56457 } 56458 56459 var top = createConstraints.top, 56460 right = createConstraints.right, 56461 bottom = createConstraints.bottom, 56462 left = createConstraints.left; 56463 56464 if ((left && left >= event.x) || (right && right <= event.x)) { 56465 setSnapped(event, 'x', event.x); 56466 } 56467 56468 if ((top && top >= event.y) || (bottom && bottom <= event.y)) { 56469 setSnapped(event, 'y', event.y); 56470 } 56471 } 56472 56473 function includes(array, value) { 56474 return array.indexOf(value) !== -1; 56475 } 56476 56477 function getDockingSnapOrigin(docking, isMove, event) { 56478 return isMove ? ( 56479 { 56480 x: docking.x - event.x, 56481 y: docking.y - event.y 56482 } 56483 ) : { 56484 x: docking.x, 56485 y: docking.y 56486 }; 56487 } 56488 56489 var HIGHER_PRIORITY = 1250; 56490 56491 56492 /** 56493 * Snap during resize. 56494 * 56495 * @param {EventBus} eventBus 56496 * @param {Snapping} snapping 56497 */ 56498 function ResizeSnapping(eventBus, snapping) { 56499 var self = this; 56500 56501 eventBus.on([ 'resize.start' ], function(event) { 56502 self.initSnap(event); 56503 }); 56504 56505 eventBus.on([ 56506 'resize.move', 56507 'resize.end', 56508 ], HIGHER_PRIORITY, function(event) { 56509 var context = event.context, 56510 shape = context.shape, 56511 parent = shape.parent, 56512 direction = context.direction, 56513 snapContext = context.snapContext; 56514 56515 if (event.originalEvent && isCmd(event.originalEvent)) { 56516 return; 56517 } 56518 56519 if (isSnapped(event)) { 56520 return; 56521 } 56522 56523 var snapPoints = snapContext.pointsForTarget(parent); 56524 56525 if (!snapPoints.initialized) { 56526 snapPoints = self.addSnapTargetPoints(snapPoints, shape, parent, direction); 56527 56528 snapPoints.initialized = true; 56529 } 56530 56531 if (isHorizontal(direction)) { 56532 setSnapped(event, 'x', event.x); 56533 } 56534 56535 if (isVertical(direction)) { 56536 setSnapped(event, 'y', event.y); 56537 } 56538 56539 snapping.snap(event, snapPoints); 56540 }); 56541 56542 eventBus.on([ 'resize.cleanup' ], function() { 56543 snapping.hide(); 56544 }); 56545 } 56546 56547 ResizeSnapping.prototype.initSnap = function(event) { 56548 var context = event.context, 56549 shape = context.shape, 56550 direction = context.direction, 56551 snapContext = context.snapContext; 56552 56553 if (!snapContext) { 56554 snapContext = context.snapContext = new SnapContext(); 56555 } 56556 56557 var snapOrigin = getSnapOrigin(shape, direction); 56558 56559 snapContext.setSnapOrigin('corner', { 56560 x: snapOrigin.x - event.x, 56561 y: snapOrigin.y - event.y 56562 }); 56563 56564 return snapContext; 56565 }; 56566 56567 ResizeSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target, direction) { 56568 var snapTargets = this.getSnapTargets(shape, target); 56569 56570 forEach(snapTargets, function(snapTarget) { 56571 snapPoints.add('corner', bottomRight(snapTarget)); 56572 snapPoints.add('corner', topLeft(snapTarget)); 56573 }); 56574 56575 snapPoints.add('corner', getSnapOrigin(shape, direction)); 56576 56577 return snapPoints; 56578 }; 56579 56580 ResizeSnapping.$inject = [ 56581 'eventBus', 56582 'snapping' 56583 ]; 56584 56585 ResizeSnapping.prototype.getSnapTargets = function(shape, target) { 56586 return getChildren(target).filter(function(child) { 56587 return !isAttached(child, shape) 56588 && !isConnection(child) 56589 && !isHidden(child) 56590 && !isLabel(child); 56591 }); 56592 }; 56593 56594 // helpers ////////// 56595 56596 function getSnapOrigin(shape, direction) { 56597 var mid = getMid(shape), 56598 trbl = asTRBL(shape); 56599 56600 var snapOrigin = { 56601 x: mid.x, 56602 y: mid.y 56603 }; 56604 56605 if (direction.indexOf('n') !== -1) { 56606 snapOrigin.y = trbl.top; 56607 } else if (direction.indexOf('s') !== -1) { 56608 snapOrigin.y = trbl.bottom; 56609 } 56610 56611 if (direction.indexOf('e') !== -1) { 56612 snapOrigin.x = trbl.right; 56613 } else if (direction.indexOf('w') !== -1) { 56614 snapOrigin.x = trbl.left; 56615 } 56616 56617 return snapOrigin; 56618 } 56619 56620 function isAttached(element, host) { 56621 return element.host === host; 56622 } 56623 56624 function isConnection(element) { 56625 return !!element.waypoints; 56626 } 56627 56628 function isHidden(element) { 56629 return !!element.hidden; 56630 } 56631 56632 function isLabel(element) { 56633 return !!element.labelTarget; 56634 } 56635 56636 function isHorizontal(direction) { 56637 return direction === 'n' || direction === 's'; 56638 } 56639 56640 function isVertical(direction) { 56641 return direction === 'e' || direction === 'w'; 56642 } 56643 56644 var SNAP_TOLERANCE = 7; 56645 56646 var SNAP_LINE_HIDE_DELAY = 1000; 56647 56648 56649 /** 56650 * Generic snapping feature. 56651 * 56652 * @param {EventBus} eventBus 56653 * @param {Canvas} canvas 56654 */ 56655 function Snapping(canvas) { 56656 this._canvas = canvas; 56657 56658 // delay hide by 1000 seconds since last snap 56659 this._asyncHide = debounce(bind$2(this.hide, this), SNAP_LINE_HIDE_DELAY); 56660 } 56661 56662 Snapping.$inject = [ 'canvas' ]; 56663 56664 /** 56665 * Snap an event to given snap points. 56666 * 56667 * @param {Event} event 56668 * @param {SnapPoints} snapPoints 56669 */ 56670 Snapping.prototype.snap = function(event, snapPoints) { 56671 var context = event.context, 56672 snapContext = context.snapContext, 56673 snapLocations = snapContext.getSnapLocations(); 56674 56675 var snapping = { 56676 x: isSnapped(event, 'x'), 56677 y: isSnapped(event, 'y') 56678 }; 56679 56680 forEach(snapLocations, function(location) { 56681 var snapOrigin = snapContext.getSnapOrigin(location); 56682 56683 var snapCurrent = { 56684 x: event.x + snapOrigin.x, 56685 y: event.y + snapOrigin.y 56686 }; 56687 56688 // snap both axis if not snapped already 56689 forEach([ 'x', 'y' ], function(axis) { 56690 var locationSnapping; 56691 56692 if (!snapping[axis]) { 56693 locationSnapping = snapPoints.snap(snapCurrent, location, axis, SNAP_TOLERANCE); 56694 56695 if (locationSnapping !== undefined) { 56696 snapping[axis] = { 56697 value: locationSnapping, 56698 originValue: locationSnapping - snapOrigin[axis] 56699 }; 56700 } 56701 } 56702 }); 56703 56704 // no need to continue snapping 56705 if (snapping.x && snapping.y) { 56706 return false; 56707 } 56708 }); 56709 56710 // show snap lines 56711 this.showSnapLine('vertical', snapping.x && snapping.x.value); 56712 this.showSnapLine('horizontal', snapping.y && snapping.y.value); 56713 56714 // snap event 56715 forEach([ 'x', 'y' ], function(axis) { 56716 var axisSnapping = snapping[axis]; 56717 56718 if (isObject(axisSnapping)) { 56719 setSnapped(event, axis, axisSnapping.originValue); 56720 } 56721 }); 56722 }; 56723 56724 Snapping.prototype._createLine = function(orientation) { 56725 var root = this._canvas.getLayer('snap'); 56726 56727 var line = create$1('path'); 56728 56729 attr(line, { d: 'M0,0 L0,0' }); 56730 56731 classes(line).add('djs-snap-line'); 56732 56733 append(root, line); 56734 56735 return { 56736 update: function(position) { 56737 56738 if (!isNumber(position)) { 56739 attr(line, { display: 'none' }); 56740 } else { 56741 if (orientation === 'horizontal') { 56742 attr(line, { 56743 d: 'M-100000,' + position + ' L+100000,' + position, 56744 display: '' 56745 }); 56746 } else { 56747 attr(line, { 56748 d: 'M ' + position + ',-100000 L ' + position + ', +100000', 56749 display: '' 56750 }); 56751 } 56752 } 56753 } 56754 }; 56755 }; 56756 56757 Snapping.prototype._createSnapLines = function() { 56758 this._snapLines = { 56759 horizontal: this._createLine('horizontal'), 56760 vertical: this._createLine('vertical') 56761 }; 56762 }; 56763 56764 Snapping.prototype.showSnapLine = function(orientation, position) { 56765 56766 var line = this.getSnapLine(orientation); 56767 56768 if (line) { 56769 line.update(position); 56770 } 56771 56772 this._asyncHide(); 56773 }; 56774 56775 Snapping.prototype.getSnapLine = function(orientation) { 56776 if (!this._snapLines) { 56777 this._createSnapLines(); 56778 } 56779 56780 return this._snapLines[orientation]; 56781 }; 56782 56783 Snapping.prototype.hide = function() { 56784 forEach(this._snapLines, function(snapLine) { 56785 snapLine.update(); 56786 }); 56787 }; 56788 56789 var SnappingModule$1 = { 56790 __init__: [ 56791 'createMoveSnapping', 56792 'resizeSnapping', 56793 'snapping' 56794 ], 56795 createMoveSnapping: [ 'type', CreateMoveSnapping ], 56796 resizeSnapping: [ 'type', ResizeSnapping ], 56797 snapping: [ 'type', Snapping ] 56798 }; 56799 56800 var SnappingModule = { 56801 __depends__: [ SnappingModule$1 ], 56802 __init__: [ 56803 'connectSnapping', 56804 'createMoveSnapping' 56805 ], 56806 connectSnapping: [ 'type', BpmnConnectSnapping ], 56807 createMoveSnapping: [ 'type', BpmnCreateMoveSnapping ] 56808 }; 56809 56810 /** 56811 * Provides searching infrastructure 56812 */ 56813 function SearchPad(canvas, eventBus, overlays, selection) { 56814 this._open = false; 56815 this._results = []; 56816 this._eventMaps = []; 56817 56818 this._canvas = canvas; 56819 this._eventBus = eventBus; 56820 this._overlays = overlays; 56821 this._selection = selection; 56822 56823 // setup elements 56824 this._container = domify(SearchPad.BOX_HTML); 56825 this._searchInput = query(SearchPad.INPUT_SELECTOR, this._container); 56826 this._resultsContainer = query(SearchPad.RESULTS_CONTAINER_SELECTOR, this._container); 56827 56828 // attach search pad 56829 this._canvas.getContainer().appendChild(this._container); 56830 56831 // cleanup on destroy 56832 eventBus.on([ 'canvas.destroy', 'diagram.destroy' ], this.close, this); 56833 } 56834 56835 56836 SearchPad.$inject = [ 56837 'canvas', 56838 'eventBus', 56839 'overlays', 56840 'selection' 56841 ]; 56842 56843 56844 /** 56845 * Binds and keeps track of all event listereners 56846 */ 56847 SearchPad.prototype._bindEvents = function() { 56848 var self = this; 56849 56850 function listen(el, selector, type, fn) { 56851 self._eventMaps.push({ 56852 el: el, 56853 type: type, 56854 listener: delegate.bind(el, selector, type, fn) 56855 }); 56856 } 56857 56858 // close search on clicking anywhere outside 56859 listen(document, 'html', 'click', function(e) { 56860 self.close(); 56861 }); 56862 56863 // stop event from propagating and closing search 56864 // focus on input 56865 listen(this._container, SearchPad.INPUT_SELECTOR, 'click', function(e) { 56866 e.stopPropagation(); 56867 e.delegateTarget.focus(); 56868 }); 56869 56870 // preselect result on hover 56871 listen(this._container, SearchPad.RESULT_SELECTOR, 'mouseover', function(e) { 56872 e.stopPropagation(); 56873 self._scrollToNode(e.delegateTarget); 56874 self._preselect(e.delegateTarget); 56875 }); 56876 56877 // selects desired result on mouse click 56878 listen(this._container, SearchPad.RESULT_SELECTOR, 'click', function(e) { 56879 e.stopPropagation(); 56880 self._select(e.delegateTarget); 56881 }); 56882 56883 // prevent cursor in input from going left and right when using up/down to 56884 // navigate results 56885 listen(this._container, SearchPad.INPUT_SELECTOR, 'keydown', function(e) { 56886 56887 // up 56888 if (e.keyCode === 38) { 56889 e.preventDefault(); 56890 } 56891 56892 // down 56893 if (e.keyCode === 40) { 56894 e.preventDefault(); 56895 } 56896 }); 56897 56898 // handle keyboard input 56899 listen(this._container, SearchPad.INPUT_SELECTOR, 'keyup', function(e) { 56900 56901 // escape 56902 if (e.keyCode === 27) { 56903 return self.close(); 56904 } 56905 56906 // enter 56907 if (e.keyCode === 13) { 56908 var selected = self._getCurrentResult(); 56909 56910 return selected ? self._select(selected) : self.close(); 56911 } 56912 56913 // up 56914 if (e.keyCode === 38) { 56915 return self._scrollToDirection(true); 56916 } 56917 56918 // down 56919 if (e.keyCode === 40) { 56920 return self._scrollToDirection(); 56921 } 56922 56923 // left && right 56924 // do not search while navigating text input 56925 if (e.keyCode === 37 || e.keyCode === 39) { 56926 return; 56927 } 56928 56929 // anything else 56930 self._search(e.delegateTarget.value); 56931 }); 56932 }; 56933 56934 56935 /** 56936 * Unbinds all previously established listeners 56937 */ 56938 SearchPad.prototype._unbindEvents = function() { 56939 this._eventMaps.forEach(function(m) { 56940 delegate.unbind(m.el, m.type, m.listener); 56941 }); 56942 }; 56943 56944 56945 /** 56946 * Performs a search for the given pattern. 56947 * 56948 * @param {string} pattern 56949 */ 56950 SearchPad.prototype._search = function(pattern) { 56951 var self = this; 56952 56953 this._clearResults(); 56954 56955 // do not search on empty query 56956 if (!pattern || pattern === '') { 56957 return; 56958 } 56959 56960 var searchResults = this._searchProvider.find(pattern); 56961 56962 if (!searchResults.length) { 56963 return; 56964 } 56965 56966 // append new results 56967 searchResults.forEach(function(result) { 56968 var id = result.element.id; 56969 var node = self._createResultNode(result, id); 56970 self._results[id] = { 56971 element: result.element, 56972 node: node 56973 }; 56974 }); 56975 56976 // preselect first result 56977 var node = query(SearchPad.RESULT_SELECTOR, this._resultsContainer); 56978 this._scrollToNode(node); 56979 this._preselect(node); 56980 }; 56981 56982 56983 /** 56984 * Navigate to the previous/next result. Defaults to next result. 56985 * @param {boolean} previous 56986 */ 56987 SearchPad.prototype._scrollToDirection = function(previous) { 56988 var selected = this._getCurrentResult(); 56989 if (!selected) { 56990 return; 56991 } 56992 56993 var node = previous ? selected.previousElementSibling : selected.nextElementSibling; 56994 if (node) { 56995 this._scrollToNode(node); 56996 this._preselect(node); 56997 } 56998 }; 56999 57000 57001 /** 57002 * Scroll to the node if it is not visible. 57003 * 57004 * @param {Element} node 57005 */ 57006 SearchPad.prototype._scrollToNode = function(node) { 57007 if (!node || node === this._getCurrentResult()) { 57008 return; 57009 } 57010 57011 var nodeOffset = node.offsetTop; 57012 var containerScroll = this._resultsContainer.scrollTop; 57013 57014 var bottomScroll = nodeOffset - this._resultsContainer.clientHeight + node.clientHeight; 57015 57016 if (nodeOffset < containerScroll) { 57017 this._resultsContainer.scrollTop = nodeOffset; 57018 } else if (containerScroll < bottomScroll) { 57019 this._resultsContainer.scrollTop = bottomScroll; 57020 } 57021 }; 57022 57023 57024 /** 57025 * Clears all results data. 57026 */ 57027 SearchPad.prototype._clearResults = function() { 57028 clear$1(this._resultsContainer); 57029 57030 this._results = []; 57031 57032 this._resetOverlay(); 57033 57034 this._eventBus.fire('searchPad.cleared'); 57035 }; 57036 57037 57038 /** 57039 * Get currently selected result. 57040 * 57041 * @return {Element} 57042 */ 57043 SearchPad.prototype._getCurrentResult = function() { 57044 return query(SearchPad.RESULT_SELECTED_SELECTOR, this._resultsContainer); 57045 }; 57046 57047 57048 /** 57049 * Create result DOM element within results container 57050 * that corresponds to a search result. 57051 * 57052 * 'result' : one of the elements returned by SearchProvider 57053 * 'id' : id attribute value to assign to the new DOM node 57054 * return : created DOM element 57055 * 57056 * @param {SearchResult} result 57057 * @param {string} id 57058 * @return {Element} 57059 */ 57060 SearchPad.prototype._createResultNode = function(result, id) { 57061 var node = domify(SearchPad.RESULT_HTML); 57062 57063 // create only if available 57064 if (result.primaryTokens.length > 0) { 57065 createInnerTextNode(node, result.primaryTokens, SearchPad.RESULT_PRIMARY_HTML); 57066 } 57067 57068 // secondary tokens (represent element ID) are allways available 57069 createInnerTextNode(node, result.secondaryTokens, SearchPad.RESULT_SECONDARY_HTML); 57070 57071 attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE, id); 57072 57073 this._resultsContainer.appendChild(node); 57074 57075 return node; 57076 }; 57077 57078 57079 /** 57080 * Register search element provider. 57081 * 57082 * SearchProvider.find - provides search function over own elements 57083 * (pattern) => [{ text: <String>, element: <Element>}, ...] 57084 * 57085 * @param {SearchProvider} provider 57086 */ 57087 SearchPad.prototype.registerProvider = function(provider) { 57088 this._searchProvider = provider; 57089 }; 57090 57091 57092 /** 57093 * Open search pad. 57094 */ 57095 SearchPad.prototype.open = function() { 57096 if (!this._searchProvider) { 57097 throw new Error('no search provider registered'); 57098 } 57099 57100 if (this.isOpen()) { 57101 return; 57102 } 57103 57104 this._bindEvents(); 57105 57106 this._open = true; 57107 57108 classes$1(this._container).add('open'); 57109 57110 this._searchInput.focus(); 57111 57112 this._eventBus.fire('searchPad.opened'); 57113 }; 57114 57115 57116 /** 57117 * Close search pad. 57118 */ 57119 SearchPad.prototype.close = function() { 57120 if (!this.isOpen()) { 57121 return; 57122 } 57123 57124 this._unbindEvents(); 57125 57126 this._open = false; 57127 57128 classes$1(this._container).remove('open'); 57129 57130 this._clearResults(); 57131 57132 this._searchInput.value = ''; 57133 this._searchInput.blur(); 57134 57135 this._resetOverlay(); 57136 57137 this._eventBus.fire('searchPad.closed'); 57138 }; 57139 57140 57141 /** 57142 * Toggles search pad on/off. 57143 */ 57144 SearchPad.prototype.toggle = function() { 57145 this.isOpen() ? this.close() : this.open(); 57146 }; 57147 57148 57149 /** 57150 * Report state of search pad. 57151 */ 57152 SearchPad.prototype.isOpen = function() { 57153 return this._open; 57154 }; 57155 57156 57157 /** 57158 * Preselect result entry. 57159 * 57160 * @param {Element} element 57161 */ 57162 SearchPad.prototype._preselect = function(node) { 57163 var selectedNode = this._getCurrentResult(); 57164 57165 // already selected 57166 if (node === selectedNode) { 57167 return; 57168 } 57169 57170 // removing preselection from current node 57171 if (selectedNode) { 57172 classes$1(selectedNode).remove(SearchPad.RESULT_SELECTED_CLASS); 57173 } 57174 57175 var id = attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE); 57176 var element = this._results[id].element; 57177 57178 classes$1(node).add(SearchPad.RESULT_SELECTED_CLASS); 57179 57180 this._resetOverlay(element); 57181 57182 this._canvas.scrollToElement(element, { top: 400 }); 57183 57184 this._selection.select(element); 57185 57186 this._eventBus.fire('searchPad.preselected', element); 57187 }; 57188 57189 57190 /** 57191 * Select result node. 57192 * 57193 * @param {Element} element 57194 */ 57195 SearchPad.prototype._select = function(node) { 57196 var id = attr$1(node, SearchPad.RESULT_ID_ATTRIBUTE); 57197 var element = this._results[id].element; 57198 57199 this.close(); 57200 57201 this._resetOverlay(); 57202 57203 this._canvas.scrollToElement(element, { top: 400 }); 57204 57205 this._selection.select(element); 57206 57207 this._eventBus.fire('searchPad.selected', element); 57208 }; 57209 57210 57211 /** 57212 * Reset overlay removes and, optionally, set 57213 * overlay to a new element. 57214 * 57215 * @param {Element} element 57216 */ 57217 SearchPad.prototype._resetOverlay = function(element) { 57218 if (this._overlayId) { 57219 this._overlays.remove(this._overlayId); 57220 } 57221 57222 if (element) { 57223 var box = getBBox(element); 57224 var overlay = constructOverlay(box); 57225 this._overlayId = this._overlays.add(element, overlay); 57226 } 57227 }; 57228 57229 57230 /** 57231 * Construct overlay object for the given bounding box. 57232 * 57233 * @param {BoundingBox} box 57234 * @return {Object} 57235 */ 57236 function constructOverlay(box) { 57237 57238 var offset = 6; 57239 var w = box.width + offset * 2; 57240 var h = box.height + offset * 2; 57241 57242 var styles = [ 57243 'width: '+ w +'px', 57244 'height: '+ h + 'px' 57245 ].join('; '); 57246 57247 return { 57248 position: { 57249 bottom: h - offset, 57250 right: w - offset 57251 }, 57252 show: true, 57253 html: '<div style="' + styles + '" class="' + SearchPad.OVERLAY_CLASS + '"></div>' 57254 }; 57255 } 57256 57257 57258 /** 57259 * Creates and appends child node from result tokens and HTML template. 57260 * 57261 * @param {Element} node 57262 * @param {Array<Object>} tokens 57263 * @param {string} template 57264 */ 57265 function createInnerTextNode(parentNode, tokens, template) { 57266 var text = createHtmlText(tokens); 57267 var childNode = domify(template); 57268 childNode.innerHTML = text; 57269 parentNode.appendChild(childNode); 57270 } 57271 57272 /** 57273 * Create internal HTML markup from result tokens. 57274 * Caters for highlighting pattern matched tokens. 57275 * 57276 * @param {Array<Object>} tokens 57277 * @return {string} 57278 */ 57279 function createHtmlText(tokens) { 57280 var htmlText = ''; 57281 57282 tokens.forEach(function(t) { 57283 if (t.matched) { 57284 htmlText += '<strong class="' + SearchPad.RESULT_HIGHLIGHT_CLASS + '">' + escapeHTML(t.matched) + '</strong>'; 57285 } else { 57286 htmlText += escapeHTML(t.normal); 57287 } 57288 }); 57289 57290 return htmlText !== '' ? htmlText : null; 57291 } 57292 57293 57294 /** 57295 * CONSTANTS 57296 */ 57297 SearchPad.CONTAINER_SELECTOR = '.djs-search-container'; 57298 SearchPad.INPUT_SELECTOR = '.djs-search-input input'; 57299 SearchPad.RESULTS_CONTAINER_SELECTOR = '.djs-search-results'; 57300 SearchPad.RESULT_SELECTOR = '.djs-search-result'; 57301 SearchPad.RESULT_SELECTED_CLASS = 'djs-search-result-selected'; 57302 SearchPad.RESULT_SELECTED_SELECTOR = '.' + SearchPad.RESULT_SELECTED_CLASS; 57303 SearchPad.RESULT_ID_ATTRIBUTE = 'data-result-id'; 57304 SearchPad.RESULT_HIGHLIGHT_CLASS = 'djs-search-highlight'; 57305 SearchPad.OVERLAY_CLASS = 'djs-search-overlay'; 57306 57307 SearchPad.BOX_HTML = 57308 '<div class="djs-search-container djs-draggable djs-scrollable">' + 57309 '<div class="djs-search-input">' + 57310 '<input type="text"/>' + 57311 '</div>' + 57312 '<div class="djs-search-results"></div>' + 57313 '</div>'; 57314 57315 SearchPad.RESULT_HTML = 57316 '<div class="djs-search-result"></div>'; 57317 57318 SearchPad.RESULT_PRIMARY_HTML = 57319 '<div class="djs-search-result-primary"></div>'; 57320 57321 SearchPad.RESULT_SECONDARY_HTML = 57322 '<p class="djs-search-result-secondary"></p>'; 57323 57324 var SearchPadModule = { 57325 __depends__: [ 57326 OverlaysModule, 57327 SelectionModule 57328 ], 57329 searchPad: [ 'type', SearchPad ] 57330 }; 57331 57332 /** 57333 * Provides ability to search through BPMN elements 57334 */ 57335 function BpmnSearchProvider(elementRegistry, searchPad, canvas) { 57336 57337 this._elementRegistry = elementRegistry; 57338 this._canvas = canvas; 57339 57340 searchPad.registerProvider(this); 57341 } 57342 57343 BpmnSearchProvider.$inject = [ 57344 'elementRegistry', 57345 'searchPad', 57346 'canvas' 57347 ]; 57348 57349 57350 /** 57351 * Finds all elements that match given pattern 57352 * 57353 * <Result> : 57354 * { 57355 * primaryTokens: <Array<Token>>, 57356 * secondaryTokens: <Array<Token>>, 57357 * element: <Element> 57358 * } 57359 * 57360 * <Token> : 57361 * { 57362 * normal|matched: <string> 57363 * } 57364 * 57365 * @param {string} pattern 57366 * @return {Array<Result>} 57367 */ 57368 BpmnSearchProvider.prototype.find = function(pattern) { 57369 var rootElement = this._canvas.getRootElement(); 57370 57371 var elements = this._elementRegistry.filter(function(element) { 57372 if (element.labelTarget) { 57373 return false; 57374 } 57375 return true; 57376 }); 57377 57378 // do not include root element 57379 elements = filter(elements, function(element) { 57380 return element !== rootElement; 57381 }); 57382 57383 elements = map$1(elements, function(element) { 57384 return { 57385 primaryTokens: matchAndSplit(getLabel(element), pattern), 57386 secondaryTokens: matchAndSplit(element.id, pattern), 57387 element: element 57388 }; 57389 }); 57390 57391 // exclude non-matched elements 57392 elements = filter(elements, function(element) { 57393 return hasMatched(element.primaryTokens) || hasMatched(element.secondaryTokens); 57394 }); 57395 57396 elements = sortBy(elements, function(element) { 57397 return getLabel(element.element) + element.element.id; 57398 }); 57399 57400 return elements; 57401 }; 57402 57403 57404 function hasMatched(tokens) { 57405 var matched = filter(tokens, function(t) { 57406 return !!t.matched; 57407 }); 57408 57409 return matched.length > 0; 57410 } 57411 57412 57413 function matchAndSplit(text, pattern) { 57414 var tokens = [], 57415 originalText = text; 57416 57417 if (!text) { 57418 return tokens; 57419 } 57420 57421 text = text.toLowerCase(); 57422 pattern = pattern.toLowerCase(); 57423 57424 var i = text.indexOf(pattern); 57425 57426 if (i > -1) { 57427 if (i !== 0) { 57428 tokens.push({ 57429 normal: originalText.substr(0, i) 57430 }); 57431 } 57432 57433 tokens.push({ 57434 matched: originalText.substr(i, pattern.length) 57435 }); 57436 57437 if (pattern.length + i < text.length) { 57438 tokens.push({ 57439 normal: originalText.substr(pattern.length + i, text.length) 57440 }); 57441 } 57442 } else { 57443 tokens.push({ 57444 normal: originalText 57445 }); 57446 } 57447 57448 return tokens; 57449 } 57450 57451 var SearchModule = { 57452 __depends__: [ 57453 SearchPadModule 57454 ], 57455 __init__: [ 'bpmnSearch'], 57456 bpmnSearch: [ 'type', BpmnSearchProvider ] 57457 }; 57458 57459 var initialDiagram = 57460 '<?xml version="1.0" encoding="UTF-8"?>' + 57461 '<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' + 57462 'xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" ' + 57463 'xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" ' + 57464 'xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" ' + 57465 'targetNamespace="http://bpmn.io/schema/bpmn" ' + 57466 'id="Definitions_1">' + 57467 '<bpmn:process id="Process_1" isExecutable="false">' + 57468 '<bpmn:startEvent id="StartEvent_1"/>' + 57469 '</bpmn:process>' + 57470 '<bpmndi:BPMNDiagram id="BPMNDiagram_1">' + 57471 '<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">' + 57472 '<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">' + 57473 '<dc:Bounds height="36.0" width="36.0" x="173.0" y="102.0"/>' + 57474 '</bpmndi:BPMNShape>' + 57475 '</bpmndi:BPMNPlane>' + 57476 '</bpmndi:BPMNDiagram>' + 57477 '</bpmn:definitions>'; 57478 57479 57480 /** 57481 * A modeler for BPMN 2.0 diagrams. 57482 * 57483 * 57484 * ## Extending the Modeler 57485 * 57486 * In order to extend the viewer pass extension modules to bootstrap via the 57487 * `additionalModules` option. An extension module is an object that exposes 57488 * named services. 57489 * 57490 * The following example depicts the integration of a simple 57491 * logging component that integrates with interaction events: 57492 * 57493 * 57494 * ```javascript 57495 * 57496 * // logging component 57497 * function InteractionLogger(eventBus) { 57498 * eventBus.on('element.hover', function(event) { 57499 * console.log() 57500 * }) 57501 * } 57502 * 57503 * InteractionLogger.$inject = [ 'eventBus' ]; // minification save 57504 * 57505 * // extension module 57506 * var extensionModule = { 57507 * __init__: [ 'interactionLogger' ], 57508 * interactionLogger: [ 'type', InteractionLogger ] 57509 * }; 57510 * 57511 * // extend the viewer 57512 * var bpmnModeler = new Modeler({ additionalModules: [ extensionModule ] }); 57513 * bpmnModeler.importXML(...); 57514 * ``` 57515 * 57516 * 57517 * ## Customizing / Replacing Components 57518 * 57519 * You can replace individual diagram components by redefining them in override modules. 57520 * This works for all components, including those defined in the core. 57521 * 57522 * Pass in override modules via the `options.additionalModules` flag like this: 57523 * 57524 * ```javascript 57525 * function CustomContextPadProvider(contextPad) { 57526 * 57527 * contextPad.registerProvider(this); 57528 * 57529 * this.getContextPadEntries = function(element) { 57530 * // no entries, effectively disable the context pad 57531 * return {}; 57532 * }; 57533 * } 57534 * 57535 * CustomContextPadProvider.$inject = [ 'contextPad' ]; 57536 * 57537 * var overrideModule = { 57538 * contextPadProvider: [ 'type', CustomContextPadProvider ] 57539 * }; 57540 * 57541 * var bpmnModeler = new Modeler({ additionalModules: [ overrideModule ]}); 57542 * ``` 57543 * 57544 * @param {Object} [options] configuration options to pass to the viewer 57545 * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body. 57546 * @param {string|number} [options.width] the width of the viewer 57547 * @param {string|number} [options.height] the height of the viewer 57548 * @param {Object} [options.moddleExtensions] extension packages to provide 57549 * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules 57550 * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules 57551 */ 57552 function Modeler(options) { 57553 BaseModeler.call(this, options); 57554 } 57555 57556 inherits$1(Modeler, BaseModeler); 57557 57558 57559 Modeler.Viewer = Viewer; 57560 Modeler.NavigatedViewer = NavigatedViewer; 57561 57562 /** 57563 * The createDiagram result. 57564 * 57565 * @typedef {Object} CreateDiagramResult 57566 * 57567 * @property {Array<string>} warnings 57568 */ 57569 57570 /** 57571 * The createDiagram error. 57572 * 57573 * @typedef {Error} CreateDiagramError 57574 * 57575 * @property {Array<string>} warnings 57576 */ 57577 57578 /** 57579 * Create a new diagram to start modeling. 57580 * 57581 * Returns {Promise<CreateDiagramResult, CreateDiagramError>} 57582 */ 57583 Modeler.prototype.createDiagram = wrapForCompatibility(function createDiagram() { 57584 return this.importXML(initialDiagram); 57585 }); 57586 57587 57588 Modeler.prototype._interactionModules = [ 57589 57590 // non-modeling components 57591 KeyboardMoveModule, 57592 MoveCanvasModule, 57593 TouchModule, 57594 ZoomScrollModule 57595 ]; 57596 57597 Modeler.prototype._modelingModules = [ 57598 57599 // modeling components 57600 AlignElementsModule, 57601 AutoPlaceModule, 57602 AutoScrollModule, 57603 AutoResizeModule, 57604 BendpointsModule, 57605 ConnectModule, 57606 ConnectionPreviewModule, 57607 ContextPadModule, 57608 CopyPasteModule, 57609 CreateModule, 57610 DistributeElementsModule, 57611 EditorActionsModule, 57612 GridSnappingModule, 57613 InteractionEventsModule, 57614 KeyboardModule, 57615 KeyboardMoveSelectionModule, 57616 LabelEditingModule, 57617 ModelingModule, 57618 MoveModule, 57619 PaletteModule, 57620 ReplacePreviewModule, 57621 ResizeModule, 57622 SnappingModule, 57623 SearchModule 57624 ]; 57625 57626 57627 // modules the modeler is composed of 57628 // 57629 // - viewer modules 57630 // - interaction modules 57631 // - modeling modules 57632 57633 Modeler.prototype._modules = [].concat( 57634 Viewer.prototype._modules, 57635 Modeler.prototype._interactionModules, 57636 Modeler.prototype._modelingModules 57637 ); 57638 57639 return Modeler; 57640 57641}))); 57642