BpmnRules.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  1. import {
  2. every,
  3. find,
  4. forEach,
  5. some
  6. } from 'min-dash';
  7. import inherits from 'inherits-browser';
  8. import {
  9. is,
  10. getBusinessObject
  11. } from '../../util/ModelUtil';
  12. import {
  13. getParent,
  14. isAny
  15. } from '../modeling/util/ModelingUtil';
  16. import {
  17. isLabel
  18. } from '../../util/LabelUtil';
  19. import {
  20. isExpanded,
  21. isEventSubProcess,
  22. isInterrupting,
  23. hasErrorEventDefinition,
  24. hasEscalationEventDefinition,
  25. hasCompensateEventDefinition
  26. } from '../../util/DiUtil';
  27. import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
  28. import {
  29. getBoundaryAttachment as isBoundaryAttachment
  30. } from '../snapping/BpmnSnappingUtil';
  31. import { isConnection } from 'diagram-js/lib/util/ModelUtil';
  32. /**
  33. * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
  34. *
  35. * @typedef {import('../../model/Types').Connection} Connection
  36. * @typedef {import('../../model/Types').Element} Element
  37. * @typedef {import('../../model/Types').Shape} Shape
  38. * @typedef {import('../../model/Types').ModdleElement} ModdleElement
  39. *
  40. * @typedef {import('diagram-js/lib/util/Types').Point} Point
  41. * @typedef {import('diagram-js/lib/util/Types').Rect} Rect
  42. *
  43. * @typedef { {
  44. * associationDirection?: 'None' | 'One' | 'Both';
  45. * type: string;
  46. * } | boolean | null } CanConnectResult
  47. *
  48. * @typedef { {
  49. * id: string;
  50. * type: string;
  51. * } | boolean } CanReplaceResult
  52. */
  53. /**
  54. * BPMN-specific modeling rules.
  55. *
  56. * @param {EventBus} eventBus
  57. */
  58. export default function BpmnRules(eventBus) {
  59. RuleProvider.call(this, eventBus);
  60. }
  61. inherits(BpmnRules, RuleProvider);
  62. BpmnRules.$inject = [ 'eventBus' ];
  63. BpmnRules.prototype.init = function() {
  64. this.addRule('connection.start', function(context) {
  65. var source = context.source;
  66. return canStartConnection(source);
  67. });
  68. this.addRule('connection.create', function(context) {
  69. var source = context.source,
  70. target = context.target,
  71. hints = context.hints || {},
  72. targetParent = hints.targetParent,
  73. targetAttach = hints.targetAttach;
  74. // don't allow incoming connections on
  75. // newly created boundary events
  76. // to boundary events
  77. if (targetAttach) {
  78. return false;
  79. }
  80. // temporarily set target parent for scoping
  81. // checks to work
  82. if (targetParent) {
  83. target.parent = targetParent;
  84. }
  85. try {
  86. return canConnect(source, target);
  87. } finally {
  88. // unset temporary target parent
  89. if (targetParent) {
  90. target.parent = null;
  91. }
  92. }
  93. });
  94. this.addRule('connection.reconnect', function(context) {
  95. var connection = context.connection,
  96. source = context.source,
  97. target = context.target;
  98. return canConnect(source, target, connection);
  99. });
  100. this.addRule('connection.updateWaypoints', function(context) {
  101. return {
  102. type: context.connection.type
  103. };
  104. });
  105. this.addRule('shape.resize', function(context) {
  106. var shape = context.shape,
  107. newBounds = context.newBounds;
  108. return canResize(shape, newBounds);
  109. });
  110. this.addRule('elements.create', function(context) {
  111. var elements = context.elements,
  112. position = context.position,
  113. target = context.target;
  114. if (isConnection(target) && !canInsert(elements, target, position)) {
  115. return false;
  116. }
  117. return every(elements, function(element) {
  118. if (isConnection(element)) {
  119. return canConnect(element.source, element.target, element);
  120. }
  121. if (element.host) {
  122. return canAttach(element, element.host, null, position);
  123. }
  124. return canCreate(element, target, null, position);
  125. });
  126. });
  127. this.addRule('elements.move', function(context) {
  128. var target = context.target,
  129. shapes = context.shapes,
  130. position = context.position;
  131. return canAttach(shapes, target, null, position) ||
  132. canReplace(shapes, target, position) ||
  133. canMove(shapes, target, position) ||
  134. canInsert(shapes, target, position);
  135. });
  136. this.addRule('shape.create', function(context) {
  137. return canCreate(
  138. context.shape,
  139. context.target,
  140. context.source,
  141. context.position
  142. );
  143. });
  144. this.addRule('shape.attach', function(context) {
  145. return canAttach(
  146. context.shape,
  147. context.target,
  148. null,
  149. context.position
  150. );
  151. });
  152. this.addRule('element.copy', function(context) {
  153. var element = context.element,
  154. elements = context.elements;
  155. return canCopy(elements, element);
  156. });
  157. };
  158. BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;
  159. BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
  160. BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;
  161. BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
  162. BpmnRules.prototype.canConnectCompensationAssociation = canConnectCompensationAssociation;
  163. BpmnRules.prototype.canMove = canMove;
  164. BpmnRules.prototype.canAttach = canAttach;
  165. BpmnRules.prototype.canReplace = canReplace;
  166. BpmnRules.prototype.canDrop = canDrop;
  167. BpmnRules.prototype.canInsert = canInsert;
  168. BpmnRules.prototype.canCreate = canCreate;
  169. BpmnRules.prototype.canConnect = canConnect;
  170. BpmnRules.prototype.canResize = canResize;
  171. BpmnRules.prototype.canCopy = canCopy;
  172. /**
  173. * Utility functions for rule checking
  174. */
  175. /**
  176. * Checks if given element can be used for starting connection.
  177. *
  178. * @param {Element} source
  179. *
  180. * @return {boolean}
  181. */
  182. function canStartConnection(element) {
  183. if (nonExistingOrLabel(element)) {
  184. return null;
  185. }
  186. return isAny(element, [
  187. 'bpmn:FlowNode',
  188. 'bpmn:InteractionNode',
  189. 'bpmn:DataObjectReference',
  190. 'bpmn:DataStoreReference',
  191. 'bpmn:Group',
  192. 'bpmn:TextAnnotation'
  193. ]);
  194. }
  195. /**
  196. * @param {Element} element
  197. *
  198. * @return {boolean}
  199. */
  200. function nonExistingOrLabel(element) {
  201. return !element || isLabel(element);
  202. }
  203. function isSame(a, b) {
  204. return a === b;
  205. }
  206. /**
  207. * @param {Element} element
  208. *
  209. * @return {ModdleElement}
  210. */
  211. function getOrganizationalParent(element) {
  212. do {
  213. if (is(element, 'bpmn:Process')) {
  214. return getBusinessObject(element);
  215. }
  216. if (is(element, 'bpmn:Participant')) {
  217. return (
  218. getBusinessObject(element).processRef ||
  219. getBusinessObject(element)
  220. );
  221. }
  222. } while ((element = element.parent));
  223. }
  224. /**
  225. * @param {Element} element
  226. *
  227. * @return {boolean}
  228. */
  229. function isTextAnnotation(element) {
  230. return is(element, 'bpmn:TextAnnotation');
  231. }
  232. /**
  233. * @param {Element} element
  234. *
  235. * @return {boolean}
  236. */
  237. function isGroup(element) {
  238. return is(element, 'bpmn:Group') && !element.labelTarget;
  239. }
  240. /**
  241. * @param {Element} element
  242. *
  243. * @return {boolean}
  244. */
  245. function isCompensationBoundary(element) {
  246. return is(element, 'bpmn:BoundaryEvent') &&
  247. hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
  248. }
  249. /**
  250. * @param {Element} element
  251. *
  252. * @return {boolean}
  253. */
  254. function isForCompensation(element) {
  255. return getBusinessObject(element).isForCompensation;
  256. }
  257. /**
  258. * @param {Element} a
  259. * @param {Element} b
  260. *
  261. * @return {boolean}
  262. */
  263. function isSameOrganization(a, b) {
  264. var parentA = getOrganizationalParent(a),
  265. parentB = getOrganizationalParent(b);
  266. return parentA === parentB;
  267. }
  268. /**
  269. * @param {Element} element
  270. *
  271. * @return {boolean}
  272. */
  273. function isMessageFlowSource(element) {
  274. return (
  275. is(element, 'bpmn:InteractionNode') &&
  276. !is(element, 'bpmn:BoundaryEvent') && (
  277. !is(element, 'bpmn:Event') || (
  278. is(element, 'bpmn:ThrowEvent') &&
  279. hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
  280. )
  281. )
  282. );
  283. }
  284. /**
  285. * @param {Element} element
  286. *
  287. * @return {boolean}
  288. */
  289. function isMessageFlowTarget(element) {
  290. return (
  291. is(element, 'bpmn:InteractionNode') &&
  292. !isForCompensation(element) && (
  293. !is(element, 'bpmn:Event') || (
  294. is(element, 'bpmn:CatchEvent') &&
  295. hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
  296. )
  297. ) && !(
  298. is(element, 'bpmn:BoundaryEvent') &&
  299. !hasEventDefinition(element, 'bpmn:MessageEventDefinition')
  300. )
  301. );
  302. }
  303. /**
  304. * @param {Element} element
  305. *
  306. * @return {ModdleElement}
  307. */
  308. function getScopeParent(element) {
  309. var parent = element;
  310. while ((parent = parent.parent)) {
  311. if (is(parent, 'bpmn:FlowElementsContainer')) {
  312. return getBusinessObject(parent);
  313. }
  314. if (is(parent, 'bpmn:Participant')) {
  315. return getBusinessObject(parent).processRef;
  316. }
  317. }
  318. return null;
  319. }
  320. /**
  321. * @param {Element} a
  322. * @param {Element} b
  323. *
  324. * @return {boolean}
  325. */
  326. function isSameScope(a, b) {
  327. var scopeParentA = getScopeParent(a),
  328. scopeParentB = getScopeParent(b);
  329. return scopeParentA === scopeParentB;
  330. }
  331. /**
  332. * @param {Element} element
  333. * @param {string} eventDefinition
  334. *
  335. * @return {boolean}
  336. */
  337. function hasEventDefinition(element, eventDefinition) {
  338. var businessObject = getBusinessObject(element);
  339. return !!find(businessObject.eventDefinitions || [], function(definition) {
  340. return is(definition, eventDefinition);
  341. });
  342. }
  343. /**
  344. * @param {Element} element
  345. * @param {string} eventDefinition
  346. *
  347. * @return {boolean}
  348. */
  349. function hasEventDefinitionOrNone(element, eventDefinition) {
  350. var businessObject = getBusinessObject(element);
  351. return (businessObject.eventDefinitions || []).every(function(definition) {
  352. return is(definition, eventDefinition);
  353. });
  354. }
  355. /**
  356. * @param {Element} element
  357. *
  358. * @return {boolean}
  359. */
  360. function isSequenceFlowSource(element) {
  361. return (
  362. is(element, 'bpmn:FlowNode') &&
  363. !is(element, 'bpmn:EndEvent') &&
  364. !isEventSubProcess(element) &&
  365. !(is(element, 'bpmn:IntermediateThrowEvent') &&
  366. hasEventDefinition(element, 'bpmn:LinkEventDefinition')
  367. ) &&
  368. !isCompensationBoundary(element) &&
  369. !isForCompensation(element)
  370. );
  371. }
  372. /**
  373. * @param {Element} element
  374. *
  375. * @return {boolean}
  376. */
  377. function isSequenceFlowTarget(element) {
  378. return (
  379. is(element, 'bpmn:FlowNode') &&
  380. !is(element, 'bpmn:StartEvent') &&
  381. !is(element, 'bpmn:BoundaryEvent') &&
  382. !isEventSubProcess(element) &&
  383. !(is(element, 'bpmn:IntermediateCatchEvent') &&
  384. hasEventDefinition(element, 'bpmn:LinkEventDefinition')
  385. ) &&
  386. !isForCompensation(element)
  387. );
  388. }
  389. /**
  390. * @param {Element} element
  391. *
  392. * @return {boolean}
  393. */
  394. function isEventBasedTarget(element) {
  395. return (
  396. is(element, 'bpmn:ReceiveTask') || (
  397. is(element, 'bpmn:IntermediateCatchEvent') && (
  398. hasEventDefinition(element, 'bpmn:MessageEventDefinition') ||
  399. hasEventDefinition(element, 'bpmn:TimerEventDefinition') ||
  400. hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') ||
  401. hasEventDefinition(element, 'bpmn:SignalEventDefinition')
  402. )
  403. )
  404. );
  405. }
  406. /**
  407. * @param {Element} element
  408. *
  409. * @return {Shape[]}
  410. */
  411. function getParents(element) {
  412. var parents = [];
  413. while (element) {
  414. element = element.parent;
  415. if (element) {
  416. parents.push(element);
  417. }
  418. }
  419. return parents;
  420. }
  421. /**
  422. * @param {Shape} possibleParent
  423. * @param {Element} element
  424. *
  425. * @return {boolean}
  426. */
  427. function isParent(possibleParent, element) {
  428. var allParents = getParents(element);
  429. return allParents.indexOf(possibleParent) !== -1;
  430. }
  431. /**
  432. * @param {Element} source
  433. * @param {Element} target
  434. * @param {Connection} connection
  435. *
  436. * @return {CanConnectResult}
  437. */
  438. function canConnect(source, target, connection) {
  439. if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
  440. return null;
  441. }
  442. if (!is(connection, 'bpmn:DataAssociation')) {
  443. if (canConnectMessageFlow(source, target)) {
  444. return { type: 'bpmn:MessageFlow' };
  445. }
  446. if (canConnectSequenceFlow(source, target)) {
  447. return { type: 'bpmn:SequenceFlow' };
  448. }
  449. }
  450. var connectDataAssociation = canConnectDataAssociation(source, target);
  451. if (connectDataAssociation) {
  452. return connectDataAssociation;
  453. }
  454. if (canConnectCompensationAssociation(source, target)) {
  455. return {
  456. type: 'bpmn:Association',
  457. associationDirection: 'One'
  458. };
  459. }
  460. if (canConnectAssociation(source, target)) {
  461. return {
  462. type: 'bpmn:Association',
  463. associationDirection: 'None'
  464. };
  465. }
  466. return false;
  467. }
  468. /**
  469. * Can an element be dropped into the target element.
  470. *
  471. * @param {Element} element
  472. * @param {Shape} target
  473. *
  474. * @return {boolean}
  475. */
  476. function canDrop(element, target) {
  477. // can move labels and groups everywhere
  478. if (isLabel(element) || isGroup(element)) {
  479. return true;
  480. }
  481. // disallow to create elements on collapsed pools
  482. if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
  483. return false;
  484. }
  485. // allow to create new participants on
  486. // existing collaboration and process diagrams
  487. if (is(element, 'bpmn:Participant')) {
  488. return is(target, 'bpmn:Process') || is(target, 'bpmn:Collaboration');
  489. }
  490. // allow moving DataInput / DataOutput within its original container only
  491. if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {
  492. if (element.parent) {
  493. return target === element.parent;
  494. }
  495. }
  496. // allow creating lanes on participants and other lanes only
  497. if (is(element, 'bpmn:Lane')) {
  498. return is(target, 'bpmn:Participant') || is(target, 'bpmn:Lane');
  499. }
  500. // disallow dropping boundary events which cannot replace with intermediate event
  501. if (is(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
  502. return false;
  503. }
  504. // drop flow elements onto flow element containers
  505. // and participants
  506. if (is(element, 'bpmn:FlowElement') && !is(element, 'bpmn:DataStoreReference')) {
  507. if (is(target, 'bpmn:FlowElementsContainer')) {
  508. return isExpanded(target);
  509. }
  510. return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
  511. }
  512. // disallow dropping data store reference if there is no process to append to
  513. if (is(element, 'bpmn:DataStoreReference') && is(target, 'bpmn:Collaboration')) {
  514. return some(getBusinessObject(target).get('participants'), function(participant) {
  515. return !!participant.get('processRef');
  516. });
  517. }
  518. // account for the fact that data associations are always
  519. // rendered and moved to top (Process or Collaboration level)
  520. //
  521. // artifacts may be placed wherever, too
  522. if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
  523. return isAny(target, [
  524. 'bpmn:Collaboration',
  525. 'bpmn:Lane',
  526. 'bpmn:Participant',
  527. 'bpmn:Process',
  528. 'bpmn:SubProcess' ]);
  529. }
  530. if (is(element, 'bpmn:MessageFlow')) {
  531. return is(target, 'bpmn:Collaboration')
  532. || element.source.parent == target
  533. || element.target.parent == target;
  534. }
  535. return false;
  536. }
  537. /**
  538. * @param {Shape} event
  539. *
  540. * @return {boolean}
  541. */
  542. function isDroppableBoundaryEvent(event) {
  543. return getBusinessObject(event).cancelActivity && (
  544. hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
  545. );
  546. }
  547. /**
  548. * @param {Element} element
  549. *
  550. * @return {boolean}
  551. */
  552. function isBoundaryEvent(element) {
  553. return !isLabel(element) && is(element, 'bpmn:BoundaryEvent');
  554. }
  555. /**
  556. * @param {Element} element
  557. *
  558. * @return {boolean}
  559. */
  560. function isLane(element) {
  561. return is(element, 'bpmn:Lane');
  562. }
  563. /**
  564. * `bpmn:IntermediateThrowEvents` are treated as boundary events during create.
  565. *
  566. * @param {Element} element
  567. *
  568. * @return {boolean}
  569. */
  570. function isBoundaryCandidate(element) {
  571. if (isBoundaryEvent(element)) {
  572. return true;
  573. }
  574. if (is(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
  575. return true;
  576. }
  577. return (
  578. is(element, 'bpmn:IntermediateCatchEvent') &&
  579. hasCommonBoundaryIntermediateEventDefinition(element)
  580. );
  581. }
  582. /**
  583. * @param {Element} element
  584. *
  585. * @return {boolean}
  586. */
  587. function hasNoEventDefinition(element) {
  588. var businessObject = getBusinessObject(element);
  589. return businessObject && !(businessObject.eventDefinitions && businessObject.eventDefinitions.length);
  590. }
  591. /**
  592. * @param {Element} element
  593. *
  594. * @return {boolean}
  595. */
  596. function hasCommonBoundaryIntermediateEventDefinition(element) {
  597. return hasOneOfEventDefinitions(element, [
  598. 'bpmn:MessageEventDefinition',
  599. 'bpmn:TimerEventDefinition',
  600. 'bpmn:SignalEventDefinition',
  601. 'bpmn:ConditionalEventDefinition'
  602. ]);
  603. }
  604. /**
  605. * @param {Element} element
  606. * @param {string[]} eventDefinitions
  607. *
  608. * @return {boolean}
  609. */
  610. function hasOneOfEventDefinitions(element, eventDefinitions) {
  611. return eventDefinitions.some(function(definition) {
  612. return hasEventDefinition(element, definition);
  613. });
  614. }
  615. /**
  616. * @param {Element} element
  617. *
  618. * @return {boolean}
  619. */
  620. function isReceiveTaskAfterEventBasedGateway(element) {
  621. return (
  622. is(element, 'bpmn:ReceiveTask') &&
  623. find(element.incoming, function(incoming) {
  624. return is(incoming.source, 'bpmn:EventBasedGateway');
  625. })
  626. );
  627. }
  628. /**
  629. * TODO(philippfromme): remove `source` parameter
  630. *
  631. * @param {Element[]} elements
  632. * @param {Shape} target
  633. * @param {Element} source
  634. * @param {Point} [position]
  635. *
  636. * @return {boolean | 'attach'}
  637. */
  638. function canAttach(elements, target, source, position) {
  639. if (!Array.isArray(elements)) {
  640. elements = [ elements ];
  641. }
  642. // only (re-)attach one element at a time
  643. if (elements.length !== 1) {
  644. return false;
  645. }
  646. var element = elements[0];
  647. // do not attach labels
  648. if (isLabel(element)) {
  649. return false;
  650. }
  651. // only handle boundary events
  652. if (!isBoundaryCandidate(element)) {
  653. return false;
  654. }
  655. // disallow drop on event sub processes
  656. if (isEventSubProcess(target)) {
  657. return false;
  658. }
  659. // only allow drop on non compensation activities
  660. if (!is(target, 'bpmn:Activity') || isForCompensation(target)) {
  661. return false;
  662. }
  663. // only attach to subprocess border
  664. if (position && !isBoundaryAttachment(position, target)) {
  665. return false;
  666. }
  667. // do not attach on receive tasks after event based gateways
  668. if (isReceiveTaskAfterEventBasedGateway(target)) {
  669. return false;
  670. }
  671. return 'attach';
  672. }
  673. /**
  674. * Check whether the given elements can be replaced. Return all elements which
  675. * can be replaced.
  676. *
  677. * @example
  678. *
  679. * ```javascript
  680. * [{
  681. * id: 'IntermediateEvent_1',
  682. * type: 'bpmn:StartEvent'
  683. * },
  684. * {
  685. * id: 'Task_1',
  686. * type: 'bpmn:ServiceTask'
  687. * }]
  688. * ```
  689. *
  690. * @param {Element[]} elements
  691. * @param {Shape} [target]
  692. * @param {Point} [position]
  693. *
  694. * @return {CanReplaceResult}
  695. */
  696. function canReplace(elements, target, position) {
  697. if (!target) {
  698. return false;
  699. }
  700. var canExecute = {
  701. replacements: []
  702. };
  703. forEach(elements, function(element) {
  704. if (!isEventSubProcess(target)) {
  705. if (is(element, 'bpmn:StartEvent') &&
  706. element.type !== 'label' &&
  707. canDrop(element, target)) {
  708. // replace a non-interrupting start event by a blank interrupting start event
  709. // when the target is not an event sub process
  710. if (!isInterrupting(element)) {
  711. canExecute.replacements.push({
  712. oldElementId: element.id,
  713. newElementType: 'bpmn:StartEvent'
  714. });
  715. }
  716. // replace an error/escalation/compensate start event by a blank interrupting start event
  717. // when the target is not an event sub process
  718. if (hasErrorEventDefinition(element) ||
  719. hasEscalationEventDefinition(element) ||
  720. hasCompensateEventDefinition(element)) {
  721. canExecute.replacements.push({
  722. oldElementId: element.id,
  723. newElementType: 'bpmn:StartEvent'
  724. });
  725. }
  726. // replace a typed start event by a blank interrupting start event
  727. // when the target is a sub process but not an event sub process
  728. if (hasOneOfEventDefinitions(element,
  729. [
  730. 'bpmn:MessageEventDefinition',
  731. 'bpmn:TimerEventDefinition',
  732. 'bpmn:SignalEventDefinition',
  733. 'bpmn:ConditionalEventDefinition'
  734. ]) &&
  735. is(target, 'bpmn:SubProcess')) {
  736. canExecute.replacements.push({
  737. oldElementId: element.id,
  738. newElementType: 'bpmn:StartEvent'
  739. });
  740. }
  741. }
  742. }
  743. if (!is(target, 'bpmn:Transaction')) {
  744. if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') &&
  745. element.type !== 'label') {
  746. if (is(element, 'bpmn:EndEvent') && canDrop(element, target)) {
  747. canExecute.replacements.push({
  748. oldElementId: element.id,
  749. newElementType: 'bpmn:EndEvent'
  750. });
  751. }
  752. if (is(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
  753. canExecute.replacements.push({
  754. oldElementId: element.id,
  755. newElementType: 'bpmn:BoundaryEvent'
  756. });
  757. }
  758. }
  759. }
  760. });
  761. return canExecute.replacements.length ? canExecute : false;
  762. }
  763. /**
  764. * @param {Element[]} elements
  765. * @param {Shape} target
  766. *
  767. * @return {boolean}
  768. */
  769. function canMove(elements, target) {
  770. // do not move selection containing lanes
  771. if (some(elements, isLane)) {
  772. return false;
  773. }
  774. // allow default move check to start move operation
  775. if (!target) {
  776. return true;
  777. }
  778. return elements.every(function(element) {
  779. return canDrop(element, target);
  780. });
  781. }
  782. /**
  783. * @param {Shape} shape
  784. * @param {Shape} target
  785. * @param {Element} source
  786. * @param {Point} position
  787. *
  788. * @return {boolean}
  789. */
  790. function canCreate(shape, target, source, position) {
  791. if (!target) {
  792. return false;
  793. }
  794. if (isLabel(shape) || isGroup(shape)) {
  795. return true;
  796. }
  797. if (isSame(source, target)) {
  798. return false;
  799. }
  800. // ensure we do not drop the element
  801. // into source
  802. if (source && isParent(source, target)) {
  803. return false;
  804. }
  805. return canDrop(shape, target, position) || canInsert(shape, target, position);
  806. }
  807. /**
  808. * @param {Shape} shape
  809. * @param {Rect} newBounds
  810. *
  811. * @return {boolean}
  812. */
  813. function canResize(shape, newBounds) {
  814. if (is(shape, 'bpmn:SubProcess')) {
  815. return (
  816. isExpanded(shape) && (
  817. !newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
  818. )
  819. );
  820. }
  821. if (is(shape, 'bpmn:Lane')) {
  822. return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60);
  823. }
  824. if (is(shape, 'bpmn:Participant')) {
  825. return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50);
  826. }
  827. if (isTextAnnotation(shape)) {
  828. return true;
  829. }
  830. if (isGroup(shape)) {
  831. return true;
  832. }
  833. return false;
  834. }
  835. /**
  836. * Check whether one of of the elements to be connected is a text annotation.
  837. *
  838. * @param {Element} source
  839. * @param {Element} target
  840. *
  841. * @return {boolean}
  842. */
  843. function isOneTextAnnotation(source, target) {
  844. var sourceTextAnnotation = isTextAnnotation(source),
  845. targetTextAnnotation = isTextAnnotation(target);
  846. return (
  847. (sourceTextAnnotation || targetTextAnnotation) &&
  848. (sourceTextAnnotation !== targetTextAnnotation)
  849. );
  850. }
  851. /**
  852. * @param {Element} source
  853. * @param {Element} target
  854. *
  855. * @return {CanConnectResult}
  856. */
  857. function canConnectAssociation(source, target) {
  858. // don't connect parent <-> child
  859. if (isParent(target, source) || isParent(source, target)) {
  860. return false;
  861. }
  862. // allow connection of associations between <!TextAnnotation> and <TextAnnotation>
  863. if (isOneTextAnnotation(source, target)) {
  864. return true;
  865. }
  866. // can connect associations where we can connect
  867. // data associations, too (!)
  868. return !!canConnectDataAssociation(source, target);
  869. }
  870. /**
  871. * @param {Element} source
  872. * @param {Element} target
  873. *
  874. * @return {boolean}
  875. */
  876. function canConnectCompensationAssociation(source, target) {
  877. return (
  878. isSameScope(source, target) &&
  879. isCompensationBoundary(source) &&
  880. is(target, 'bpmn:Activity') &&
  881. !isHostOfElement(target, source) &&
  882. !isEventSubProcess(target)
  883. );
  884. }
  885. /**
  886. * @param {Element} source
  887. * @param {Element} target
  888. *
  889. * @return {boolean}
  890. */
  891. function canConnectMessageFlow(source, target) {
  892. // during connect user might move mouse out of canvas
  893. // https://github.com/bpmn-io/bpmn-js/issues/1033
  894. if (getRootElement(source) && !getRootElement(target)) {
  895. return false;
  896. }
  897. return (
  898. isMessageFlowSource(source) &&
  899. isMessageFlowTarget(target) &&
  900. !isSameOrganization(source, target)
  901. );
  902. }
  903. /**
  904. * @param {Element} source
  905. * @param {Element} target
  906. *
  907. * @return {boolean}
  908. */
  909. function canConnectSequenceFlow(source, target) {
  910. return isSequenceFlowSource(source) &&
  911. isSequenceFlowTarget(target) &&
  912. isSameScope(source, target) &&
  913. !(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
  914. }
  915. /**
  916. * @param {Element} source
  917. * @param {Element} target
  918. *
  919. * @return {CanConnectResult}
  920. */
  921. function canConnectDataAssociation(source, target) {
  922. if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
  923. isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
  924. return { type: 'bpmn:DataInputAssociation' };
  925. }
  926. if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
  927. isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
  928. return { type: 'bpmn:DataOutputAssociation' };
  929. }
  930. return false;
  931. }
  932. /**
  933. * @param {Shape} shape
  934. * @param {Connection} connection
  935. * @param {Point} position
  936. *
  937. * @return {boolean}
  938. */
  939. function canInsert(shape, connection, position) {
  940. if (!connection) {
  941. return false;
  942. }
  943. if (Array.isArray(shape)) {
  944. if (shape.length !== 1) {
  945. return false;
  946. }
  947. shape = shape[ 0 ];
  948. }
  949. if (connection.source === shape ||
  950. connection.target === shape) {
  951. return false;
  952. }
  953. // return true if shape can be inserted into connection parent
  954. return (
  955. isAny(connection, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
  956. !isLabel(connection) &&
  957. is(shape, 'bpmn:FlowNode') &&
  958. !is(shape, 'bpmn:BoundaryEvent') &&
  959. canDrop(shape, connection.parent, position));
  960. }
  961. /**
  962. * @param {Element[]} elements
  963. * @param {Element} element
  964. *
  965. * @return {boolean}
  966. */
  967. function includes(elements, element) {
  968. return (elements && element) && elements.indexOf(element) !== -1;
  969. }
  970. /**
  971. * @param {Element[]} elements
  972. * @param {Element} element
  973. *
  974. * @return {boolean}
  975. */
  976. function canCopy(elements, element) {
  977. if (isLabel(element)) {
  978. return true;
  979. }
  980. if (is(element, 'bpmn:Lane') && !includes(elements, element.parent)) {
  981. return false;
  982. }
  983. return true;
  984. }
  985. /**
  986. * @param {Element} element
  987. *
  988. * @return {Element|null}
  989. */
  990. function getRootElement(element) {
  991. return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration');
  992. }
  993. function isHostOfElement(potentialHost, element) {
  994. return potentialHost.attachers.includes(element);
  995. }