@libs-ui/services-diagram-draw 0.2.356-37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +279 -0
  2. package/canvas.service.d.ts +42 -0
  3. package/diagram-draw.service.d.ts +91 -0
  4. package/direction.service.d.ts +43 -0
  5. package/esm2022/canvas.service.mjs +628 -0
  6. package/esm2022/diagram-draw.service.mjs +711 -0
  7. package/esm2022/direction.service.mjs +255 -0
  8. package/esm2022/index.mjs +6 -0
  9. package/esm2022/interfaces/canvas.interface.mjs +2 -0
  10. package/esm2022/interfaces/coordinates.interface.mjs +2 -0
  11. package/esm2022/interfaces/diagram.interface.mjs +2 -0
  12. package/esm2022/libs-ui-services-diagram-draw.mjs +5 -0
  13. package/esm2022/utils/calculator-branch.util.mjs +187 -0
  14. package/esm2022/utils/calculator-element.util.mjs +335 -0
  15. package/esm2022/utils/canvas.util.mjs +176 -0
  16. package/esm2022/utils/diagram.util.mjs +54 -0
  17. package/esm2022/utils/direction.util.mjs +103 -0
  18. package/esm2022/utils/horizontal/calculator-branch.util.mjs +243 -0
  19. package/esm2022/utils/horizontal/calculator-coordinates.util.mjs +315 -0
  20. package/fesm2022/libs-ui-services-diagram-draw.mjs +2982 -0
  21. package/fesm2022/libs-ui-services-diagram-draw.mjs.map +1 -0
  22. package/index.d.ts +5 -0
  23. package/interfaces/canvas.interface.d.ts +21 -0
  24. package/interfaces/coordinates.interface.d.ts +40 -0
  25. package/interfaces/diagram.interface.d.ts +109 -0
  26. package/package.json +27 -0
  27. package/utils/calculator-branch.util.d.ts +16 -0
  28. package/utils/calculator-element.util.d.ts +24 -0
  29. package/utils/canvas.util.d.ts +21 -0
  30. package/utils/diagram.util.d.ts +6 -0
  31. package/utils/direction.util.d.ts +6 -0
  32. package/utils/horizontal/calculator-branch.util.d.ts +67 -0
  33. package/utils/horizontal/calculator-coordinates.util.d.ts +42 -0
@@ -0,0 +1,2982 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, Injectable, inject } from '@angular/core';
3
+ import { setStylesElement, cloneDeep, uuid } from '@libs-ui/utils';
4
+ import { TranslateService } from '@ngx-translate/core';
5
+ import { Subject } from 'rxjs';
6
+
7
+ class MoLibDiagramCalculatorBranchUtil {
8
+ static drawStraightLine(left, lineFormElementToBranching, top, branch, maxHeightBranch, config) {
9
+ const startDraw = top + lineFormElementToBranching;
10
+ let Ly = startDraw;
11
+ Ly += canvasConfigReadonly().ELEMENT_HEIGHT_CURVE; // chiều dài đến element đầu tiên
12
+ Ly += getDistanceFromBranchToFirstBlock(branch.nodeOtherConfig); // chiều dài đến element đầu tiên
13
+ if (!branch.elements || !branch.elements.length) {
14
+ Ly += maxHeightBranch;
15
+ Ly += 20; // thêm đoạn nối từ điểm kết thúc nhánh đến hết
16
+ }
17
+ const coord1 = {
18
+ mx: left,
19
+ my: top,
20
+ ly: Ly,
21
+ lx: left,
22
+ };
23
+ branch.attributeSvgD = `M ${coord1.mx} ${coord1.my} L ${coord1.lx} ${coord1.ly}`;
24
+ if (branch.nodeOtherConfig) {
25
+ const endYToWait = startDraw + canvasConfigReadonly().ELEMENT_HEIGHT_CURVE + canvasConfigReadonly().DISTANCE_TO_WAIT;
26
+ branch.attributeSvgD = `M ${coord1.mx} ${coord1.my} L ${coord1.mx} ${endYToWait}`;
27
+ const waitHeight = getHeightWaitConfig(branch.nodeOtherConfig);
28
+ const topStartLine2 = endYToWait + waitHeight;
29
+ delete branch.nodeOtherConfig.attributeSvgD;
30
+ if (branch.elements && branch.elements.length) {
31
+ branch.nodeOtherConfig.attributeSvgD = `M ${coord1.mx} ${topStartLine2} L ${coord1.mx} ${coord1.ly}`;
32
+ }
33
+ branch.nodeOtherConfig.specific_y = endYToWait;
34
+ branch.nodeOtherConfig.specific_x = coord1.mx;
35
+ }
36
+ branch.specific_start_branch_y = top + lineFormElementToBranching + canvasConfigReadonly().ELEMENT_HEIGHT_CURVE;
37
+ branch.specific_start_branch_x = left;
38
+ if (!branch.elements || !branch.elements.length) {
39
+ return;
40
+ }
41
+ const heightToElement = canvasConfigReadonly().ELEMENT_HEIGHT_CURVE + getDistanceFromBranchToFirstBlock(branch.nodeOtherConfig); // chiều dài đến element đầu tiên;
42
+ branch.elements.forEach((elementBranch) => {
43
+ elementBranch.specific_y = top + lineFormElementToBranching + heightToElement;
44
+ elementBranch.specific_x = left - (elementBranch.specific_width ?? 0) / 2;
45
+ });
46
+ MoLibDiagramCalculatorCoordinatesUtil.setXYElements(branch.elements, config);
47
+ }
48
+ static leftLine(left, lineFormElementToBranching, top, lineBetweenBranch, branch, maxHeightBranch, element, config) {
49
+ const { coordQL2, isCheckAllElementConnectToExit } = this.drawAboveUnderLine(left, lineFormElementToBranching, top, lineBetweenBranch, branch, maxHeightBranch, 'left', config);
50
+ branch.specific_start_branch_x = coordQL2.x;
51
+ branch.specific_start_branch_y = coordQL2.y;
52
+ if (!branch.elements || !branch.elements.length) {
53
+ return;
54
+ }
55
+ this.drawElementInBranchLine(branch, coordQL2, config);
56
+ if (isCheckAllElementConnectToExit) {
57
+ return;
58
+ }
59
+ }
60
+ static rightLine(left, lineFormElementToBranching, top, lineBetweenBranch, branch, maxHeightBranch, element, config) {
61
+ const { coordQL1, coordQL2, isCheckAllElementConnectToExit } = this.drawAboveUnderLine(left, lineFormElementToBranching, top, lineBetweenBranch, branch, maxHeightBranch, 'right', config);
62
+ branch.specific_start_branch_x = coordQL2.x;
63
+ branch.specific_start_branch_y = coordQL2.y;
64
+ const maxY = (coordQL1.y ?? 0) + 200;
65
+ const configSvg = config.widthHeightSvgCanvas;
66
+ configSvg.heightSvg = maxY > configSvg.heightSvg ? maxY : configSvg.heightSvg;
67
+ if (!branch.elements || !branch.elements.length) {
68
+ return;
69
+ }
70
+ this.drawElementInBranchLine(branch, coordQL2, config);
71
+ if (isCheckAllElementConnectToExit) {
72
+ return;
73
+ }
74
+ }
75
+ static drawElementInBranchLine(branch, coordQL2, config) {
76
+ const topElementInBranch = coordQL2.ly; // bắt đầu của 1 emelent trong nhánh sẽ là điểm cuối cùng của nhánh trỏ tới
77
+ const elementFirst = branch.elements[0];
78
+ elementFirst.specific_y = topElementInBranch;
79
+ elementFirst.specific_x = (coordQL2.lx ?? 0) - (elementFirst.specific_width ?? 0) / 2;
80
+ MoLibDiagramCalculatorCoordinatesUtil.setXYElements(branch.elements, config);
81
+ }
82
+ static drawMissingLineFromLastElement(branch, coordQ3) {
83
+ const { mx, my } = coordQ3;
84
+ if (!my) {
85
+ return;
86
+ }
87
+ const lastElementInBranch = branch.elements[branch.elements.length - 1];
88
+ if (!lastElementInBranch.branches?.length) {
89
+ branch.attributeSvgD = `${branch.attributeSvgD} M ${mx} ${my} L ${mx} ${lastElementInBranch.position_end ?? 0}`;
90
+ return;
91
+ }
92
+ const ly = (lastElementInBranch.position_end_branch ?? 0) - canvasConfigReadonly().ELEMENT_HEIGHT_CURVE;
93
+ if (ly === my) {
94
+ return;
95
+ }
96
+ branch.attributeSvgD = `${branch.attributeSvgD} M ${mx} ${my} L ${mx} ${ly}`;
97
+ }
98
+ static drawAboveUnderLine(left, lineFormElementToBranching, top, lineBetweenBranch, branch, maxHeightBranch, direction, config) {
99
+ const M = `M ${left} ${top}`; // đặt bút vẽ từ điểm x, y
100
+ const Ly = top + lineFormElementToBranching - canvasConfigReadonly().ELEMENT_HEIGHT_CURVE;
101
+ const L = `L ${left} ${Ly}`; // Kéo dài 1 đường thẳng tới X, Y,
102
+ const straightLineBetweenBranch = lineBetweenBranch - canvasConfigReadonly().ELEMENT_HEIGHT_CURVE * 2;
103
+ const coordQL1 = {
104
+ // điểm cong số 1 của nhánh và đường thẳng kéo trên dưới
105
+ mx: left,
106
+ my: Ly,
107
+ x1: left,
108
+ y1: Ly + canvasConfigReadonly().ELEMENT_HEIGHT_CURVE,
109
+ x: this.mathOperatorsCalculation(direction, left, canvasConfigReadonly().ELEMENT_HEIGHT_CURVE, true),
110
+ y: Ly + canvasConfigReadonly().ELEMENT_HEIGHT_CURVE,
111
+ };
112
+ coordQL1.ly = coordQL1.y;
113
+ // đoạn thẳng kéo dài lineBetweenBranch phải trừ đi hai đầu cong
114
+ coordQL1.lx = this.mathOperatorsCalculation(direction, coordQL1.x, straightLineBetweenBranch, true);
115
+ const QL1 = `M ${coordQL1.mx},${coordQL1.my} Q ${coordQL1.x1},${coordQL1.y1} ${coordQL1.x},${coordQL1.y} L ${coordQL1.lx} ${coordQL1.ly}`;
116
+ const coordQL2 = {
117
+ // điểm cong số 2 của nhánh và đường thẳng đứng
118
+ y1: coordQL1.ly,
119
+ x1: this.mathOperatorsCalculation(direction, coordQL1.lx, canvasConfigReadonly().ELEMENT_HEIGHT_CURVE, true),
120
+ y: (coordQL1.ly ?? 0) + canvasConfigReadonly().ELEMENT_HEIGHT_CURVE,
121
+ x: this.mathOperatorsCalculation(direction, coordQL1.lx, canvasConfigReadonly().ELEMENT_HEIGHT_CURVE, true),
122
+ };
123
+ coordQL2.ly = (coordQL2.y ?? 0) + getDistanceFromBranchToFirstBlock(branch.nodeOtherConfig);
124
+ coordQL2.lx = coordQL2.x;
125
+ if (!branch.elements || !branch.elements.length) {
126
+ coordQL2.ly = maxHeightBranch + (coordQL2.ly ?? 0); // tổng chiều cao của nhánh + điểm xuất phát
127
+ }
128
+ let QL2 = `Q ${coordQL2.x1},${coordQL2.y1} ${coordQL2.x},${coordQL2.y} L ${coordQL2.lx} ${coordQL2.ly}`;
129
+ if (branch.nodeOtherConfig) {
130
+ const endYToWait = (coordQL2.y ?? 0) + canvasConfigReadonly().DISTANCE_TO_WAIT; // xử lý các line vào first element
131
+ const waitHeight = getHeightWaitConfig(branch.nodeOtherConfig);
132
+ const topStartLine2 = endYToWait + waitHeight;
133
+ delete branch.nodeOtherConfig.attributeSvgD;
134
+ QL2 = `Q ${coordQL2.x1},${coordQL2.y1} ${coordQL2.x},${coordQL2.y} M ${coordQL2.x},${coordQL2.y} L ${coordQL2.x},${endYToWait}`;
135
+ if (branch.elements && branch.elements.length) {
136
+ branch.nodeOtherConfig.attributeSvgD = `M ${coordQL2.x} ${topStartLine2} L ${coordQL2.x} ${coordQL2.ly}`;
137
+ }
138
+ branch.nodeOtherConfig.specific_y = endYToWait;
139
+ branch.nodeOtherConfig.specific_x = coordQL2.x;
140
+ }
141
+ branch.positionMaxLeft = coordQL2.lx;
142
+ branch.attributeSvgD = `${M} ${L} ${QL1} ${QL2}`;
143
+ const isCheckAllElementConnectToExit = this.checkChildrenConnectToExit(branch.elements);
144
+ checkMaxLeft(config, branch.nodeOtherConfig?.specific_x, branch.positionMaxLeft);
145
+ return { coordQL1, coordQL2, isCheckAllElementConnectToExit };
146
+ }
147
+ static checkChildrenConnectToExit(elements) {
148
+ let toExit = false;
149
+ if (elements.length && (elements[elements.length - 1].subCodeOfElement || elements[elements.length - 1].code) === canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
150
+ toExit = true;
151
+ return toExit;
152
+ }
153
+ elements.forEach((element) => {
154
+ if (!element.branches || !element.branches.length) {
155
+ return;
156
+ }
157
+ let branchConnectToExit = 0;
158
+ element.branches.forEach((branch) => {
159
+ if (this.checkChildrenConnectToExit(branch.elements)) {
160
+ branchConnectToExit++;
161
+ }
162
+ });
163
+ if (branchConnectToExit === element.branches.length) {
164
+ toExit = true;
165
+ }
166
+ });
167
+ return toExit;
168
+ }
169
+ /* above, left a + b; opposite = true a - b;
170
+ under, right a - b; opposite = true a + b
171
+ */
172
+ static mathOperatorsCalculation(direction, a, b, opposite) {
173
+ switch (direction) {
174
+ case 'left':
175
+ case 'above':
176
+ if (opposite) {
177
+ return (a ?? 0) - (b ?? 0);
178
+ }
179
+ return (a ?? 0) + (b ?? 0);
180
+ case 'right':
181
+ case 'under':
182
+ if (opposite) {
183
+ return (a ?? 0) + (b ?? 0);
184
+ }
185
+ return (a ?? 0) - (b ?? 0);
186
+ default:
187
+ return (a ?? 0) - (b ?? 0);
188
+ }
189
+ }
190
+ }
191
+
192
+ const buildFlatElement = (elements, flatElementsContainer) => {
193
+ elements.forEach((element) => {
194
+ flatElementsContainer.push(element);
195
+ if (!element.branches || !element.branches.length) {
196
+ return;
197
+ }
198
+ flatElementHasBranch(element, flatElementsContainer);
199
+ });
200
+ };
201
+ const flatElementHasBranch = (element, flatElementsContainer) => {
202
+ if (!element.branches || !element.branches.length) {
203
+ return;
204
+ }
205
+ element.branches.forEach((branch) => {
206
+ if (!branch.elements || !branch.elements.length) {
207
+ return;
208
+ }
209
+ buildFlatElement(branch.elements, flatElementsContainer);
210
+ });
211
+ };
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ const setAttributeSvgAndAppend = (svgElement, elementSvg, svgAttributePathD, color) => {
214
+ elementSvg?.setAttribute('d', svgAttributePathD);
215
+ elementSvg?.setAttribute('stroke', `#${color ?? canvasConfigReadonly().ELEMENT_SVG_STROKE_COLOR}`);
216
+ elementSvg?.setAttribute('fill', 'none');
217
+ elementSvg?.setAttribute('stroke-width', canvasConfigReadonly().ELEMENT_SVG_STROKE_WIDTH);
218
+ svgElement?.nativeElement.appendChild(elementSvg);
219
+ };
220
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
221
+ const canvasRemoveChildInSvg = (svgElement) => {
222
+ if (!svgElement) {
223
+ return;
224
+ }
225
+ const children = Array.from(svgElement?.nativeElement.children);
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
+ children.forEach((child) => {
228
+ if (child.tagName !== 'path') {
229
+ return;
230
+ }
231
+ child.remove();
232
+ });
233
+ };
234
+ const applyElementStyle = (el, element) => {
235
+ el.style.position = 'absolute';
236
+ el.style.top = '0';
237
+ el.style.left = '0';
238
+ el.style.transform = `translate(${element.translateX}px, ${element.translateY}px)`;
239
+ el.style.width = `${element.specific_width}px`;
240
+ el.style.height = `${element.specific_height}px`;
241
+ el.style.zIndex = '1';
242
+ el.style.willChange = 'transform';
243
+ };
244
+
245
+ class MoLibDiagramCalculatorCoordinatesUtil {
246
+ static setXYElements(elements, config, positionX) {
247
+ if (!elements || !elements.length) {
248
+ return;
249
+ }
250
+ elements.forEach((currentElement, index) => {
251
+ currentElement.specific_x = (currentElement.specific_x ?? 0) + (positionX ?? 0);
252
+ const preElement = elements[index - 1];
253
+ if (preElement) {
254
+ currentElement.specific_x = (preElement.specific_x ?? 0) + (preElement.specific_width ?? 0) / 2 - (currentElement.specific_width ?? 0) / 2;
255
+ currentElement.specific_y = (preElement.specific_y ?? 0) + (preElement.specific_height ?? 0) + checkElementGetHeight(preElement.nodeOtherConfig);
256
+ if (preElement.position_end_branch) {
257
+ currentElement.specific_y = preElement.position_end_branch;
258
+ }
259
+ }
260
+ const top = currentElement.specific_y ?? 0;
261
+ const left = (currentElement.specific_x ?? 0) + (currentElement.specific_width ?? 0) / 2;
262
+ let lineBetweenElements = (currentElement.specific_height ?? 0) + checkElementGetHeight(currentElement.nodeOtherConfig);
263
+ if (currentElement.branches && currentElement.branches.length) {
264
+ lineBetweenElements = (currentElement.specific_height ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH;
265
+ }
266
+ const maxY = (currentElement.specific_y ?? 0) + (currentElement.specific_height ?? 0) + 200;
267
+ const maxXLeft = (currentElement.specific_x ?? 0) + (currentElement.specific_width ?? 0) + 100;
268
+ const configSvg = config.widthHeightSvgCanvas;
269
+ configSvg.heightSvg = maxY > configSvg.heightSvg ? maxY : configSvg.heightSvg;
270
+ configSvg.widthSvg = maxXLeft > configSvg.widthSvg ? maxXLeft : configSvg.widthSvg;
271
+ checkMaxLeft(config, currentElement.specific_x, currentElement.branches?.[0].positionMaxLeft);
272
+ if (!currentElement.branches || !currentElement.branches.length) {
273
+ // các element không có nhánh mới vẽ đường thẳng
274
+ const drawFormToHeightExit = (currentElement.subCodeOfElement || currentElement.code) === canvasConfigReadonly().TYPE_ELEMENT_EXIT ? (currentElement.specific_height ?? 0) / 2 : lineBetweenElements;
275
+ currentElement.attributeSvgD = `M ${left} ${top} l 0 ${drawFormToHeightExit}`;
276
+ currentElement.position_end = top + lineBetweenElements;
277
+ if (currentElement.nodeOtherConfig) {
278
+ const waitHeight = getHeightWaitConfig(currentElement.nodeOtherConfig);
279
+ const distanceElementToWait = (currentElement.specific_height ?? 0) + canvasConfigReadonly().DISTANCE_TO_WAIT;
280
+ const topStartLine2 = top + distanceElementToWait + waitHeight;
281
+ currentElement.attributeSvgD = `M ${left} ${top} l 0 ${distanceElementToWait}`;
282
+ currentElement.position_end = topStartLine2 + lineBetweenElements;
283
+ currentElement.nodeOtherConfig.specific_y = top + distanceElementToWait;
284
+ currentElement.nodeOtherConfig.specific_x = left;
285
+ delete currentElement.nodeOtherConfig.attributeSvgD;
286
+ if (currentElement.subCodeOfElement !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
287
+ currentElement.nodeOtherConfig.attributeSvgD = `M ${left} ${topStartLine2} l 0 ${canvasConfigReadonly().WAIT_TO_ELEMENT}`;
288
+ }
289
+ }
290
+ return;
291
+ }
292
+ // start các element có nhánh
293
+ const half = Math.ceil(currentElement.branches.length / 2);
294
+ const aboveHalf = currentElement.branches.slice(0, half);
295
+ const underHalf = currentElement.branches.slice(half);
296
+ let brachMiddle;
297
+ if (aboveHalf.length !== underHalf.length) {
298
+ brachMiddle = aboveHalf.pop();
299
+ }
300
+ // TÍNH CHIỀU Cao TỐI ĐA CỦA NHÁNH
301
+ const maxHeightBranch = {
302
+ height: 0,
303
+ };
304
+ MoLibDiagramCalculatorCoordinatesUtil.calculatorMaxBranchHeight(currentElement, maxHeightBranch);
305
+ currentElement.maxHeightBranch = maxHeightBranch.height;
306
+ const maxWidthBrachMiddle = { width: 0, aboveWidth: 0, underWidth: 0 };
307
+ const lineFormElementToBranching = (currentElement.specific_height ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH;
308
+ if (brachMiddle) {
309
+ this.calculatorWidthBranch(brachMiddle.elements, maxWidthBrachMiddle);
310
+ brachMiddle.widthBranch = {
311
+ width: maxWidthBrachMiddle.width,
312
+ above: maxWidthBrachMiddle.aboveWidth,
313
+ under: maxWidthBrachMiddle.underWidth,
314
+ };
315
+ MoLibDiagramCalculatorBranchUtil.drawStraightLine(left, lineFormElementToBranching, top, brachMiddle, maxHeightBranch.height, config);
316
+ }
317
+ let hasNextSameParentElementNext = false;
318
+ const aboveHalfReverse = aboveHalf.reverse();
319
+ aboveHalfReverse.forEach((branch, indexBranch) => {
320
+ if (!hasNextSameParentElementNext) {
321
+ const flatElementOnBranch = [];
322
+ buildFlatElement(branch.elements, flatElementOnBranch);
323
+ hasNextSameParentElementNext = !flatElementOnBranch || !flatElementOnBranch.length ? true : flatElementOnBranch.find((item) => item.next_id === currentElement.next_id) ? true : false;
324
+ }
325
+ branch.onTheSide = 'above';
326
+ const maxWidthBranch = { width: 0, aboveWidth: 0, underWidth: 0 }; // tính chiều ngang để cách nhánh trên or dưới
327
+ this.calculatorWidthBranch(branch.elements, maxWidthBranch);
328
+ branch.widthBranch = {
329
+ width: maxWidthBranch.width,
330
+ above: maxWidthBranch.aboveWidth,
331
+ under: maxWidthBranch.underWidth,
332
+ };
333
+ const branchPre = aboveHalfReverse[indexBranch - 1];
334
+ let lineBetweenBranch = maxWidthBranch.underWidth + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
335
+ if (branchPre) {
336
+ lineBetweenBranch += (branchPre.widthBranch?.width ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT;
337
+ }
338
+ if (brachMiddle) {
339
+ lineBetweenBranch += (brachMiddle.widthBranch?.above ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
340
+ }
341
+ // nhánh trái
342
+ MoLibDiagramCalculatorBranchUtil.leftLine(left, lineFormElementToBranching, top, lineBetweenBranch, branch, maxHeightBranch.height, currentElement, config);
343
+ });
344
+ underHalf.forEach((branch, indexBranch) => {
345
+ if (!hasNextSameParentElementNext) {
346
+ const flatElementOnBranch = [];
347
+ buildFlatElement(branch.elements, flatElementOnBranch);
348
+ hasNextSameParentElementNext = !flatElementOnBranch || !flatElementOnBranch.length ? true : flatElementOnBranch.find((item) => item.next_id === currentElement.next_id) ? true : false;
349
+ }
350
+ branch.onTheSide = 'under';
351
+ const maxWidthBranch = { width: 0, aboveWidth: 0, underWidth: 0 }; // tính chiều cao để cách nhánh trên or dưới
352
+ this.calculatorWidthBranch(branch.elements, maxWidthBranch);
353
+ branch.widthBranch = {
354
+ width: maxWidthBranch.width,
355
+ above: maxWidthBranch.aboveWidth,
356
+ under: maxWidthBranch.underWidth,
357
+ };
358
+ const branchPre = underHalf[indexBranch - 1];
359
+ let lineBetweenBranch = maxWidthBranch.aboveWidth + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
360
+ if (branchPre) {
361
+ lineBetweenBranch += (branchPre.widthBranch?.width ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT;
362
+ }
363
+ if (brachMiddle) {
364
+ lineBetweenBranch += (brachMiddle.widthBranch?.under ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
365
+ }
366
+ // nhánh phải
367
+ MoLibDiagramCalculatorBranchUtil.rightLine(left, lineFormElementToBranching, top, lineBetweenBranch, branch, maxHeightBranch.height, currentElement, config);
368
+ });
369
+ });
370
+ // Set translateX và translateY để có thể render nodes
371
+ this.setTranslatePositions(elements);
372
+ }
373
+ /**
374
+ * Set translateX và translateY từ specific_x và specific_y cho tất cả elements (bao gồm branches)
375
+ */
376
+ static setTranslatePositions(elements) {
377
+ elements.forEach((element) => {
378
+ // Set position cho element chính
379
+ element.translateX = element.specific_x;
380
+ element.translateY = element.specific_y;
381
+ // Set position cho nodeOtherConfig nếu có
382
+ if (element.nodeOtherConfig) {
383
+ const offsetX = (element.nodeOtherConfig.specific_width ?? 0) / 2;
384
+ element.nodeOtherConfig.translateX = (element.nodeOtherConfig.specific_x ?? 0) - offsetX;
385
+ element.nodeOtherConfig.translateY = element.nodeOtherConfig.specific_y;
386
+ }
387
+ // Recursively set positions cho elements trong branches
388
+ if (element.branches && element.branches.length > 0) {
389
+ element.branches.forEach((branch) => {
390
+ // Set position cho branch nodeOtherConfig nếu có
391
+ if (branch.nodeOtherConfig) {
392
+ const offsetX = (branch.nodeOtherConfig.specific_width ?? 0) / 2;
393
+ branch.nodeOtherConfig.translateX = (branch.nodeOtherConfig.specific_x ?? 0) - offsetX;
394
+ branch.nodeOtherConfig.translateY = branch.nodeOtherConfig.specific_y;
395
+ }
396
+ // Recursively set positions cho elements trong branch
397
+ if (branch.elements && branch.elements.length > 0) {
398
+ this.setTranslatePositions(branch.elements);
399
+ }
400
+ });
401
+ }
402
+ });
403
+ }
404
+ static calculatorMaxBranchHeight(element, maxHeightBranch) {
405
+ if (!element.branches || !element.branches.length) {
406
+ return;
407
+ }
408
+ element.branches.forEach((branch) => {
409
+ if (!branch.elements || !branch.elements.length) {
410
+ const lineBranchNoHasElement = canvasConfigReadonly().ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT;
411
+ maxHeightBranch.height = lineBranchNoHasElement > maxHeightBranch.height ? lineBranchNoHasElement : maxHeightBranch.height;
412
+ return;
413
+ }
414
+ const total = {
415
+ height: 0,
416
+ };
417
+ this.plusHeightElementInBranch(branch.elements, total);
418
+ maxHeightBranch.height = total.height > maxHeightBranch.height ? total.height : maxHeightBranch.height;
419
+ });
420
+ }
421
+ static plusHeightElementInBranch(elements, total) {
422
+ elements.forEach((element) => {
423
+ const margin = !element.branches || !element.branches.length ? checkElementGetHeight(element.nodeOtherConfig) : canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH;
424
+ total.height += (element.specific_height ?? 0) + margin;
425
+ if (!element.branches || !element.branches.length) {
426
+ return;
427
+ }
428
+ total.height += 64; // từ chiều đường thẳng ngang xuống đến nhánh đầu tiên
429
+ const theMostElementsInBranch = {
430
+ height: 0,
431
+ };
432
+ element.branches.forEach((branch) => {
433
+ const heightInBranch = { height: 0 };
434
+ heightInBranch.height += canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST + 20; // đường cong kết thúc nhánh + 10px đường cong
435
+ if (!branch.elements || !branch.elements.length) {
436
+ heightInBranch.height += canvasConfigReadonly().ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT;
437
+ if (theMostElementsInBranch.height < heightInBranch.height) {
438
+ theMostElementsInBranch.height = heightInBranch.height;
439
+ }
440
+ return;
441
+ }
442
+ this.plusHeightElementInBranch(branch.elements, heightInBranch);
443
+ if (theMostElementsInBranch.height < heightInBranch.height) {
444
+ theMostElementsInBranch.height = heightInBranch.height;
445
+ }
446
+ });
447
+ total.height += theMostElementsInBranch.height;
448
+ });
449
+ }
450
+ static calculatorWidthBranch(elements, maxWidth, log) {
451
+ if (!elements || !elements.length) {
452
+ const width = canvasConfigReadonly().DEFAULT_BRANCH_WHEN_NO_ELEMENT;
453
+ maxWidth.width = width;
454
+ maxWidth.aboveWidth = width / 2;
455
+ maxWidth.underWidth = width / 2;
456
+ return;
457
+ }
458
+ const maxWidthInElements = { width: 0, aboveWidth: 0, underWidth: 0 };
459
+ elements.forEach((element) => {
460
+ if (!element.branches || !element.branches.length) {
461
+ // tính chiều cao của từng element 1, vì chiều cao của từng thằng có thể khác nhau
462
+ if ((element.specific_width ?? 0) < maxWidthInElements.width) {
463
+ return;
464
+ }
465
+ const width = element.specific_width ?? 0;
466
+ maxWidthInElements.width = width;
467
+ maxWidthInElements.aboveWidth = width / 2;
468
+ maxWidthInElements.underWidth = width / 2;
469
+ return;
470
+ }
471
+ const widthElementIncludeBranch = { width: 0, aboveWidth: 0, underWidth: 0 };
472
+ const width = element.specific_width ?? 0;
473
+ if (width > widthElementIncludeBranch.width) {
474
+ widthElementIncludeBranch.width = width;
475
+ widthElementIncludeBranch.aboveWidth = width / 2;
476
+ widthElementIncludeBranch.underWidth = width / 2;
477
+ }
478
+ const half = Math.ceil(element.branches.length / 2);
479
+ const aboveHalf = element.branches.slice(0, half);
480
+ const underHalf = element.branches.slice(half);
481
+ let brachMiddle;
482
+ if (aboveHalf.length !== underHalf.length) {
483
+ brachMiddle = aboveHalf.pop();
484
+ }
485
+ if (brachMiddle) {
486
+ brachMiddle.onTheSide = 'center';
487
+ }
488
+ aboveHalf.forEach((branch) => (branch.onTheSide = 'above'));
489
+ underHalf.forEach((branch) => (branch.onTheSide = 'under'));
490
+ // tính chiều cao của từng nhánh 1, rồi tính tổng => so sánh với chiều cao xem element có nhánh nào cao nhất thì lấy
491
+ const widthBranch = { width: 0, aboveWidth: 0, underWidth: 0 };
492
+ element.branches.forEach((branch) => {
493
+ const width = { width: 0, aboveWidth: 0, underWidth: 0 }; // tính chiều cao của từng nhánh 1
494
+ this.calculatorWidthBranch(branch.elements, width, log);
495
+ switch (branch.onTheSide) {
496
+ case 'above':
497
+ widthBranch.aboveWidth += width.width;
498
+ break;
499
+ case 'under':
500
+ widthBranch.underWidth += width.width;
501
+ break;
502
+ case 'center':
503
+ widthBranch.aboveWidth += width.aboveWidth;
504
+ widthBranch.underWidth += width.underWidth;
505
+ break;
506
+ }
507
+ });
508
+ const marginBetweenBranch = (element.branches.length - 1) * canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT;
509
+ widthBranch.aboveWidth += marginBetweenBranch / 2;
510
+ widthBranch.underWidth += marginBetweenBranch / 2;
511
+ const maxAboveWidth = Math.max(widthElementIncludeBranch.aboveWidth, widthBranch.aboveWidth);
512
+ const maxUnderWidth = Math.max(widthElementIncludeBranch.underWidth, widthBranch.underWidth);
513
+ if (maxWidthInElements.aboveWidth < maxAboveWidth) {
514
+ maxWidthInElements.aboveWidth = maxAboveWidth;
515
+ }
516
+ if (maxWidthInElements.underWidth < maxUnderWidth) {
517
+ maxWidthInElements.underWidth = maxUnderWidth;
518
+ }
519
+ maxWidthInElements.width = maxWidthInElements.aboveWidth + maxWidthInElements.underWidth;
520
+ });
521
+ maxWidth.width += maxWidthInElements.aboveWidth + maxWidthInElements.underWidth;
522
+ maxWidth.aboveWidth += maxWidthInElements.aboveWidth;
523
+ maxWidth.underWidth += maxWidthInElements.underWidth;
524
+ }
525
+ }
526
+ const checkElementGetHeight = (hasWaitConfig) => {
527
+ if (hasWaitConfig) {
528
+ return canvasConfigReadonly().DISTANCE_TO_WAIT + getHeightWaitConfig(hasWaitConfig) + canvasConfigReadonly().WAIT_TO_ELEMENT;
529
+ }
530
+ return canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT;
531
+ };
532
+ const getDistanceFromBranchToFirstBlock = (hasWaitConfig) => {
533
+ if (hasWaitConfig) {
534
+ return canvasConfigReadonly().DISTANCE_TO_WAIT + getHeightWaitConfig(hasWaitConfig) + canvasConfigReadonly().WAIT_TO_ELEMENT;
535
+ }
536
+ return canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST;
537
+ };
538
+ const getHeightWaitConfig = (hasWaitConfig) => {
539
+ if (hasWaitConfig) {
540
+ return (hasWaitConfig.specific_height ?? canvasConfigReadonly().ELEMENT_WAIT_DEFAULT) + canvasConfigReadonly().DISTANCE_WAIT_TO_NEXT_LINE;
541
+ }
542
+ return canvasConfigReadonly().ELEMENT_WAIT_DEFAULT + canvasConfigReadonly().DISTANCE_WAIT_TO_NEXT_LINE;
543
+ };
544
+ const checkMaxLeft = (config, specific_x, positionMaxLeft) => {
545
+ let numberTranslatePosition = config.positionMaxLeft;
546
+ if (numberTranslatePosition === 0 && specific_x && specific_x > 0 && specific_x < 50) {
547
+ numberTranslatePosition = -50;
548
+ }
549
+ if (specific_x && specific_x < numberTranslatePosition) {
550
+ numberTranslatePosition = specific_x;
551
+ }
552
+ if (positionMaxLeft && positionMaxLeft < numberTranslatePosition) {
553
+ numberTranslatePosition = positionMaxLeft;
554
+ }
555
+ if (numberTranslatePosition === config.positionMaxLeft) {
556
+ return;
557
+ }
558
+ config.positionMaxLeft = numberTranslatePosition;
559
+ };
560
+ const checkMaxTop = (config, specific_y, positionMaxTop) => {
561
+ let numberTranslatePosition = config.positionMaxTop;
562
+ if (numberTranslatePosition === 0 && specific_y && specific_y > 0 && specific_y < 100) {
563
+ numberTranslatePosition = specific_y - 120;
564
+ }
565
+ if (specific_y && specific_y < numberTranslatePosition) {
566
+ numberTranslatePosition = specific_y;
567
+ }
568
+ if (positionMaxTop && positionMaxTop < numberTranslatePosition) {
569
+ numberTranslatePosition = positionMaxTop;
570
+ }
571
+ if (numberTranslatePosition === config.positionMaxTop) {
572
+ return;
573
+ }
574
+ config.positionMaxTop = numberTranslatePosition;
575
+ };
576
+
577
+ /**
578
+ * Horizontal branch calculator.
579
+ * Flow direction: LEFT → RIGHT (trái sang phải).
580
+ * Branches spread: above = UP (y giảm), under = DOWN (y tăng).
581
+ *
582
+ * Mapping từ trục dọc sang ngang:
583
+ * vertical "left" branch (x giảm) ↔ horizontal "above" branch (y giảm)
584
+ * vertical "right" branch (x tăng) ↔ horizontal "under" branch (y tăng)
585
+ *
586
+ * Ký hiệu trong ICoordinatesDiagramSvg:
587
+ * (mx,my) = điểm bắt đầu Q curve
588
+ * (x1,y1) = control point
589
+ * (x,y) = end point của Q curve
590
+ * (lx,ly) = điểm cuối của L sau Q
591
+ */
592
+ class MoLibDiagramHorizontalCalculatorBranchUtil {
593
+ static getHorizontalDistanceToFirstBlock(branch) {
594
+ if (branch.nodeOtherConfig) {
595
+ const distToWait = 110; // increase statically since branch label is always assigned later
596
+ return distToWait + getHeightWaitConfig(branch.nodeOtherConfig) + canvasConfigReadonly().WAIT_TO_ELEMENT;
597
+ }
598
+ return canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST;
599
+ }
600
+ // ─────────────────────────────────────────────────────────────────── public ──
601
+ /** Nhánh giữa (center): kéo thẳng sang phải */
602
+ static drawStraightLine(top, // Y tâm của element cha
603
+ lineFormElementToBranching, // width element + margin đến điểm rẽ
604
+ left, // X trái của element cha
605
+ branch, maxWidthBranch, config) {
606
+ branch.onTheSide = 'center';
607
+ const startDraw = left + lineFormElementToBranching;
608
+ let Lx = startDraw;
609
+ Lx += canvasConfigReadonly().ELEMENT_HEIGHT_CURVE;
610
+ Lx += this.getHorizontalDistanceToFirstBlock(branch);
611
+ if (!branch.elements || !branch.elements.length) {
612
+ Lx += maxWidthBranch + 20;
613
+ }
614
+ const coord1 = { mx: left, my: top, lx: Lx, ly: top };
615
+ branch.attributeSvgD = `M ${coord1.mx} ${coord1.my} L ${coord1.lx} ${coord1.ly}`;
616
+ if (branch.nodeOtherConfig) {
617
+ const distToWait = 110;
618
+ const endXToWait = startDraw + canvasConfigReadonly().ELEMENT_HEIGHT_CURVE + distToWait;
619
+ branch.attributeSvgD = `M ${coord1.mx} ${coord1.my} L ${endXToWait} ${coord1.my}`;
620
+ const waitWidth = getHeightWaitConfig(branch.nodeOtherConfig);
621
+ const leftLine2 = endXToWait + waitWidth;
622
+ delete branch.nodeOtherConfig.attributeSvgD;
623
+ if (branch.elements && branch.elements.length) {
624
+ branch.nodeOtherConfig.attributeSvgD = `M ${leftLine2} ${coord1.my} L ${coord1.lx} ${coord1.my}`;
625
+ }
626
+ branch.nodeOtherConfig.specific_x = endXToWait;
627
+ branch.nodeOtherConfig.specific_y = coord1.my;
628
+ // Đảm bảo có kích thước để setTranslatePositions tính offset đúng
629
+ if (!branch.nodeOtherConfig.specific_height) {
630
+ branch.nodeOtherConfig.specific_height = canvasConfigReadonly().ELEMENT_WAIT_DEFAULT;
631
+ }
632
+ if (!branch.nodeOtherConfig.specific_width) {
633
+ branch.nodeOtherConfig.specific_width = canvasConfigReadonly().ELEMENT_WAIT_DEFAULT;
634
+ }
635
+ }
636
+ branch.specific_start_branch_x = left + lineFormElementToBranching + canvasConfigReadonly().ELEMENT_HEIGHT_CURVE;
637
+ branch.specific_start_branch_y = top;
638
+ if (!branch.elements || !branch.elements.length)
639
+ return;
640
+ const widthToElement = canvasConfigReadonly().ELEMENT_HEIGHT_CURVE + this.getHorizontalDistanceToFirstBlock(branch);
641
+ branch.elements.forEach((el) => {
642
+ el.specific_x = left + lineFormElementToBranching + widthToElement;
643
+ el.specific_y = top - (el.specific_height ?? 0) / 2;
644
+ });
645
+ MoLibDiagramHorizontalCalculatorCoordinatesUtil.setXYElements(branch.elements, config);
646
+ }
647
+ /** Nhánh phía trên (above): rẽ lên, rồi bẻ phải đến element */
648
+ static aboveLine(top, lineFormElementToBranching, left, lineBetweenBranch, branch, maxWidthBranch, element, config) {
649
+ branch.onTheSide = 'above';
650
+ const { coordQL2, isCheckAllElementConnectToExit } = this.drawAboveUnderLine(top, lineFormElementToBranching, left, lineBetweenBranch, branch, maxWidthBranch, 'above', config);
651
+ branch.specific_start_branch_x = coordQL2.x;
652
+ branch.specific_start_branch_y = coordQL2.y;
653
+ if (!branch.elements || !branch.elements.length)
654
+ return;
655
+ this.drawElementInBranchLine(branch, coordQL2, config);
656
+ if (isCheckAllElementConnectToExit)
657
+ return;
658
+ }
659
+ /** Nhánh phía dưới (under): rẽ xuống, rồi bẻ phải đến element */
660
+ static underLine(top, lineFormElementToBranching, left, lineBetweenBranch, branch, maxWidthBranch, element, config) {
661
+ branch.onTheSide = 'under';
662
+ const { coordQL1, coordQL2, isCheckAllElementConnectToExit } = this.drawAboveUnderLine(top, lineFormElementToBranching, left, lineBetweenBranch, branch, maxWidthBranch, 'under', config);
663
+ branch.specific_start_branch_x = coordQL2.x;
664
+ branch.specific_start_branch_y = coordQL2.y;
665
+ // Mở rộng widthSvg nếu nhánh xuống vượt
666
+ const maxY = (coordQL1.y ?? 0) + 200;
667
+ const configSvg = config.widthHeightSvgCanvas;
668
+ configSvg.heightSvg = maxY > configSvg.heightSvg ? maxY : configSvg.heightSvg;
669
+ if (!branch.elements || !branch.elements.length)
670
+ return;
671
+ this.drawElementInBranchLine(branch, coordQL2, config);
672
+ if (isCheckAllElementConnectToExit)
673
+ return;
674
+ }
675
+ // ────────────────────────────────────────────────────────────────── private ──
676
+ /** Đặt element đầu tiên trong nhánh dựa theo tọa độ cuối của QL2 */
677
+ static drawElementInBranchLine(branch, coordQL2, config) {
678
+ const elementFirst = branch.elements[0];
679
+ // lx = X bắt đầu của element; ly = Y tâm nhánh → specific_y = tâm - half height
680
+ elementFirst.specific_x = coordQL2.lx;
681
+ elementFirst.specific_y = (coordQL2.ly ?? 0) - (elementFirst.specific_height ?? 0) / 2;
682
+ MoLibDiagramHorizontalCalculatorCoordinatesUtil.setXYElements(branch.elements, config);
683
+ }
684
+ /**
685
+ * Vẽ đường rẽ nhánh kiểu horizontal:
686
+ *
687
+ * M left top → bắt đầu tại tâm Y của element cha
688
+ * L Lx top → kéo NGANG sang phải đến điểm rẽ (Lx = left + lineFormElementToBranching - CURVE)
689
+ *
690
+ * QL1: bẻ từ hướng → sang hướng ↑ (above) hoặc ↓ (under)
691
+ * M (Lx, top)
692
+ * Q (Lx+C, top) → (Lx+C, top±C) control đi thẳng phải, điểm kết bắt đầu đi dọc
693
+ * L Lx+C, top±C±straight kéo thẳng dọc (lên/xuống)
694
+ *
695
+ * QL2: bẻ ngược lại → hướng ngang để tới element
696
+ * Q (Lx+C, top±C±straight±C) → (Lx+2C, top±C±straight±C)
697
+ * control đi tiếp dọc, điểm kết đi ngang (phải)
698
+ * L Lx+2C+distToFirst, same_Y kéo ngang đến vị trí element đầu tiên
699
+ *
700
+ * Sau QL2:
701
+ * coordQL2.lx = X bắt đầu của element đầu tiên
702
+ * coordQL2.ly = Y tâm của nhánh
703
+ */
704
+ static drawAboveUnderLine(top, lineFormElementToBranching, left, lineBetweenBranch, branch, maxWidthBranch, direction, config) {
705
+ const CURVE = canvasConfigReadonly().ELEMENT_HEIGHT_CURVE;
706
+ const M = `M ${left} ${top}`;
707
+ const Lx = left + lineFormElementToBranching - CURVE;
708
+ const L = `L ${Lx} ${top}`;
709
+ const straight = lineBetweenBranch - CURVE * 2; // đoạn thẳng dọc (bỏ 2 cong đầu-cuối)
710
+ // ── QL1: curve từ hướng ngang → hướng dọc (lên/xuống), rồi thẳng dọc ────
711
+ const coordQL1 = {
712
+ mx: Lx,
713
+ my: top,
714
+ x1: Lx + CURVE, // control: đi thẳng phải thêm CURVE
715
+ y1: top, // control: cùng Y
716
+ x: Lx + CURVE, // end X: giữ nguyên (= Lx+CURVE)
717
+ y: this.mathOp(direction, top, CURVE, true),
718
+ // 'above': top - CURVE (bắt đầu đi lên)
719
+ // 'under': top + CURVE (bắt đầu đi xuống)
720
+ };
721
+ // Đoạn L sau QL1: giữ nguyên X, tiếp tục đi dọc
722
+ coordQL1.lx = coordQL1.x; // X không đổi
723
+ coordQL1.ly = this.mathOp(direction, coordQL1.y, straight, true); // tiếp tục lên/xuống
724
+ const QL1 = `M ${coordQL1.mx},${coordQL1.my} Q ${coordQL1.x1},${coordQL1.y1} ${coordQL1.x},${coordQL1.y} L ${coordQL1.lx} ${coordQL1.ly}`;
725
+ // ── QL2: curve từ hướng dọc → hướng ngang (phải), rồi thẳng ngang đến element ──
726
+ const coordQL2 = {
727
+ // control: tiếp tục đi dọc thêm CURVE từ điểm cuối QL1
728
+ x1: coordQL1.lx,
729
+ y1: this.mathOp(direction, coordQL1.ly, CURVE, true),
730
+ // end: rẽ ngang sang phải thêm CURVE, giữ nguyên Y tại y1
731
+ y: this.mathOp(direction, coordQL1.ly, CURVE, true),
732
+ x: (coordQL1.lx ?? 0) + CURVE,
733
+ };
734
+ // Đoạn L sau QL2: đi ngang (phải) đến vị trí element đầu tiên trong nhánh
735
+ const distToFirst = this.getHorizontalDistanceToFirstBlock(branch);
736
+ if (!branch.elements || !branch.elements.length) {
737
+ coordQL2.lx = (coordQL2.x ?? 0) + distToFirst + maxWidthBranch;
738
+ }
739
+ else {
740
+ coordQL2.lx = (coordQL2.x ?? 0) + distToFirst;
741
+ }
742
+ coordQL2.ly = coordQL2.y; // Y không đổi → tâm Y của nhánh
743
+ // Xử lý nodeOtherConfig (wait block) trên nhánh
744
+ let QL2 = `Q ${coordQL2.x1},${coordQL2.y1} ${coordQL2.x},${coordQL2.y} L ${coordQL2.lx} ${coordQL2.ly}`;
745
+ if (branch.nodeOtherConfig) {
746
+ const distToWait = 110;
747
+ const endXToWait = (coordQL2.x ?? 0) + distToWait;
748
+ const waitWidth = getHeightWaitConfig(branch.nodeOtherConfig);
749
+ const leftLine2 = endXToWait + waitWidth;
750
+ delete branch.nodeOtherConfig.attributeSvgD;
751
+ QL2 = `Q ${coordQL2.x1},${coordQL2.y1} ${coordQL2.x},${coordQL2.y} M ${coordQL2.x},${coordQL2.y} L ${endXToWait},${coordQL2.y}`;
752
+ if (branch.elements && branch.elements.length) {
753
+ branch.nodeOtherConfig.attributeSvgD = `M ${leftLine2} ${coordQL2.y} L ${coordQL2.lx} ${coordQL2.ly}`;
754
+ }
755
+ branch.nodeOtherConfig.specific_x = endXToWait;
756
+ // specific_y = tâm Y của nhánh — setTranslatePositions sẽ trừ height/2 khi set translateY
757
+ branch.nodeOtherConfig.specific_y = coordQL2.y ?? 0;
758
+ // Đảm bảo có kích thước để setTranslatePositions tính offset đúng
759
+ if (!branch.nodeOtherConfig.specific_height) {
760
+ branch.nodeOtherConfig.specific_height = canvasConfigReadonly().ELEMENT_WAIT_DEFAULT;
761
+ }
762
+ if (!branch.nodeOtherConfig.specific_width) {
763
+ branch.nodeOtherConfig.specific_width = canvasConfigReadonly().ELEMENT_WAIT_DEFAULT;
764
+ }
765
+ }
766
+ branch.attributeSvgD = `${M} ${L} ${QL1} ${QL2}`;
767
+ const isCheckAllElementConnectToExit = this.checkChildrenConnectToExit(branch.elements);
768
+ branch.positionMaxTop = coordQL2.y;
769
+ checkMaxTop(config, branch.nodeOtherConfig?.specific_y, branch.positionMaxTop);
770
+ return { coordQL1, coordQL2, isCheckAllElementConnectToExit };
771
+ }
772
+ static checkChildrenConnectToExit(elements) {
773
+ let toExit = false;
774
+ if (elements.length &&
775
+ (elements[elements.length - 1].subCodeOfElement || elements[elements.length - 1].code) === canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
776
+ return true;
777
+ }
778
+ elements.forEach((element) => {
779
+ if (!element.branches || !element.branches.length)
780
+ return;
781
+ let branchConnectToExit = 0;
782
+ element.branches.forEach((branch) => {
783
+ if (this.checkChildrenConnectToExit(branch.elements))
784
+ branchConnectToExit++;
785
+ });
786
+ if (branchConnectToExit === element.branches.length)
787
+ toExit = true;
788
+ });
789
+ return toExit;
790
+ }
791
+ /**
792
+ * Quy ước:
793
+ * above + opposite=true → a - b (đi lên, y giảm)
794
+ * above + opposite=false → a + b
795
+ * under + opposite=true → a + b (đi xuống, y tăng)
796
+ * under + opposite=false → a - b
797
+ */
798
+ static mathOp(direction, a, b, opposite) {
799
+ const av = a ?? 0;
800
+ const bv = b ?? 0;
801
+ switch (direction) {
802
+ case 'above':
803
+ return opposite ? av - bv : av + bv;
804
+ case 'under':
805
+ return opposite ? av + bv : av - bv;
806
+ default:
807
+ return av - bv;
808
+ }
809
+ }
810
+ /** Alias để tương thích với code cũ gọi mathOperatorsCalculation */
811
+ static mathOperatorsCalculation(direction, a, b, opposite) {
812
+ return this.mathOp(direction, a, b, opposite);
813
+ }
814
+ }
815
+
816
+ /**
817
+ * Horizontal layout calculator.
818
+ * Mirrors MoLibDiagramCalculatorCoordinatesUtil (vertical) but rotated 90°:
819
+ * - Elements grow LEFT → RIGHT (specific_x advances along main axis)
820
+ * - Branches split UP / DOWN (specific_y for branch offset)
821
+ * - "above" branch = upper side (y decreases)
822
+ * - "under" branch = lower side (y increases)
823
+ *
824
+ * Key axis mapping vs. vertical:
825
+ * vertical: specific_y ↕ specific_x centered
826
+ * horizontal: specific_x → specific_y centered
827
+ */
828
+ class MoLibDiagramHorizontalCalculatorCoordinatesUtil {
829
+ static setXYElements(elements, config, positionY) {
830
+ if (!elements || !elements.length) {
831
+ return;
832
+ }
833
+ elements.forEach((currentElement, index) => {
834
+ currentElement.specific_y = (currentElement.specific_y ?? 0) + (positionY ?? 0);
835
+ const preElement = elements[index - 1];
836
+ if (preElement) {
837
+ // Trong horizontal: y (chiều cao) căn giữa theo preElement, x tiến sang phải
838
+ currentElement.specific_y = (preElement.specific_y ?? 0) + (preElement.specific_height ?? 0) / 2 - (currentElement.specific_height ?? 0) / 2;
839
+ currentElement.specific_x = (preElement.specific_x ?? 0) + (preElement.specific_width ?? 0) + this.checkElementGetMargin(preElement.nodeOtherConfig);
840
+ if (preElement.position_end_branch) {
841
+ currentElement.specific_x = preElement.position_end_branch;
842
+ }
843
+ }
844
+ checkMaxTop(config, currentElement.specific_y, currentElement.branches?.[0]?.positionMaxTop);
845
+ const top = currentElement.specific_y ?? 0;
846
+ const left = (currentElement.specific_x ?? 0) + (currentElement.specific_width ?? 0) / 2; // điểm giữa theo X (điểm vẽ SVG)
847
+ let lineBetweenElements = (currentElement.specific_width ?? 0) + this.checkElementGetMargin(currentElement.nodeOtherConfig);
848
+ if (currentElement.branches && currentElement.branches.length) {
849
+ lineBetweenElements = (currentElement.specific_width ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH;
850
+ }
851
+ const maxY = (currentElement.specific_y ?? 0) + (currentElement.specific_height ?? 0) + 200;
852
+ const maxXRight = (currentElement.specific_x ?? 0) + (currentElement.specific_width ?? 0) + 200;
853
+ const configSvg = config.widthHeightSvgCanvas;
854
+ configSvg.heightSvg = maxY > configSvg.heightSvg ? maxY : configSvg.heightSvg;
855
+ configSvg.widthSvg = maxXRight > configSvg.widthSvg ? maxXRight : configSvg.widthSvg;
856
+ if (!currentElement.branches || !currentElement.branches.length) {
857
+ // Element không có nhánh: vẽ đường thẳng ngang
858
+ const drawFormToWidthExit = (currentElement.subCodeOfElement || currentElement.code) === canvasConfigReadonly().TYPE_ELEMENT_EXIT ? (currentElement.specific_width ?? 0) / 2 : lineBetweenElements;
859
+ currentElement.attributeSvgD = `M ${currentElement.specific_x ?? 0} ${top + (currentElement.specific_height ?? 0) / 2} l ${drawFormToWidthExit} 0`;
860
+ currentElement.position_end = (currentElement.specific_x ?? 0) + lineBetweenElements;
861
+ if (currentElement.nodeOtherConfig) {
862
+ const waitWidth = this.getWidthWaitConfig(currentElement.nodeOtherConfig);
863
+ const distanceElementToWait = (currentElement.specific_width ?? 0) + canvasConfigReadonly().DISTANCE_TO_WAIT;
864
+ const leftStartLine2 = (currentElement.specific_x ?? 0) + distanceElementToWait + waitWidth;
865
+ currentElement.attributeSvgD = `M ${currentElement.specific_x ?? 0} ${top + (currentElement.specific_height ?? 0) / 2} l ${distanceElementToWait} 0`;
866
+ currentElement.position_end = leftStartLine2 + lineBetweenElements;
867
+ currentElement.nodeOtherConfig.specific_x = (currentElement.specific_x ?? 0) + distanceElementToWait;
868
+ // specific_y = tâm Y của đường line — setTranslatePositions sẽ trừ height/2 để căn giữa dọc
869
+ currentElement.nodeOtherConfig.specific_y = top + (currentElement.specific_height ?? 0) / 2;
870
+ // Đảm bảo có kích thước để setTranslatePositions tính offset đúng
871
+ if (!currentElement.nodeOtherConfig.specific_height) {
872
+ currentElement.nodeOtherConfig.specific_height = canvasConfigReadonly().ELEMENT_WAIT_DEFAULT;
873
+ }
874
+ if (!currentElement.nodeOtherConfig.specific_width) {
875
+ currentElement.nodeOtherConfig.specific_width = canvasConfigReadonly().ELEMENT_WAIT_DEFAULT;
876
+ }
877
+ delete currentElement.nodeOtherConfig.attributeSvgD;
878
+ if (currentElement.subCodeOfElement !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
879
+ currentElement.nodeOtherConfig.attributeSvgD = `M ${leftStartLine2} ${top + (currentElement.specific_height ?? 0) / 2} l ${canvasConfigReadonly().WAIT_TO_ELEMENT} 0`;
880
+ }
881
+ }
882
+ return;
883
+ }
884
+ // Elements có nhánh
885
+ const half = Math.ceil(currentElement.branches.length / 2);
886
+ const aboveHalf = currentElement.branches.slice(0, half);
887
+ const underHalf = currentElement.branches.slice(half);
888
+ let brachMiddle;
889
+ if (aboveHalf.length !== underHalf.length) {
890
+ brachMiddle = aboveHalf.pop();
891
+ }
892
+ // TÍNH CHIỀU CAO TỐI ĐA CỦA NHÁNH (đây là chiều rộng trong horizontal context nhưng
893
+ // dùng chung maxHeightBranch để đơn giản, ý nghĩa là "khoảng trống tối thiểu dành cho nhánh không có element")
894
+ const maxWidthBranch = { width: 0 };
895
+ this.calculatorMaxBranchWidth(currentElement, maxWidthBranch);
896
+ currentElement.maxHeightBranch = maxWidthBranch.width;
897
+ const maxWidthBrachMiddle = { width: 0, aboveWidth: 0, underWidth: 0 };
898
+ const lineFormElementToBranching = (currentElement.specific_width ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH;
899
+ // top = y + height/2: điểm giữa theo chiều Y → dùng làm tọa độ Y của đường ngang chính
900
+ const topMid = top + (currentElement.specific_height ?? 0) / 2;
901
+ if (brachMiddle) {
902
+ brachMiddle.onTheSide = 'center';
903
+ this.calculatorHeightBranch(brachMiddle.elements, maxWidthBrachMiddle);
904
+ brachMiddle.widthBranch = {
905
+ width: maxWidthBrachMiddle.width,
906
+ above: maxWidthBrachMiddle.aboveWidth,
907
+ under: maxWidthBrachMiddle.underWidth,
908
+ };
909
+ MoLibDiagramHorizontalCalculatorBranchUtil.drawStraightLine(topMid, lineFormElementToBranching, currentElement.specific_x ?? 0, brachMiddle, maxWidthBranch.width, config);
910
+ }
911
+ let hasNextSameParentElementNext = false;
912
+ const aboveHalfReverse = aboveHalf.reverse();
913
+ aboveHalfReverse.forEach((branch, indexBranch) => {
914
+ if (!hasNextSameParentElementNext) {
915
+ const flatElementOnBranch = [];
916
+ buildFlatElement(branch.elements, flatElementOnBranch);
917
+ hasNextSameParentElementNext = !flatElementOnBranch || !flatElementOnBranch.length ? true : flatElementOnBranch.find((item) => item.next_id === currentElement.next_id) ? true : false;
918
+ }
919
+ branch.onTheSide = 'above';
920
+ const maxWidthB = { width: 0, aboveWidth: 0, underWidth: 0 };
921
+ this.calculatorHeightBranch(branch.elements, maxWidthB);
922
+ branch.widthBranch = {
923
+ width: maxWidthB.width,
924
+ above: maxWidthB.aboveWidth,
925
+ under: maxWidthB.underWidth,
926
+ };
927
+ const branchPre = aboveHalfReverse[indexBranch - 1];
928
+ let lineBetweenBranch = maxWidthB.underWidth + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
929
+ if (branchPre) {
930
+ lineBetweenBranch += (branchPre.widthBranch?.width ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT;
931
+ }
932
+ if (brachMiddle) {
933
+ lineBetweenBranch += (brachMiddle.widthBranch?.above ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
934
+ }
935
+ MoLibDiagramHorizontalCalculatorBranchUtil.aboveLine(topMid, lineFormElementToBranching, currentElement.specific_x ?? 0, lineBetweenBranch, branch, maxWidthBranch.width, currentElement, config);
936
+ });
937
+ underHalf.forEach((branch, indexBranch) => {
938
+ if (!hasNextSameParentElementNext) {
939
+ const flatElementOnBranch = [];
940
+ buildFlatElement(branch.elements, flatElementOnBranch);
941
+ hasNextSameParentElementNext = !flatElementOnBranch || !flatElementOnBranch.length ? true : flatElementOnBranch.find((item) => item.next_id === currentElement.next_id) ? true : false;
942
+ }
943
+ branch.onTheSide = 'under';
944
+ const maxWidthB = { width: 0, aboveWidth: 0, underWidth: 0 };
945
+ this.calculatorHeightBranch(branch.elements, maxWidthB);
946
+ branch.widthBranch = {
947
+ width: maxWidthB.width,
948
+ above: maxWidthB.aboveWidth,
949
+ under: maxWidthB.underWidth,
950
+ };
951
+ const branchPre = underHalf[indexBranch - 1];
952
+ let lineBetweenBranch = maxWidthB.aboveWidth + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
953
+ if (branchPre) {
954
+ lineBetweenBranch += (branchPre.widthBranch?.width ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT;
955
+ }
956
+ if (brachMiddle) {
957
+ lineBetweenBranch += (brachMiddle.widthBranch?.under ?? 0) + canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT / 2;
958
+ }
959
+ MoLibDiagramHorizontalCalculatorBranchUtil.underLine(topMid, lineFormElementToBranching, currentElement.specific_x ?? 0, lineBetweenBranch, branch, maxWidthBranch.width, currentElement, config);
960
+ });
961
+ });
962
+ this.setTranslatePositions(elements);
963
+ }
964
+ /**
965
+ * Set translateX/Y cho tất cả elements (bao gồm branches và nodeOtherConfig)
966
+ */
967
+ static setTranslatePositions(elements) {
968
+ elements.forEach((element) => {
969
+ element.translateX = element.specific_x;
970
+ element.translateY = element.specific_y;
971
+ if (element.nodeOtherConfig) {
972
+ const offsetY = (element.nodeOtherConfig.specific_height ?? 0) / 2;
973
+ element.nodeOtherConfig.translateX = element.nodeOtherConfig.specific_x;
974
+ element.nodeOtherConfig.translateY = (element.nodeOtherConfig.specific_y ?? 0) - offsetY;
975
+ }
976
+ if (element.branches && element.branches.length > 0) {
977
+ element.branches.forEach((branch) => {
978
+ if (branch.nodeOtherConfig) {
979
+ const offsetY = (branch.nodeOtherConfig.specific_height ?? 0) / 2;
980
+ branch.nodeOtherConfig.translateX = branch.nodeOtherConfig.specific_x;
981
+ branch.nodeOtherConfig.translateY = (branch.nodeOtherConfig.specific_y ?? 0) - offsetY;
982
+ }
983
+ if (branch.elements && branch.elements.length > 0) {
984
+ this.setTranslatePositions(branch.elements);
985
+ }
986
+ });
987
+ }
988
+ });
989
+ }
990
+ /** Tính chiều rộng tối đa của nhánh (trong horizontal = chiều cao thực của nhánh không có element) */
991
+ static calculatorMaxBranchWidth(element, maxWidthBranch) {
992
+ if (!element.branches || !element.branches.length) {
993
+ return;
994
+ }
995
+ element.branches.forEach((branch) => {
996
+ if (!branch.elements || !branch.elements.length) {
997
+ const lineBranchNoHasElement = canvasConfigReadonly().ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT;
998
+ maxWidthBranch.width = lineBranchNoHasElement > maxWidthBranch.width ? lineBranchNoHasElement : maxWidthBranch.width;
999
+ return;
1000
+ }
1001
+ const total = { height: 0 };
1002
+ this.plusWidthElementInBranch(branch.elements, total);
1003
+ maxWidthBranch.width = total.height > maxWidthBranch.width ? total.height : maxWidthBranch.width;
1004
+ });
1005
+ }
1006
+ /** Tính tổng chiều rộng các elements trong nhánh (dọc theo trục X) */
1007
+ static plusWidthElementInBranch(elements, total) {
1008
+ elements.forEach((element) => {
1009
+ const margin = !element.branches || !element.branches.length ? this.checkElementGetMargin(element.nodeOtherConfig) : canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH;
1010
+ total.height += (element.specific_width ?? 0) + margin;
1011
+ if (!element.branches || !element.branches.length) {
1012
+ return;
1013
+ }
1014
+ total.height += 64;
1015
+ const theMostElementsInBranch = { height: 0 };
1016
+ element.branches.forEach((branch) => {
1017
+ const widthInBranch = { height: 0 };
1018
+ widthInBranch.height += canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST + 20;
1019
+ if (!branch.elements || !branch.elements.length) {
1020
+ widthInBranch.height += canvasConfigReadonly().ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT;
1021
+ if (theMostElementsInBranch.height < widthInBranch.height) {
1022
+ theMostElementsInBranch.height = widthInBranch.height;
1023
+ }
1024
+ return;
1025
+ }
1026
+ this.plusWidthElementInBranch(branch.elements, widthInBranch);
1027
+ if (theMostElementsInBranch.height < widthInBranch.height) {
1028
+ theMostElementsInBranch.height = widthInBranch.height;
1029
+ }
1030
+ });
1031
+ total.height += theMostElementsInBranch.height;
1032
+ });
1033
+ }
1034
+ /**
1035
+ * Tính chiều cao (Y span) của một nhánh — dùng để offset các nhánh trên/dưới.
1036
+ * Trong horizontal context: mỗi nhánh trải dài theo Y (lên/xuống).
1037
+ */
1038
+ static calculatorHeightBranch(elements, maxWidth) {
1039
+ if (!elements || !elements.length) {
1040
+ const w = canvasConfigReadonly().DEFAULT_BRANCH_WHEN_NO_ELEMENT;
1041
+ maxWidth.width = w;
1042
+ maxWidth.aboveWidth = w / 2;
1043
+ maxWidth.underWidth = w / 2;
1044
+ return;
1045
+ }
1046
+ const maxWidthInElements = { width: 0, aboveWidth: 0, underWidth: 0 };
1047
+ elements.forEach((element) => {
1048
+ if (!element.branches || !element.branches.length) {
1049
+ // element không nhánh: dùng specific_height làm Y span
1050
+ if ((element.specific_height ?? 0) < maxWidthInElements.width) {
1051
+ return;
1052
+ }
1053
+ const h = element.specific_height ?? 0;
1054
+ maxWidthInElements.width = h;
1055
+ maxWidthInElements.aboveWidth = h / 2;
1056
+ maxWidthInElements.underWidth = h / 2;
1057
+ return;
1058
+ }
1059
+ const widthElementIncludeBranch = { width: 0, aboveWidth: 0, underWidth: 0 };
1060
+ const h = element.specific_height ?? 0;
1061
+ if (h > widthElementIncludeBranch.width) {
1062
+ widthElementIncludeBranch.width = h;
1063
+ widthElementIncludeBranch.aboveWidth = h / 2;
1064
+ widthElementIncludeBranch.underWidth = h / 2;
1065
+ }
1066
+ const half = Math.ceil(element.branches.length / 2);
1067
+ const aboveHalf = element.branches.slice(0, half);
1068
+ const underHalf = element.branches.slice(half);
1069
+ let brachMiddle;
1070
+ if (aboveHalf.length !== underHalf.length) {
1071
+ brachMiddle = aboveHalf.pop();
1072
+ }
1073
+ if (brachMiddle)
1074
+ brachMiddle.onTheSide = 'center';
1075
+ aboveHalf.forEach((branch) => (branch.onTheSide = 'above'));
1076
+ underHalf.forEach((branch) => (branch.onTheSide = 'under'));
1077
+ const widthBranch = { width: 0, aboveWidth: 0, underWidth: 0 };
1078
+ element.branches.forEach((branch) => {
1079
+ const w = { width: 0, aboveWidth: 0, underWidth: 0 };
1080
+ this.calculatorHeightBranch(branch.elements, w);
1081
+ switch (branch.onTheSide) {
1082
+ case 'above':
1083
+ widthBranch.aboveWidth += w.width;
1084
+ break;
1085
+ case 'under':
1086
+ widthBranch.underWidth += w.width;
1087
+ break;
1088
+ case 'center':
1089
+ widthBranch.aboveWidth += w.aboveWidth;
1090
+ widthBranch.underWidth += w.underWidth;
1091
+ break;
1092
+ }
1093
+ });
1094
+ const marginBetweenBranch = (element.branches.length - 1) * canvasConfigReadonly().ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT;
1095
+ widthBranch.aboveWidth += marginBetweenBranch / 2;
1096
+ widthBranch.underWidth += marginBetweenBranch / 2;
1097
+ const maxAboveWidth = Math.max(widthElementIncludeBranch.aboveWidth, widthBranch.aboveWidth);
1098
+ const maxUnderWidth = Math.max(widthElementIncludeBranch.underWidth, widthBranch.underWidth);
1099
+ if (maxWidthInElements.aboveWidth < maxAboveWidth) {
1100
+ maxWidthInElements.aboveWidth = maxAboveWidth;
1101
+ }
1102
+ if (maxWidthInElements.underWidth < maxUnderWidth) {
1103
+ maxWidthInElements.underWidth = maxUnderWidth;
1104
+ }
1105
+ maxWidthInElements.width = maxWidthInElements.aboveWidth + maxWidthInElements.underWidth;
1106
+ });
1107
+ maxWidth.width += maxWidthInElements.aboveWidth + maxWidthInElements.underWidth;
1108
+ maxWidth.aboveWidth += maxWidthInElements.aboveWidth;
1109
+ maxWidth.underWidth += maxWidthInElements.underWidth;
1110
+ }
1111
+ /** Tính khoảng cách thêm vào sau element (bao gồm wait config nếu có) */
1112
+ static checkElementGetMargin(hasWaitConfig) {
1113
+ if (hasWaitConfig) {
1114
+ return canvasConfigReadonly().DISTANCE_TO_WAIT + this.getWidthWaitConfig(hasWaitConfig) + canvasConfigReadonly().WAIT_TO_ELEMENT;
1115
+ }
1116
+ return canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT;
1117
+ }
1118
+ /** Tính chiều rộng của wait config node */
1119
+ static getWidthWaitConfig(hasWaitConfig) {
1120
+ if (hasWaitConfig) {
1121
+ return (hasWaitConfig.specific_width ?? canvasConfigReadonly().ELEMENT_WAIT_DEFAULT) + canvasConfigReadonly().DISTANCE_WAIT_TO_NEXT_LINE;
1122
+ }
1123
+ return canvasConfigReadonly().ELEMENT_WAIT_DEFAULT + canvasConfigReadonly().DISTANCE_WAIT_TO_NEXT_LINE;
1124
+ }
1125
+ }
1126
+
1127
+ const canvasConfig = signal({
1128
+ ELEMENT_WIDTH_DEFAULT_BRANCH_WHEN_NOT_ELEMENT: 60,
1129
+ ELEMENT_MARGIN_DEFAULT: 60,
1130
+ ELEMENT_MARGIN_DEFAULT_BRANCH: 16,
1131
+ ELEMENT_MARGIN_BETWEEN_BRANCH_DEFAULT: 60,
1132
+ DEFAULT_BRANCH_WHEN_NO_ELEMENT: 100,
1133
+ ELEMENT_HEIGHT_CURVE: 10,
1134
+ ELEMENT_SVG_STROKE_COLOR: '9ca2ad',
1135
+ ELEMENT_SVG_STROKE_WIDTH: '1',
1136
+ ELEMENT_MARGIN_DEFAULT_BRANCH_TO_ELEMENT_FIRST: 78,
1137
+ DISTANCE_TO_WAIT: 30,
1138
+ WAIT_TO_ELEMENT: 50,
1139
+ ELEMENT_WAIT_DEFAULT: 28,
1140
+ DISTANCE_WAIT_TO_NEXT_LINE: 4,
1141
+ TYPE_ELEMENT_EXIT: 'EXIT',
1142
+ TYPE_ELEMENT_WORKFLOW: 'WORKFLOW',
1143
+ WIDTH_ELEMENT_DEFAULT: 165,
1144
+ HEIGHT_ELEMENT_DEFAULT: 48,
1145
+ ADD_ICON_DIAMETER: 24,
1146
+ });
1147
+ const canvasConfigReadonly = canvasConfig.asReadonly();
1148
+ const storeDataDefault = {
1149
+ positionMaxLeft: 0,
1150
+ positionMaxTop: 0,
1151
+ widthHeightSvgCanvas: { heightSvg: window.innerHeight, widthSvg: window.innerWidth },
1152
+ };
1153
+ class LibsUiDiagramDrawService {
1154
+ configCanvas = signal(undefined);
1155
+ orientation = signal('vertical');
1156
+ elementSvg = signal(undefined);
1157
+ attributeSvgD = signal('');
1158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1159
+ componentRefs = signal([]);
1160
+ // Virtualization Tracking
1161
+ virtualViewport;
1162
+ virtualFlatElements = [];
1163
+ virtualFlatBranches = [];
1164
+ virtualNodesMap = new Map();
1165
+ updateViewport(vp) {
1166
+ this.virtualViewport = vp;
1167
+ if (this.configCanvas()?.useVirtualizationTemplate) {
1168
+ this.applyVirtualizationDiff();
1169
+ }
1170
+ }
1171
+ set setConfig(config) {
1172
+ this.configCanvas.set(config);
1173
+ }
1174
+ set setConfigCanvas(config) {
1175
+ if (!config) {
1176
+ return;
1177
+ }
1178
+ canvasConfig.update((current) => ({ ...current, ...config }));
1179
+ }
1180
+ configXYElements(elements, updateSVG, positionX) {
1181
+ const storeData = this.configCanvas()?.storeDataDefine ?? storeDataDefault;
1182
+ const maxLeftOrigin = storeData.positionMaxLeft;
1183
+ const maxTopOrigin = storeData.positionMaxTop ?? 0;
1184
+ if (this.orientation() === 'horizontal') {
1185
+ MoLibDiagramHorizontalCalculatorCoordinatesUtil.setXYElements(elements, storeData, positionX);
1186
+ }
1187
+ else {
1188
+ MoLibDiagramCalculatorCoordinatesUtil.setXYElements(elements, storeData, positionX);
1189
+ }
1190
+ const maxLeftNew = storeData.positionMaxLeft;
1191
+ const maxTopNew = storeData.positionMaxTop ?? 0;
1192
+ if (!this.elementSvg()) {
1193
+ this.elementSvg.set(document.createElementNS('http://www.w3.org/2000/svg', 'path'));
1194
+ }
1195
+ if (!updateSVG && this.configCanvas()?.svgContainer && this.elementSvg()) {
1196
+ setAttributeSvgAndAppend(this.configCanvas()?.svgContainer, this.elementSvg(), '');
1197
+ }
1198
+ if (this.orientation() === 'horizontal' && maxTopOrigin !== maxTopNew) {
1199
+ this.updatePositionElements(maxTopNew, elements, storeData);
1200
+ return;
1201
+ }
1202
+ if (this.orientation() === 'vertical' && maxLeftOrigin !== maxLeftNew) {
1203
+ this.updatePositionElements(maxLeftNew, elements, storeData);
1204
+ return;
1205
+ }
1206
+ if (this.elementSvg()) {
1207
+ this.attributeSvgD.set('');
1208
+ this.buildSvgElement(elements);
1209
+ this.elementSvg()?.setAttribute('d', this.attributeSvgD());
1210
+ }
1211
+ }
1212
+ updatePositionElements(positionOffset, elements, config) {
1213
+ if (positionOffset && positionOffset < 0) {
1214
+ positionOffset = positionOffset * -1 + 200; // 100 là khoảng cách cách biên
1215
+ }
1216
+ if (this.orientation() === 'horizontal') {
1217
+ MoLibDiagramHorizontalCalculatorCoordinatesUtil.setXYElements(elements, { ...config, updateSVG: true }, positionOffset);
1218
+ }
1219
+ else {
1220
+ MoLibDiagramCalculatorCoordinatesUtil.setXYElements(elements, { ...config, updateSVG: true }, positionOffset);
1221
+ }
1222
+ if (!this.elementSvg()) {
1223
+ this.elementSvg.set(document.createElementNS('http://www.w3.org/2000/svg', 'path'));
1224
+ }
1225
+ if (this.elementSvg()) {
1226
+ this.attributeSvgD.set('');
1227
+ this.buildSvgElement(elements);
1228
+ this.elementSvg()?.setAttribute('d', this.attributeSvgD());
1229
+ }
1230
+ }
1231
+ buildSvgElement(elements) {
1232
+ elements.forEach((currentElement) => {
1233
+ if (currentElement.attributeSvgD) {
1234
+ this.attributeSvgD.update((prev) => `${prev ?? ''} ${currentElement.attributeSvgD}`);
1235
+ }
1236
+ if (currentElement.nodeOtherConfig?.attributeSvgD) {
1237
+ this.attributeSvgD.update((prev) => `${prev ?? ''} ${currentElement.nodeOtherConfig?.attributeSvgD}`);
1238
+ }
1239
+ if (!currentElement.branches || !currentElement.branches.length) {
1240
+ return;
1241
+ }
1242
+ currentElement.branches.forEach((branch) => {
1243
+ if (branch.attributeSvgD) {
1244
+ this.attributeSvgD.update((prev) => `${prev ?? ''} ${branch.attributeSvgD}`);
1245
+ }
1246
+ if (branch.nodeOtherConfig?.attributeSvgD) {
1247
+ this.attributeSvgD.update((prev) => `${prev ?? ''} ${branch.nodeOtherConfig?.attributeSvgD}`);
1248
+ }
1249
+ if (!branch.elements.length) {
1250
+ return;
1251
+ }
1252
+ this.buildSvgElement(branch.elements);
1253
+ });
1254
+ });
1255
+ }
1256
+ /**
1257
+ * Render tất cả nodes từ elements array
1258
+ */
1259
+ renderNodes(elements) {
1260
+ if (this.configCanvas()?.useVirtualizationTemplate) {
1261
+ this.extractVirtualElements(elements);
1262
+ this.applyVirtualizationDiff();
1263
+ return;
1264
+ }
1265
+ this.clearNodes();
1266
+ this.renderElementsRecursively(elements, true, undefined);
1267
+ this.renderBranchLabels(elements);
1268
+ }
1269
+ extractVirtualElements(elements) {
1270
+ this.virtualFlatElements = [];
1271
+ this.virtualFlatBranches = [];
1272
+ const scan = (list, branch, isFirstLevel = true) => {
1273
+ list.forEach((el, index) => {
1274
+ const shouldShowArrow = !(isFirstLevel && index === 0);
1275
+ this.virtualFlatElements.push({ element: el, branch, isOtherConfig: false, shouldShowArrow, parentId: el.id });
1276
+ if (el.nodeOtherConfig)
1277
+ this.virtualFlatElements.push({ element: el.nodeOtherConfig, branch, isOtherConfig: true, shouldShowArrow: false, parentId: el.id });
1278
+ if (el.branches) {
1279
+ el.branches.forEach((b, bIndex) => {
1280
+ if (!b.label)
1281
+ b.label = `Nhánh ${bIndex + 1}`;
1282
+ this.virtualFlatBranches.push(b);
1283
+ if (b.elements)
1284
+ scan(b.elements, b, false);
1285
+ if (b.nodeOtherConfig)
1286
+ this.virtualFlatElements.push({ element: b.nodeOtherConfig, branch: b, isOtherConfig: true, shouldShowArrow: false, parentId: 'branch_' + bIndex + '_' + el.id });
1287
+ });
1288
+ }
1289
+ });
1290
+ };
1291
+ scan(elements, undefined, true);
1292
+ }
1293
+ applyVirtualizationDiff() {
1294
+ const vp = this.virtualViewport;
1295
+ if (!vp)
1296
+ return;
1297
+ const config = this.configCanvas();
1298
+ const container = config?.nodesContainer?.nativeElement;
1299
+ if (!container || !config)
1300
+ return;
1301
+ const buffer = 300;
1302
+ const visibleIds = new Set();
1303
+ this.virtualFlatElements.forEach((item) => {
1304
+ const el = item.element;
1305
+ if (el.translateX === undefined || el.translateY === undefined)
1306
+ return;
1307
+ if (el.translateX + (el.specific_width || 165) < vp.x - buffer || el.translateX > vp.x + vp.width + buffer || el.translateY + (el.specific_height || 48) < vp.y - buffer || el.translateY > vp.y + vp.height + buffer)
1308
+ return;
1309
+ const id = String(item.parentId) + (item.isOtherConfig ? '_other' : '');
1310
+ visibleIds.add(id);
1311
+ if (!this.virtualNodesMap.has(id)) {
1312
+ const componentType = item.isOtherConfig ? config.nodeOtherConfigComponentType : config.nodeComponentType;
1313
+ if (componentType && config.viewContainerRef) {
1314
+ const ref = config.viewContainerRef.createComponent(componentType);
1315
+ ref.setInput('element', el);
1316
+ if (item.branch)
1317
+ ref.setInput('branch', item.branch);
1318
+ ref.setInput('orientation', this.orientation());
1319
+ const domEl = ref.location.nativeElement;
1320
+ domEl.classList.add('diagram-node-dynamic');
1321
+ applyElementStyle(domEl, el);
1322
+ container.appendChild(domEl);
1323
+ let arrow;
1324
+ if (item.shouldShowArrow && !item.isOtherConfig) {
1325
+ arrow = this.createArrowIndicator(el, container);
1326
+ }
1327
+ this.virtualNodesMap.set(id, { component: ref, arrow });
1328
+ }
1329
+ }
1330
+ else {
1331
+ const cache = this.virtualNodesMap.get(id);
1332
+ if (cache) {
1333
+ if (cache.component)
1334
+ applyElementStyle(cache.component.location.nativeElement, el);
1335
+ if (cache.arrow) {
1336
+ const arrowSize = 16;
1337
+ const nodeWidth = el.specific_width ?? 165;
1338
+ const nodeHeight = el.specific_height ?? 48;
1339
+ if (this.orientation() === 'horizontal') {
1340
+ cache.arrow.innerHTML = `
1341
+ <svg width="${arrowSize}" height="${arrowSize}" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1342
+ <path d="M8 4L16 8L8 12Z" fill="#9ca3af"/>
1343
+ </svg>
1344
+ `;
1345
+ cache.arrow.style.left = `${el.translateX - arrowSize}px`;
1346
+ cache.arrow.style.top = `${el.translateY + nodeHeight / 2 - arrowSize / 2}px`;
1347
+ }
1348
+ else {
1349
+ cache.arrow.innerHTML = `
1350
+ <svg width="${arrowSize}" height="${arrowSize}" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1351
+ <path d="M8 16L4 12H12L8 16Z" fill="#9ca3af"/>
1352
+ </svg>
1353
+ `;
1354
+ cache.arrow.style.left = `${el.translateX + nodeWidth / 2 - arrowSize / 2}px`;
1355
+ cache.arrow.style.top = `${el.translateY - arrowSize}px`;
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1360
+ });
1361
+ this.virtualFlatBranches.forEach((branch, index) => {
1362
+ const x = branch.specific_start_branch_x;
1363
+ const y = branch.specific_start_branch_y;
1364
+ if (x === undefined || y === undefined)
1365
+ return;
1366
+ if (x < vp.x - buffer || x > vp.x + vp.width + buffer || y < vp.y - buffer || y > vp.y + vp.height + buffer)
1367
+ return;
1368
+ const id = 'branch_' + index + '_' + Math.round(x) + '_' + Math.round(y);
1369
+ visibleIds.add(id);
1370
+ if (!this.virtualNodesMap.has(id)) {
1371
+ const labelDiv = this.createBranchLabel(branch);
1372
+ if (labelDiv)
1373
+ this.virtualNodesMap.set(id, { label: labelDiv });
1374
+ }
1375
+ else {
1376
+ const cache = this.virtualNodesMap.get(id);
1377
+ if (cache?.label) {
1378
+ const labelDiv = cache.label;
1379
+ const labelRect = labelDiv.getBoundingClientRect();
1380
+ const labelW = labelRect.width || labelDiv.offsetWidth;
1381
+ const labelH = labelRect.height || labelDiv.offsetHeight;
1382
+ if (this.orientation() === 'vertical') {
1383
+ labelDiv.style.left = `${x - labelW / 2 - 4}px`;
1384
+ labelDiv.style.top = `${y - labelH / 2 + 4}px`;
1385
+ }
1386
+ else {
1387
+ labelDiv.style.left = `${x + 4}px`;
1388
+ labelDiv.style.top = `${y - labelH / 2}px`; // center over wire
1389
+ }
1390
+ }
1391
+ }
1392
+ });
1393
+ Array.from(this.virtualNodesMap.entries()).forEach(([id, cache]) => {
1394
+ if (!visibleIds.has(id)) {
1395
+ cache.component?.destroy();
1396
+ cache.arrow?.remove();
1397
+ cache.label?.remove();
1398
+ this.virtualNodesMap.delete(id);
1399
+ }
1400
+ });
1401
+ }
1402
+ /**
1403
+ * Render elements và branches một cách đệ quy
1404
+ */
1405
+ renderElementsRecursively(elements, isFirstLevel = true, branch) {
1406
+ elements.forEach((element, index) => {
1407
+ if (element.translateX !== undefined && element.translateY !== undefined) {
1408
+ // Main node: có arrow nếu không phải element đầu tiên của level đầu
1409
+ const shouldShowArrow = !(isFirstLevel && index === 0);
1410
+ this.createNode(element, branch, shouldShowArrow, false);
1411
+ }
1412
+ // Render nodeOtherConfig nếu có (nodes phụ) - KHÔNG có arrow
1413
+ if (element.nodeOtherConfig && element.nodeOtherConfig.translateX !== undefined && element.nodeOtherConfig.translateY !== undefined) {
1414
+ this.createNode(element.nodeOtherConfig, branch, false, true);
1415
+ }
1416
+ // Render branches nếu có
1417
+ if (element.branches && element.branches.length > 0) {
1418
+ element.branches.forEach((branch) => {
1419
+ if (branch.elements && branch.elements.length > 0) {
1420
+ this.renderElementsRecursively(branch.elements, false, branch);
1421
+ }
1422
+ if (branch.nodeOtherConfig && branch.nodeOtherConfig.translateX !== undefined && branch.nodeOtherConfig.translateY !== undefined) {
1423
+ this.createNode(branch.nodeOtherConfig, branch, false, true);
1424
+ }
1425
+ });
1426
+ }
1427
+ });
1428
+ }
1429
+ /**
1430
+ * Tạo một node component động
1431
+ */
1432
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1433
+ createNode(element, branch, shouldShowArrow, isNodeOtherConfig) {
1434
+ const config = this.configCanvas();
1435
+ if (!config?.viewContainerRef) {
1436
+ return;
1437
+ }
1438
+ if (config.useVirtualizationTemplate) {
1439
+ if (config.nodesContainer?.nativeElement) {
1440
+ if (shouldShowArrow && !isNodeOtherConfig) {
1441
+ this.createArrowIndicator(element, config.nodesContainer.nativeElement);
1442
+ }
1443
+ }
1444
+ return;
1445
+ }
1446
+ const componentType = isNodeOtherConfig ? config.nodeOtherConfigComponentType : config.nodeComponentType;
1447
+ if (!componentType) {
1448
+ return;
1449
+ }
1450
+ const componentRef = config.viewContainerRef.createComponent(componentType);
1451
+ const el = componentRef.location.nativeElement;
1452
+ componentRef.setInput('element', element);
1453
+ if (branch) {
1454
+ componentRef.setInput('branch', branch);
1455
+ }
1456
+ componentRef.setInput('orientation', this.orientation());
1457
+ el.classList.add('diagram-node-dynamic');
1458
+ applyElementStyle(el, element);
1459
+ if (config.nodesContainer?.nativeElement) {
1460
+ config.nodesContainer.nativeElement.appendChild(el);
1461
+ if (shouldShowArrow && !isNodeOtherConfig) {
1462
+ this.createArrowIndicator(element, config.nodesContainer.nativeElement);
1463
+ }
1464
+ }
1465
+ this.componentRefs.update((refs) => [...refs, componentRef]); // Lưu reference để cleanup sau
1466
+ return componentRef;
1467
+ }
1468
+ /**
1469
+ * Clear tất cả nodes đã tạo
1470
+ */
1471
+ clearNodes() {
1472
+ this.componentRefs().forEach((ref) => {
1473
+ ref.destroy();
1474
+ });
1475
+ this.componentRefs.set([]);
1476
+ Array.from(this.virtualNodesMap.values()).forEach((cache) => {
1477
+ cache.component?.destroy();
1478
+ cache.arrow?.remove();
1479
+ cache.label?.remove();
1480
+ });
1481
+ this.virtualNodesMap.clear();
1482
+ const config = this.configCanvas();
1483
+ const nodesContainer = config?.nodesContainer?.nativeElement;
1484
+ if (nodesContainer) {
1485
+ if (!config?.useVirtualizationTemplate) {
1486
+ const dynamicNodes = nodesContainer.querySelectorAll('.diagram-node-dynamic');
1487
+ dynamicNodes.forEach((node) => node.remove());
1488
+ }
1489
+ const labels = nodesContainer.querySelectorAll('.branch-label');
1490
+ labels.forEach((label) => label.remove());
1491
+ const arrows = nodesContainer.querySelectorAll('.node-arrow-indicator');
1492
+ arrows.forEach((arrow) => arrow.remove());
1493
+ }
1494
+ }
1495
+ /**
1496
+ * Tạo mũi tên indicator chỉ hướng vào node.
1497
+ * - vertical: mũi tên ▼ phía TRÊN node (luồng trên → xuống)
1498
+ * - horizontal: mũi tên ► phía TRÁI node (luồng trái → phải)
1499
+ */
1500
+ createArrowIndicator(element, container) {
1501
+ if (element.translateX === undefined || element.translateY === undefined) {
1502
+ return undefined;
1503
+ }
1504
+ const arrow = document.createElement('div');
1505
+ arrow.className = 'node-arrow-indicator';
1506
+ arrow.style.position = 'absolute';
1507
+ arrow.style.zIndex = '1';
1508
+ arrow.style.pointerEvents = 'none';
1509
+ const arrowSize = 16;
1510
+ const nodeWidth = element.specific_width ?? 0;
1511
+ const nodeHeight = element.specific_height ?? 0;
1512
+ if (this.orientation() === 'horizontal') {
1513
+ // Mũi tên ► ở bên TRÁI node, căn giữa theo chiều cao
1514
+ arrow.innerHTML = `
1515
+ <svg width="${arrowSize}" height="${arrowSize}" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1516
+ <path d="M8 4L16 8L8 12Z" fill="#9ca3af"/>
1517
+ </svg>
1518
+ `;
1519
+ arrow.style.left = `${element.translateX - arrowSize}px`;
1520
+ arrow.style.top = `${element.translateY + nodeHeight / 2 - arrowSize / 2}px`;
1521
+ }
1522
+ else {
1523
+ // Mũi tên ▼ ở phía TRÊN node, căn giữa theo chiều rộng
1524
+ arrow.innerHTML = `
1525
+ <svg width="${arrowSize}" height="${arrowSize}" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1526
+ <path d="M8 16L4 12H12L8 16Z" fill="#9ca3af"/>
1527
+ </svg>
1528
+ `;
1529
+ arrow.style.left = `${element.translateX + nodeWidth / 2 - arrowSize / 2}px`;
1530
+ arrow.style.top = `${element.translateY - arrowSize}px`;
1531
+ }
1532
+ container.appendChild(arrow);
1533
+ return arrow;
1534
+ }
1535
+ /**
1536
+ * Render branch labels (text annotations on branches)
1537
+ */
1538
+ renderBranchLabels(elements) {
1539
+ const svgContainer = this.configCanvas()?.svgContainer?.nativeElement;
1540
+ if (!svgContainer) {
1541
+ return;
1542
+ }
1543
+ elements.forEach((element) => {
1544
+ if (element.branches && element.branches.length > 0) {
1545
+ element.branches.forEach((branch, index) => {
1546
+ if (branch.specific_start_branch_x !== undefined && branch.specific_start_branch_y !== undefined) {
1547
+ if (!branch.label) {
1548
+ branch.label = `Nhánh ${index + 1}`;
1549
+ }
1550
+ this.createBranchLabel(branch);
1551
+ }
1552
+ if (branch.elements && branch.elements.length > 0) {
1553
+ this.renderBranchLabels(branch.elements);
1554
+ }
1555
+ });
1556
+ }
1557
+ });
1558
+ }
1559
+ createBranchLabel(branch) {
1560
+ const config = this.configCanvas();
1561
+ const container = config?.nodesContainer?.nativeElement;
1562
+ if (!container) {
1563
+ return undefined;
1564
+ }
1565
+ const labelDiv = document.createElement('div');
1566
+ labelDiv.className = 'branch-label';
1567
+ labelDiv.textContent = branch.label || '';
1568
+ labelDiv.style.position = 'absolute';
1569
+ labelDiv.style.padding = '2px 7px';
1570
+ labelDiv.style.borderRadius = '10px';
1571
+ labelDiv.style.backgroundColor = branch.labelBgColor || '#f3f4f6';
1572
+ labelDiv.style.fontSize = '11px';
1573
+ labelDiv.style.fontFamily = 'system-ui, -apple-system, sans-serif';
1574
+ labelDiv.style.color = '#374151';
1575
+ labelDiv.style.whiteSpace = 'nowrap';
1576
+ labelDiv.style.zIndex = '2';
1577
+ labelDiv.style.pointerEvents = 'none';
1578
+ container.appendChild(labelDiv);
1579
+ const startX = branch.specific_start_branch_x ?? 0;
1580
+ const startY = branch.specific_start_branch_y ?? 0;
1581
+ const labelW = labelDiv.offsetWidth;
1582
+ const labelH = labelDiv.offsetHeight || 18;
1583
+ if (this.orientation() === 'horizontal') {
1584
+ /**
1585
+ * Horizontal: label nằm TRÊN đường line nằm ngang.
1586
+ * specific_start_branch = điểm bắt đầu đoạn thẳng ngang đến element.
1587
+ * Đặt label tịnh tiến sang phải 8px từ ngoặt nhánh, và căn giữa dây.
1588
+ */
1589
+ labelDiv.style.left = `${startX + 4}px`;
1590
+ labelDiv.style.top = `${startY - labelH / 2}px`;
1591
+ }
1592
+ else {
1593
+ /**
1594
+ * Vertical: label nằm BÊN TRÁI đường ngang, căn giữa theo Y.
1595
+ * specific_start_branch = điểm đầu đoạn thẳng đến element (sau curve).
1596
+ */
1597
+ labelDiv.style.left = `${startX - labelW / 2 - 4}px`;
1598
+ labelDiv.style.top = `${startY - labelH / 2 + 4}px`;
1599
+ }
1600
+ return labelDiv;
1601
+ }
1602
+ /**
1603
+ * Render dấu "+" tại mỗi vị trí có thể thả element từ sidebar.
1604
+ * Service tự tạo DOM elements và inject vào nodesContainer.
1605
+ * @param flatElements Danh sách flat elements đã tính toán vị trí
1606
+ * @param onDrop Callback khi người dùng thả element vào một "+"
1607
+ * @param canDropFn (tùy chọn) Function kiểm tra điều kiện hiển thị drop zone.
1608
+ * ```
1609
+ */
1610
+ renderDropZones(flatElements, onDrop, canDropFn, ruleConnectableElements) {
1611
+ this.clearDropZones();
1612
+ const container = this.configCanvas()?.nodesContainer?.nativeElement;
1613
+ if (!container)
1614
+ return;
1615
+ flatElements.forEach((el) => {
1616
+ if (el.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT)
1617
+ return;
1618
+ if (el.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
1619
+ el.branches?.forEach((branch) => {
1620
+ if (canDropFn(undefined, el, branch, ruleConnectableElements))
1621
+ return;
1622
+ const top = this.orientation() === 'horizontal' ? this.calcDropZoneTopBranchH(el, branch) : this.calcDropZoneTopBranch(el, branch);
1623
+ const left = this.orientation() === 'horizontal' ? this.calcDropZoneLeftBranchH(el, branch) : this.calcDropZoneLeftBranch(el, branch);
1624
+ this.createDropZoneEl(container, top, left, el, branch, onDrop);
1625
+ });
1626
+ }
1627
+ else {
1628
+ if (!canDropFn(undefined, el, undefined, ruleConnectableElements))
1629
+ return;
1630
+ const top = this.orientation() === 'horizontal' ? this.calcDropZoneTopNodeH(el) : this.calcDropZoneTopNode(el);
1631
+ const left = this.orientation() === 'horizontal' ? this.calcDropZoneLeftNodeH(el) : this.calcDropZoneLeftNode(el);
1632
+ this.createDropZoneEl(container, top, left, el, undefined, onDrop);
1633
+ }
1634
+ });
1635
+ }
1636
+ /** Xóa tất cả drop zone "+" khỏi DOM */
1637
+ clearDropZones() {
1638
+ const container = this.configCanvas()?.nodesContainer?.nativeElement;
1639
+ if (!container)
1640
+ return;
1641
+ container.querySelectorAll('.diagram-drop-zone').forEach((el) => el.remove());
1642
+ }
1643
+ createDropZoneEl(container, top, left, element, branch, onDrop) {
1644
+ const indicatorStyle = this.configCanvas()?.dropZoneStyle;
1645
+ const RECT_W = element.specific_width ?? canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT;
1646
+ const RECT_H = element.specific_height ?? canvasConfigReadonly().HEIGHT_ELEMENT_DEFAULT;
1647
+ // Tâm của nút tròn
1648
+ const centerX = left + canvasConfigReadonly().ADD_ICON_DIAMETER / 2;
1649
+ const centerY = top + canvasConfigReadonly().ADD_ICON_DIAMETER / 2;
1650
+ // Hit zone vô hình: rộng hơn rectangle một khoản padding để bắt drag sớm hơn
1651
+ const HIT_PAD = 20;
1652
+ const HIT_W = RECT_W + HIT_PAD * 2;
1653
+ const HIT_H = RECT_H + HIT_PAD * 2;
1654
+ const hitLeft = centerX - HIT_W / 2;
1655
+ const hitTop = centerY - HIT_H / 2;
1656
+ // ── Hit zone (wrapper vô hình, vùng nhận drag rộng) ─────────────────
1657
+ const hitZone = document.createElement('div');
1658
+ hitZone.className = 'diagram-drop-zone';
1659
+ hitZone.style.cssText = `
1660
+ position: absolute;
1661
+ top: ${hitTop}px;
1662
+ left: ${hitLeft}px;
1663
+ z-index: 9;
1664
+ width: ${HIT_W}px;
1665
+ height: ${HIT_H}px;
1666
+ cursor: pointer;
1667
+ `;
1668
+ // ── Indicator (con bên trong, phần hiển thị) ─────────────────────────
1669
+ const indicator = document.createElement('div');
1670
+ indicator.style.pointerEvents = 'none'; // tránh tranh chấp event với hitZone
1671
+ // Vị trí dot trong hitZone (căn giữa hitZone)
1672
+ const dotOffsetLeft = HIT_W / 2 - canvasConfigReadonly().ADD_ICON_DIAMETER / 2;
1673
+ const dotOffsetTop = HIT_H / 2 - canvasConfigReadonly().ADD_ICON_DIAMETER / 2;
1674
+ // Vị trí rectangle trong hitZone (căn giữa hitZone)
1675
+ const rectOffsetLeft = HIT_PAD;
1676
+ const rectOffsetTop = HIT_H / 2 - RECT_H / 2;
1677
+ const applyDotStyle = () => {
1678
+ const s = indicatorStyle;
1679
+ const size = canvasConfigReadonly().ADD_ICON_DIAMETER;
1680
+ const bg = s?.backgroundColor ?? '#3b82f6';
1681
+ const iconColor = s?.iconColor ?? '#ffffff';
1682
+ const opacity = s?.opacity ?? 0.95;
1683
+ const borderRadius = s?.borderRadius ?? '50%';
1684
+ setStylesElement(indicator, {
1685
+ position: 'absolute',
1686
+ top: `${dotOffsetTop}px`,
1687
+ left: `${dotOffsetLeft}px`,
1688
+ width: `${size}px`,
1689
+ height: `${size}px`,
1690
+ pointerEvents: 'none',
1691
+ display: 'flex',
1692
+ alignItems: 'center',
1693
+ justifyContent: 'center',
1694
+ transition: 'opacity 0.15s ease',
1695
+ border: 'none',
1696
+ });
1697
+ if (s?.renderIndicator) {
1698
+ indicator.innerHTML = s.renderIndicator(size);
1699
+ }
1700
+ else {
1701
+ indicator.innerHTML = `
1702
+ <div style="
1703
+ width: ${size}px;
1704
+ height: ${size}px;
1705
+ background-color: ${bg};
1706
+ border-radius: ${borderRadius};
1707
+ opacity: ${opacity};
1708
+ display: flex;
1709
+ align-items: center;
1710
+ justify-content: center;
1711
+ pointer-events: none;
1712
+ "><i class="libs-ui-icon-add before:text-[${iconColor}]"></i></div>
1713
+ `;
1714
+ }
1715
+ };
1716
+ const applyRectStyle = () => {
1717
+ const s = indicatorStyle;
1718
+ const hoverBg = s?.hoverBackgroundColor ?? '#eef4ff';
1719
+ const hoverBorder = s?.hoverBorderColor ?? 'rgba(59, 130, 246, 0.7)';
1720
+ const hoverBorderStyle = s?.hoverBorderStyle ?? 'dashed';
1721
+ const hoverRadius = s?.hoverBorderRadius ?? 10;
1722
+ setStylesElement(indicator, {
1723
+ top: `${rectOffsetTop}px`,
1724
+ left: `${rectOffsetLeft}px`,
1725
+ width: `${RECT_W}px`,
1726
+ height: `${RECT_H}px`,
1727
+ borderRadius: `${hoverRadius}px`,
1728
+ backgroundColor: hoverBg,
1729
+ border: `2px ${hoverBorderStyle} ${hoverBorder}`,
1730
+ transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
1731
+ });
1732
+ // Nếu có hàm render động cho trạng thái hover — gọi nó
1733
+ indicator.innerHTML = s?.renderHoverIndicator ? s.renderHoverIndicator() : '';
1734
+ };
1735
+ applyDotStyle();
1736
+ hitZone.appendChild(indicator);
1737
+ hitZone.addEventListener('dragenter', (e) => {
1738
+ e.preventDefault();
1739
+ applyRectStyle();
1740
+ });
1741
+ hitZone.addEventListener('dragover', (e) => {
1742
+ e.preventDefault();
1743
+ if (e.dataTransfer)
1744
+ e.dataTransfer.dropEffect = 'copy';
1745
+ });
1746
+ hitZone.addEventListener('dragleave', (e) => {
1747
+ if (!hitZone.contains(e.relatedTarget)) {
1748
+ applyDotStyle();
1749
+ }
1750
+ });
1751
+ hitZone.addEventListener('drop', (e) => {
1752
+ e.preventDefault();
1753
+ e.stopPropagation();
1754
+ const raw = e.dataTransfer?.getData('application/json') ?? '';
1755
+ onDrop(element, branch, raw);
1756
+ });
1757
+ container.appendChild(hitZone);
1758
+ }
1759
+ // ── Vertical drop zone helpers (existing) ───────────────────────────────────
1760
+ calcDropZoneTopBranch(el, branch) {
1761
+ const oc = branch.nodeOtherConfig;
1762
+ if (oc)
1763
+ return (oc.specific_y ?? 0) + (oc.specific_height ?? canvasConfigReadonly().ELEMENT_WAIT_DEFAULT) + 10;
1764
+ return (branch.specific_start_branch_y ?? 0) + 30;
1765
+ }
1766
+ calcDropZoneLeftBranch(el, branch) {
1767
+ const firstEl = branch.elements?.[0];
1768
+ if (firstEl?.translateX !== undefined)
1769
+ return firstEl.translateX + (firstEl.specific_width ?? canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT) / 2 - canvasConfigReadonly().ADD_ICON_DIAMETER / 2;
1770
+ return (el.translateX ?? 0) + (el.specific_width ?? canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT) / 2 - canvasConfigReadonly().ADD_ICON_DIAMETER / 2;
1771
+ }
1772
+ calcDropZoneTopNode(el) {
1773
+ const oc = el.nodeOtherConfig;
1774
+ if (oc)
1775
+ return (oc.specific_y ?? 0) + (oc.specific_height ?? canvasConfigReadonly().ELEMENT_WAIT_DEFAULT) + 10;
1776
+ return (el.translateY ?? 0) + (el.specific_height ?? canvasConfigReadonly().HEIGHT_ELEMENT_DEFAULT) + 10;
1777
+ }
1778
+ calcDropZoneLeftNode(el) {
1779
+ return (el.translateX ?? 0) + (el.specific_width ?? canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT) / 2 - canvasConfigReadonly().ADD_ICON_DIAMETER / 2;
1780
+ }
1781
+ // ── Horizontal drop zone helpers ─────────────────────────────────────────────
1782
+ /**
1783
+ * Horizontal node: "+" nằm giữa đường line nối 2 node — căn giữa Y của element.
1784
+ */
1785
+ calcDropZoneTopNodeH(el) {
1786
+ const D = canvasConfigReadonly().ADD_ICON_DIAMETER;
1787
+ const centerY = (el.translateY ?? 0) + (el.specific_height ?? canvasConfigReadonly().HEIGHT_ELEMENT_DEFAULT) / 2;
1788
+ return centerY - D / 2;
1789
+ }
1790
+ calcDropZoneLeftNodeH(el) {
1791
+ const D = canvasConfigReadonly().ADD_ICON_DIAMETER;
1792
+ const M = canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT;
1793
+ const oc = el.nodeOtherConfig;
1794
+ if (oc) {
1795
+ const right = (oc.specific_x ?? 0) + (oc.specific_width ?? canvasConfigReadonly().ELEMENT_WAIT_DEFAULT);
1796
+ return right + M / 2 - D / 2;
1797
+ }
1798
+ const right = (el.translateX ?? 0) + (el.specific_width ?? canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT);
1799
+ // Midpoint của khoảng trống giữa node và node tiếp theo (gap = ELEMENT_MARGIN_DEFAULT)
1800
+ return right + M / 2 - D / 2;
1801
+ }
1802
+ /**
1803
+ * Horizontal branch: "+" nằm căn giữa Y của branch line, sau phần tử cuối trong nhánh.
1804
+ */
1805
+ calcDropZoneTopBranchH(el, branch) {
1806
+ const D = canvasConfigReadonly().ADD_ICON_DIAMETER;
1807
+ // specific_start_branch_y = tâm Y của đường nhánh
1808
+ const centerY = branch.specific_start_branch_y ?? 0;
1809
+ return centerY - D / 2;
1810
+ }
1811
+ calcDropZoneLeftBranchH(el, branch) {
1812
+ const D = canvasConfigReadonly().ADD_ICON_DIAMETER;
1813
+ const M = canvasConfigReadonly().ELEMENT_MARGIN_DEFAULT;
1814
+ // Sau nodeOtherConfig của nhánh (cổ nhánh)
1815
+ const oc = branch.nodeOtherConfig;
1816
+ if (oc?.specific_x !== undefined) {
1817
+ return (oc.specific_x ?? 0) + (oc.specific_width ?? canvasConfigReadonly().ELEMENT_WAIT_DEFAULT) + M / 2 - D / 2;
1818
+ }
1819
+ // Nếu nhánh không có nodeOtherConfig: đặt sau điểm rẽ nhánh
1820
+ return (branch.specific_start_branch_x ?? 0) + M / 2 - D / 2;
1821
+ }
1822
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1823
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawService, providedIn: 'root' });
1824
+ }
1825
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawService, decorators: [{
1826
+ type: Injectable,
1827
+ args: [{
1828
+ providedIn: 'root',
1829
+ }]
1830
+ }] });
1831
+
1832
+ class LibsUiDiagramDrawCanvasUtil {
1833
+ static buildFlatElement(elements, flatElementsContainer) {
1834
+ elements.forEach((element) => {
1835
+ flatElementsContainer.push(element);
1836
+ if (!element.branches || !element.branches.length) {
1837
+ return;
1838
+ }
1839
+ LibsUiDiagramDrawCanvasUtil.flatElementHasBranch(element, flatElementsContainer);
1840
+ });
1841
+ }
1842
+ static flatElementHasBranch(element, flatElementsContainer) {
1843
+ if (!element.branches || !element.branches.length) {
1844
+ return;
1845
+ }
1846
+ element.branches.forEach((branch) => {
1847
+ if (!branch.elements || !branch.elements.length) {
1848
+ return;
1849
+ }
1850
+ LibsUiDiagramDrawCanvasUtil.buildFlatElement(branch.elements, flatElementsContainer);
1851
+ });
1852
+ }
1853
+ static getDefaultStorageBranchEndCount() {
1854
+ return { branchContainer: undefined, count: 0, branchEmpty: 0 };
1855
+ }
1856
+ /**
1857
+ * data: sử dụng reference Object type nên count sẽ đệ quy cho đến node cuối cùng
1858
+ * data.branchContainer: branch cuối cùng của element không next đến idElement
1859
+ * data.count: số lượng branch có next_id đến idElement
1860
+ */
1861
+ static getBranchContainerAndCountElementHasNextIdToElement(element, idElement, data, flowFlatElements) {
1862
+ if (!element || !element.branches) {
1863
+ return;
1864
+ }
1865
+ element.branches.forEach((branch) => {
1866
+ if (!branch.elements || !branch.elements.length) {
1867
+ data.count++;
1868
+ data.branchContainer = branch;
1869
+ data.branchEmpty++;
1870
+ return;
1871
+ }
1872
+ const lastElement = branch.elements[branch.elements.length - 1];
1873
+ if (!lastElement.next_id) {
1874
+ return;
1875
+ }
1876
+ if (lastElement.branches && lastElement.branches.length) {
1877
+ const dataOfLastElement = this.getDefaultStorageBranchEndCount();
1878
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(lastElement, idElement, dataOfLastElement, flowFlatElements);
1879
+ if (!dataOfLastElement.count) {
1880
+ lastElement.next_id = '';
1881
+ return;
1882
+ }
1883
+ data.count++;
1884
+ data.branchContainer = branch;
1885
+ if (dataOfLastElement.count === 1) {
1886
+ return (data.branchContainer = dataOfLastElement.branchContainer);
1887
+ }
1888
+ return;
1889
+ }
1890
+ const nextOfLastElement = flowFlatElements.find((el) => el.id === lastElement.next_id);
1891
+ if (nextOfLastElement && nextOfLastElement.id !== idElement) {
1892
+ return;
1893
+ }
1894
+ data.count++;
1895
+ data.branchContainer = branch;
1896
+ return;
1897
+ });
1898
+ }
1899
+ static getElementHasCurrentBranchOrCurrentElement(currentBranch, currentElement, flatElements) {
1900
+ const data = {
1901
+ currentBranch: undefined,
1902
+ element: undefined,
1903
+ };
1904
+ if (!flatElements || !flatElements.length || (!currentBranch && !currentElement)) {
1905
+ return data;
1906
+ }
1907
+ if (!currentElement) {
1908
+ data.currentBranch = currentBranch;
1909
+ data.element = flatElements.find((el) => el.branches && !!el.branches.find((branch) => branch === currentBranch));
1910
+ return data;
1911
+ }
1912
+ const elementsHasBranches = flatElements.filter((item) => item.branches && item.branches.length);
1913
+ if (!elementsHasBranches) {
1914
+ return data;
1915
+ }
1916
+ for (const element of elementsHasBranches) {
1917
+ const branches = element.branches;
1918
+ if (!branches) {
1919
+ return data;
1920
+ }
1921
+ for (const branch of branches) {
1922
+ if (!branch.elements || !branch.elements.length) {
1923
+ continue;
1924
+ }
1925
+ if (branch.elements.find((item) => item.id === currentElement.id)) {
1926
+ data.currentBranch = branch;
1927
+ data.element = element;
1928
+ return data;
1929
+ }
1930
+ }
1931
+ }
1932
+ return data;
1933
+ }
1934
+ static findAndDeleteElementFromContainer(containerElement, elementDelete) {
1935
+ for (const index in containerElement) {
1936
+ const elementOfIndex = containerElement[index];
1937
+ if (elementOfIndex.id === elementDelete.id) {
1938
+ containerElement.splice(+index, 1);
1939
+ return true;
1940
+ }
1941
+ if (!elementOfIndex.branches || !elementOfIndex.branches.length) {
1942
+ continue;
1943
+ }
1944
+ for (const branch of elementOfIndex.branches) {
1945
+ if (!branch.elements || !branch.elements.length) {
1946
+ continue;
1947
+ }
1948
+ if (this.findAndDeleteElementFromContainer(branch.elements, elementDelete)) {
1949
+ return true;
1950
+ }
1951
+ }
1952
+ }
1953
+ return false;
1954
+ }
1955
+ static getBranchOfElement(elementId, flatElements, elementTypeWorkflow) {
1956
+ const flatElementHasBranch = flatElements.filter((item) => item.element_type === elementTypeWorkflow);
1957
+ if (!flatElementHasBranch) {
1958
+ return;
1959
+ }
1960
+ for (const item of flatElementHasBranch) {
1961
+ if (!item.branches || !item.branches.length) {
1962
+ continue;
1963
+ }
1964
+ for (const branch of item.branches) {
1965
+ const index = branch.elements.findIndex((el) => el.id === elementId);
1966
+ if (index === -1) {
1967
+ continue;
1968
+ }
1969
+ return branch;
1970
+ }
1971
+ }
1972
+ return;
1973
+ }
1974
+ }
1975
+ const getDefaultConfigConstituentBranches = (code, dataDefault) => {
1976
+ dataDefault.branches = [
1977
+ {
1978
+ elements: [],
1979
+ },
1980
+ {
1981
+ elements: [],
1982
+ },
1983
+ ];
1984
+ return dataDefault;
1985
+ };
1986
+ const findAndDeleteElementFromContainer = (containerElement, elementDelete) => {
1987
+ for (const index in containerElement) {
1988
+ const elementOfIndex = containerElement[index];
1989
+ if (elementOfIndex.id === elementDelete.id) {
1990
+ containerElement.splice(+index, 1);
1991
+ return true;
1992
+ }
1993
+ if (!elementOfIndex.branches || !elementOfIndex.branches.length) {
1994
+ continue;
1995
+ }
1996
+ for (const branch of elementOfIndex.branches) {
1997
+ if (!branch.elements || !branch.elements.length) {
1998
+ continue;
1999
+ }
2000
+ if (findAndDeleteElementFromContainer(branch.elements, elementDelete)) {
2001
+ return true;
2002
+ }
2003
+ }
2004
+ }
2005
+ return false;
2006
+ };
2007
+
2008
+ class LibsUiDiagramDrawCanvasService {
2009
+ elements;
2010
+ flatElements;
2011
+ handlerFunction;
2012
+ elementRuleConnectable = [];
2013
+ translateService = inject(TranslateService);
2014
+ set HandlerFunction(handlerFunction) {
2015
+ this.handlerFunction = handlerFunction;
2016
+ }
2017
+ get HandlerFunction() {
2018
+ return this.handlerFunction;
2019
+ }
2020
+ loadElements(elements, positionFirstNode, ruleConnectableElements) {
2021
+ if (!elements || !elements.length) {
2022
+ return;
2023
+ }
2024
+ this.flatElements = [];
2025
+ const firstElement = elements[0];
2026
+ firstElement.specific_x = positionFirstNode.x;
2027
+ firstElement.specific_y = positionFirstNode.y;
2028
+ this.Elements = elements;
2029
+ this.RuleConnectableElements = ruleConnectableElements;
2030
+ }
2031
+ set Elements(elements) {
2032
+ this.elements = elements;
2033
+ }
2034
+ get Elements() {
2035
+ return this.elements;
2036
+ }
2037
+ set RuleConnectableElements(elements) {
2038
+ this.elementRuleConnectable = elements;
2039
+ }
2040
+ get RuleConnectableElements() {
2041
+ return this.elementRuleConnectable;
2042
+ }
2043
+ checkRuleDragDrop(beforeElementDrag, currentElementDrag, afterElementDrag, ruleConnectableElements) {
2044
+ const ruleItemDrag = ruleConnectableElements.find((item) => item.elementCode === currentElementDrag?.code);
2045
+ const ruleAfterItemDrag = ruleConnectableElements.find((item) => afterElementDrag && item.elementCode === afterElementDrag.code);
2046
+ if (!!(ruleItemDrag && ruleItemDrag.elementCodeConnectableBefore.find((code) => code === beforeElementDrag?.code)) &&
2047
+ (!ruleAfterItemDrag || !!(ruleAfterItemDrag && ruleAfterItemDrag.elementCodeConnectableBefore.find((code) => code === currentElementDrag?.code)))) {
2048
+ return true;
2049
+ }
2050
+ return false;
2051
+ }
2052
+ get FlatElements() {
2053
+ this.flatElements = [];
2054
+ LibsUiDiagramDrawCanvasUtil.buildFlatElement(this.Elements, this.flatElements);
2055
+ return this.flatElements;
2056
+ }
2057
+ checkExistNameElement(name, flatElements) {
2058
+ if (!name || !name.trim()) {
2059
+ return false;
2060
+ }
2061
+ return flatElements?.find((element) => element.name && element.name.trim() && element.name.trim().toLocaleLowerCase() === name.trim().toLocaleLowerCase()) ? true : false;
2062
+ }
2063
+ addElement(itemDrop, branchDrop, dataDrop, branchChoice) {
2064
+ if (!itemDrop || !dataDrop) {
2065
+ return;
2066
+ }
2067
+ const elementsFlat = this.FlatElements;
2068
+ if (dataDrop.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2069
+ let counter = 0;
2070
+ const elementsOfCodeDataDrop = this.FlatElements?.filter((element) => element.code === dataDrop.code);
2071
+ if (elementsOfCodeDataDrop && elementsOfCodeDataDrop.length) {
2072
+ counter = elementsOfCodeDataDrop.reduce((preEl, currentEl) => (Number(currentEl.specific_counter_current_code) > Number(preEl.specific_counter_current_code) ? currentEl : preEl))
2073
+ .specific_counter_current_code;
2074
+ }
2075
+ counter++;
2076
+ let name = `${dataDrop.name} ${counter}`;
2077
+ while (this.checkExistNameElement(name, elementsFlat)) {
2078
+ counter++;
2079
+ name = `${dataDrop.name} ${counter}`;
2080
+ }
2081
+ dataDrop.name = name;
2082
+ dataDrop.specific_counter_current_code = counter;
2083
+ }
2084
+ if (branchDrop) {
2085
+ return this.addElementToBranch(itemDrop, branchDrop, cloneDeep(dataDrop), branchChoice);
2086
+ }
2087
+ const elements = this.Elements;
2088
+ const nextCurrentItemDrop = elements?.find((item) => item.id === itemDrop.next_id);
2089
+ if (!nextCurrentItemDrop && dataDrop.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2090
+ return;
2091
+ }
2092
+ const indexItemDrop = elements?.findIndex((item) => item.id === itemDrop.id);
2093
+ const newElement = { ...cloneDeep(dataDrop), pre_id: itemDrop.id, next_id: dataDrop.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT ? nextCurrentItemDrop.id : '' };
2094
+ if (!dataDrop.id) {
2095
+ newElement.id = `${uuid()}`;
2096
+ }
2097
+ itemDrop.next_id = newElement.id;
2098
+ if (nextCurrentItemDrop) {
2099
+ nextCurrentItemDrop.pre_id = newElement.id;
2100
+ }
2101
+ elements?.splice(indexItemDrop + 1, 0, newElement);
2102
+ const flatElements = this.FlatElements;
2103
+ const afterElement = flatElements?.find((el) => el.pre_id === newElement.id);
2104
+ if (!afterElement) {
2105
+ return;
2106
+ }
2107
+ const beforeElement = flatElements?.filter((el) => el.id !== newElement.id && el.next_id === afterElement.id);
2108
+ beforeElement?.forEach((element) => {
2109
+ element.next_id = newElement.id;
2110
+ });
2111
+ if (dataDrop.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2112
+ return;
2113
+ }
2114
+ this.addElementExitWorkFlow(newElement, branchChoice);
2115
+ }
2116
+ addElementToBranch(itemDrop, branchDrop, dataDrop, numberBranch) {
2117
+ const elements = branchDrop.elements;
2118
+ let nextCurrentItemDrop = undefined;
2119
+ let indexItemDrop = -1;
2120
+ if (elements && elements.length) {
2121
+ indexItemDrop = elements.findIndex((item) => item.id === itemDrop.id);
2122
+ nextCurrentItemDrop = elements.find((item) => item.id === itemDrop.next_id);
2123
+ if (!nextCurrentItemDrop && indexItemDrop === -1) {
2124
+ nextCurrentItemDrop = elements[indexItemDrop + 1];
2125
+ }
2126
+ }
2127
+ let nextId = (nextCurrentItemDrop && nextCurrentItemDrop.id) || itemDrop.next_id;
2128
+ if (dataDrop.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2129
+ nextId = '';
2130
+ }
2131
+ const newElement = { ...dataDrop, pre_id: itemDrop.id, next_id: nextId };
2132
+ if (!dataDrop.id) {
2133
+ newElement.id = `${uuid()}`;
2134
+ }
2135
+ if (!itemDrop.branches || !itemDrop.branches.find((branch) => branch === branchDrop)) {
2136
+ itemDrop.next_id = newElement.id;
2137
+ }
2138
+ if (nextCurrentItemDrop) {
2139
+ nextCurrentItemDrop.pre_id = newElement.id;
2140
+ }
2141
+ elements?.splice(indexItemDrop + 1, 0, newElement);
2142
+ if (itemDrop && itemDrop.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2143
+ this.updateNextIdOfChildElementInElementWorkflow(itemDrop, this.FlatElements?.find((item) => item.id === itemDrop.next_id));
2144
+ }
2145
+ if (dataDrop.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2146
+ this.checkEndMoveElementWhenDropElementCodeExit(branchDrop);
2147
+ }
2148
+ if (dataDrop.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2149
+ return;
2150
+ }
2151
+ this.addElementExitWorkFlow(newElement, numberBranch);
2152
+ }
2153
+ updateNextIdOfChildElementInElementWorkflow(elementWorkflow, nextElement) {
2154
+ if (!elementWorkflow || elementWorkflow.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW || !nextElement) {
2155
+ return;
2156
+ }
2157
+ const flatElement = [];
2158
+ LibsUiDiagramDrawCanvasUtil.flatElementHasBranch(nextElement, flatElement);
2159
+ elementWorkflow.branches?.forEach((branch) => {
2160
+ if (!branch.elements || !branch.elements.length) {
2161
+ return;
2162
+ }
2163
+ const elements = branch.elements;
2164
+ elements.forEach((element, index) => {
2165
+ if (index !== elements.length - 1) {
2166
+ if (element.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW && element.next_id) {
2167
+ return this.updateNextIdOfChildElementInElementWorkflow(element, elements[index + 1]);
2168
+ }
2169
+ return;
2170
+ }
2171
+ if (element.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT || element.id === nextElement.id) {
2172
+ return;
2173
+ }
2174
+ if (element.pre_id === nextElement.id || flatElement.find((item) => item.id === element.id)) {
2175
+ // trường hợp gọi đệ quy NextElement là khối trước hoặc cha của các element hiện tại
2176
+ return;
2177
+ }
2178
+ if (element.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2179
+ if (element.next_id) {
2180
+ element.next_id = nextElement.id;
2181
+ }
2182
+ return this.updateNextIdOfChildElementInElementWorkflow(element, nextElement);
2183
+ }
2184
+ if (elementWorkflow.next_id !== nextElement.id) {
2185
+ return;
2186
+ }
2187
+ element.next_id = nextElement.id;
2188
+ });
2189
+ });
2190
+ }
2191
+ checkEndMoveElementWhenDropElementCodeExit(currentBranch) {
2192
+ const flatElements = this.FlatElements;
2193
+ const elementHasCurrentBranch = flatElements?.find((el) => el.branches && !!el.branches.find((branch) => branch === currentBranch));
2194
+ if (!elementHasCurrentBranch) {
2195
+ return;
2196
+ }
2197
+ const nextElement = flatElements?.find((el) => el.id === elementHasCurrentBranch.next_id);
2198
+ if (!nextElement) {
2199
+ return;
2200
+ }
2201
+ const dataElementHasCurrentBranch = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2202
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(elementHasCurrentBranch, nextElement.id, dataElementHasCurrentBranch, this.FlatElements);
2203
+ if (!dataElementHasCurrentBranch.branchEmpty && dataElementHasCurrentBranch.count === 0) {
2204
+ elementHasCurrentBranch.next_id = '';
2205
+ }
2206
+ const elementNextToId = flatElements?.find((el) => el.next_id === nextElement.id); // Khối cha
2207
+ if (!elementNextToId || elementNextToId.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW || !elementNextToId.next_id) {
2208
+ return;
2209
+ }
2210
+ let dataFistElement = dataElementHasCurrentBranch;
2211
+ if (elementHasCurrentBranch.id !== elementNextToId.id) {
2212
+ dataFistElement = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2213
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(elementNextToId, nextElement.id, dataFistElement, this.FlatElements);
2214
+ }
2215
+ if (dataFistElement.count > 1 || !dataFistElement.branchContainer) {
2216
+ return;
2217
+ }
2218
+ const branchAddItem = dataFistElement.branchContainer;
2219
+ if (elementHasCurrentBranch.id === elementNextToId.id) {
2220
+ if (nextElement.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2221
+ this.moveElementsToBranch(nextElement, branchAddItem);
2222
+ const { element } = LibsUiDiagramDrawCanvasUtil.getElementHasCurrentBranchOrCurrentElement(undefined, elementHasCurrentBranch, this.FlatElements);
2223
+ const data = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2224
+ const elementCount = element && element.next_id ? element : elementHasCurrentBranch;
2225
+ // Fixbug: 15771
2226
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(elementCount, elementCount.next_id, data, this.FlatElements);
2227
+ if (!data.count) {
2228
+ return (elementCount.next_id = '');
2229
+ }
2230
+ return;
2231
+ }
2232
+ elementHasCurrentBranch.next_id = '';
2233
+ const elementHasBranchesNotResetId = this.deleteElement(nextElement);
2234
+ if (!elementHasBranchesNotResetId.length) {
2235
+ return this.cloneElementAndAddElementEXitToBranch(elementHasCurrentBranch, branchAddItem, nextElement);
2236
+ }
2237
+ const { data, element } = elementHasBranchesNotResetId[0];
2238
+ const infoOfElement = LibsUiDiagramDrawCanvasUtil.getElementHasCurrentBranchOrCurrentElement(undefined, element, this.FlatElements);
2239
+ const parentOfElement = infoOfElement.element;
2240
+ let dataOfElementInfo = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2241
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(parentOfElement, parentOfElement?.next_id, dataOfElementInfo, this.FlatElements);
2242
+ if (elementHasBranchesNotResetId.length === 1) {
2243
+ if (data.count > 1) {
2244
+ return this.cloneElementAndAddElementEXitToBranch(element, branchAddItem, nextElement);
2245
+ }
2246
+ if (dataFistElement.branchContainer !== data.branchContainer && dataFistElement.count === 1) {
2247
+ this.cloneElementAndAddElementEXitToBranch(elementHasCurrentBranch, branchAddItem, nextElement);
2248
+ dataOfElementInfo = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2249
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(parentOfElement, parentOfElement?.next_id, dataOfElementInfo, this.FlatElements);
2250
+ }
2251
+ if (dataOfElementInfo.count > 1) {
2252
+ return;
2253
+ }
2254
+ // Fixbug: 15518
2255
+ if (data.branchContainer) {
2256
+ this.cloneElementAndAddElementEXitToBranch(element, data.branchContainer, nextElement);
2257
+ }
2258
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(parentOfElement, parentOfElement?.next_id, LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount(), this.FlatElements);
2259
+ return;
2260
+ }
2261
+ this.cloneElementAndAddElementEXitToBranch(element, branchAddItem, nextElement);
2262
+ const flatElement = this.FlatElements;
2263
+ elementHasBranchesNotResetId.forEach((item) => {
2264
+ const { element } = item;
2265
+ if (element.next_id && flatElement?.find((item) => item.id === element.next_id)) {
2266
+ return;
2267
+ }
2268
+ const infoFromElement = LibsUiDiagramDrawCanvasUtil.getElementHasCurrentBranchOrCurrentElement(undefined, element, flatElement);
2269
+ if (infoFromElement.element) {
2270
+ element.next_id = infoFromElement.element.next_id;
2271
+ this.updateNextIdOfChildElementInElementWorkflow(element, flatElements?.find((item) => item.id === element.next_id)); // FixBug: 15729
2272
+ }
2273
+ });
2274
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(parentOfElement, parentOfElement?.next_id, LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount(), flatElement);
2275
+ return;
2276
+ }
2277
+ if (nextElement.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2278
+ this.moveElementsToBranch(nextElement, branchAddItem);
2279
+ // kiểm tra khối cha chứa các element có branch xem có element nào count = 0 để reset next_id = '' của khối đó
2280
+ const dataOfElementNextToId = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2281
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(elementNextToId, elementNextToId.next_id, dataOfElementNextToId, this.FlatElements);
2282
+ if (!dataOfElementNextToId.count) {
2283
+ elementNextToId.next_id = '';
2284
+ }
2285
+ return;
2286
+ }
2287
+ elementNextToId.next_id = '';
2288
+ elementHasCurrentBranch.next_id = '';
2289
+ const elementHasBranchesNotResetId = this.deleteElement(nextElement);
2290
+ if (dataElementHasCurrentBranch.branchContainer) {
2291
+ this.cloneElementAndAddElementEXitToBranch(elementHasCurrentBranch, dataElementHasCurrentBranch.branchContainer, nextElement);
2292
+ }
2293
+ if (branchAddItem && branchAddItem !== dataElementHasCurrentBranch.branchContainer) {
2294
+ this.cloneElementAndAddElementEXitToBranch(elementNextToId, branchAddItem, nextElement); // dòng này sẽ khởi tạo lại next_id của khối cha
2295
+ }
2296
+ if (!elementHasBranchesNotResetId.length) {
2297
+ return;
2298
+ }
2299
+ // kiểm tra khối cha chứa các element có branch xem có element nào count = 0 để reset next_id = '' của khối đó
2300
+ dataFistElement = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2301
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(elementNextToId, nextElement.id, dataFistElement, this.FlatElements);
2302
+ if (!dataFistElement.count) {
2303
+ // nếu khối cha kiểm tra khối cha chứa các element có branch có count = 0 thì reset next_id = ''
2304
+ elementNextToId.next_id = '';
2305
+ }
2306
+ const newId = elementHasBranchesNotResetId[0].element.next_id;
2307
+ elementHasBranchesNotResetId.forEach((item) => (item.element.next_id = newId));
2308
+ }
2309
+ moveElementsToBranch(elementStart, branchContainer) {
2310
+ if (!elementStart || elementStart.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT || !branchContainer || !branchContainer.elements) {
2311
+ return;
2312
+ }
2313
+ const result = this.findContainerHasElementStart(elementStart, this.Elements);
2314
+ if (!result) {
2315
+ return;
2316
+ }
2317
+ const { container, index } = result;
2318
+ const elementsHasMove = container.slice(Number(index));
2319
+ const elementsOfBranchContainer = branchContainer.elements;
2320
+ const lastElementOfBranchContainer = elementsOfBranchContainer[elementsOfBranchContainer.length - 1];
2321
+ elementsHasMove.forEach((element) => this.deleteElement(element, true));
2322
+ if (lastElementOfBranchContainer && lastElementOfBranchContainer.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2323
+ lastElementOfBranchContainer.next_id = elementStart.id;
2324
+ elementStart.pre_id = lastElementOfBranchContainer.id;
2325
+ }
2326
+ const lastElementMove = elementsHasMove[elementsHasMove.length - 1];
2327
+ let nextElement = elementStart;
2328
+ if (lastElementMove && lastElementMove.next_id) {
2329
+ nextElement = this.FlatElements?.find((item) => item.id === lastElementMove.next_id);
2330
+ }
2331
+ const preElementStart = container[Number(index) - 1];
2332
+ branchContainer.elements.push(...elementsHasMove);
2333
+ if (!preElementStart || preElementStart.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2334
+ return;
2335
+ }
2336
+ if (nextElement) {
2337
+ this.updateNextIdOfChildElementInElementWorkflow(preElementStart, nextElement);
2338
+ }
2339
+ }
2340
+ findContainerHasElementStart(elementStart, containerCapacity) {
2341
+ if (!containerCapacity || !containerCapacity.length || !elementStart) {
2342
+ return undefined;
2343
+ }
2344
+ for (const index in containerCapacity) {
2345
+ const element = containerCapacity[index];
2346
+ if (element.id === elementStart.id) {
2347
+ return {
2348
+ elementStart: elementStart,
2349
+ index: index,
2350
+ container: containerCapacity,
2351
+ };
2352
+ }
2353
+ if (!element.branches || !element.branches.length) {
2354
+ continue;
2355
+ }
2356
+ const branches = element.branches;
2357
+ for (const branch of branches) {
2358
+ const result = this.findContainerHasElementStart(elementStart, branch.elements);
2359
+ if (result) {
2360
+ return result;
2361
+ }
2362
+ }
2363
+ }
2364
+ return undefined;
2365
+ }
2366
+ deleteElement(elementDelete, ignoreUpdatePreId) {
2367
+ if (!elementDelete || !elementDelete.id) {
2368
+ return [];
2369
+ }
2370
+ const elementHasBranchesNotResetId = new Array();
2371
+ const flatElements = this.FlatElements;
2372
+ const elementsHasNextIdToElementDelete = flatElements?.filter((el) => el.next_id === elementDelete.id);
2373
+ const elementHasPreIdToElementDelete = flatElements?.find((el) => el.pre_id === elementDelete.id && el.id === elementDelete.next_id);
2374
+ if (!ignoreUpdatePreId && elementHasPreIdToElementDelete) {
2375
+ elementHasPreIdToElementDelete.pre_id = elementDelete.pre_id;
2376
+ }
2377
+ elementsHasNextIdToElementDelete?.forEach((elementHasNextIdToElementDelete) => {
2378
+ if (elementHasPreIdToElementDelete) {
2379
+ elementHasNextIdToElementDelete.next_id = elementHasPreIdToElementDelete.id;
2380
+ return;
2381
+ }
2382
+ if (elementDelete.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2383
+ if (!elementHasPreIdToElementDelete && elementDelete.next_id) {
2384
+ // Fixbug: 15623
2385
+ elementHasNextIdToElementDelete.next_id = elementDelete.next_id;
2386
+ }
2387
+ return;
2388
+ }
2389
+ if (elementHasNextIdToElementDelete.branches && elementHasNextIdToElementDelete.branches.length) {
2390
+ const data = LibsUiDiagramDrawCanvasUtil.getDefaultStorageBranchEndCount();
2391
+ LibsUiDiagramDrawCanvasUtil.getBranchContainerAndCountElementHasNextIdToElement(elementHasNextIdToElementDelete, elementDelete.id, data, this.FlatElements);
2392
+ if (!data.count) {
2393
+ elementHasNextIdToElementDelete.next_id = '';
2394
+ return;
2395
+ }
2396
+ elementHasBranchesNotResetId.push({ element: elementHasNextIdToElementDelete, data });
2397
+ return;
2398
+ }
2399
+ elementHasNextIdToElementDelete.next_id = elementDelete.next_id;
2400
+ });
2401
+ LibsUiDiagramDrawCanvasUtil.findAndDeleteElementFromContainer(this.Elements, elementDelete);
2402
+ return elementHasBranchesNotResetId;
2403
+ }
2404
+ cloneElementAndAddElementEXitToBranch(elementHasCurrentBranch, branch, dataDrop) {
2405
+ if (!dataDrop || !elementHasCurrentBranch || !branch) {
2406
+ return;
2407
+ }
2408
+ let elementDrop = elementHasCurrentBranch;
2409
+ if (branch.elements && branch.elements.length) {
2410
+ elementDrop = branch.elements[branch.elements.length - 1];
2411
+ }
2412
+ return this.addElementToBranch(elementDrop, branch, cloneDeep(dataDrop));
2413
+ }
2414
+ addElementExitWorkFlow(newElement, numberBranch) {
2415
+ const exit = {
2416
+ element_type: canvasConfigReadonly().TYPE_ELEMENT_EXIT,
2417
+ code: canvasConfigReadonly().TYPE_ELEMENT_EXIT,
2418
+ name: this.translateService.instant('i18n_end_journey_new'),
2419
+ specific_width: canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT,
2420
+ specific_height: canvasConfigReadonly().HEIGHT_ELEMENT_DEFAULT,
2421
+ specific_x: 0,
2422
+ specific_y: 0,
2423
+ };
2424
+ if (newElement.branches && newElement.branches.length) {
2425
+ newElement.branches.forEach((item, index) => {
2426
+ if (index === numberBranch) {
2427
+ return;
2428
+ }
2429
+ if (!newElement.branches) {
2430
+ return;
2431
+ }
2432
+ this.addElement(newElement, item, exit);
2433
+ });
2434
+ }
2435
+ }
2436
+ addElementExitWhenDeleteDirectionLine(element, branch) {
2437
+ const exit = {
2438
+ element_type: canvasConfigReadonly().TYPE_ELEMENT_EXIT,
2439
+ code: canvasConfigReadonly().TYPE_ELEMENT_EXIT,
2440
+ name: this.translateService.instant('i18n_end_journey_new'),
2441
+ specific_width: canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT,
2442
+ specific_height: canvasConfigReadonly().HEIGHT_ELEMENT_DEFAULT,
2443
+ specific_x: 0,
2444
+ specific_y: 0,
2445
+ };
2446
+ delete element.nodeOtherConfig;
2447
+ this.addElement(element, branch, exit);
2448
+ }
2449
+ /**
2450
+ * Khi một element bị xoá, hàm này sẽ tự động thêm exit cho tất cả các element phía trước đang trỏ tới element đó.
2451
+ */
2452
+ addElementExitForAllElementPreviousWhenElementDelete(elementsDelete) {
2453
+ const flatElement = this.FlatElements;
2454
+ const flatElementsInElementDelete = [];
2455
+ LibsUiDiagramDrawCanvasUtil.buildFlatElement(elementsDelete, flatElementsInElementDelete);
2456
+ flatElementsInElementDelete.forEach((elementDel) => {
2457
+ if (elementDel.pre_other_id && elementDel.pre_other_id.length) {
2458
+ elementDel.pre_other_id.forEach((item) => {
2459
+ const element = flatElement?.find((el) => el.id === item);
2460
+ if (element?.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW && element.branches?.length) {
2461
+ element.branches.forEach((branch) => {
2462
+ if (!branch.next_other_id || !branch.next_other_id.length) {
2463
+ return;
2464
+ }
2465
+ const indexEl = branch.next_other_id.findIndex((el) => el === elementDel.id);
2466
+ if (indexEl !== -1) {
2467
+ branch.next_other_id.splice(indexEl, 1);
2468
+ }
2469
+ if (!branch.next_other_id.length) {
2470
+ this.addElementExitWhenDeleteDirectionLine(element, branch);
2471
+ element.subCodeOfElement = undefined;
2472
+ element.attributeSvgD = undefined;
2473
+ element.position_end = undefined;
2474
+ branch.nodeOtherConfig = undefined;
2475
+ }
2476
+ });
2477
+ return;
2478
+ }
2479
+ if (!element || !element.next_other_id) {
2480
+ return;
2481
+ }
2482
+ const indexEl = element.next_other_id.findIndex((el) => el === elementDel.id);
2483
+ if (indexEl !== -1) {
2484
+ element.next_other_id.splice(indexEl, 1);
2485
+ }
2486
+ if (!element.next_other_id.length) {
2487
+ if (element.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2488
+ const getConfigDefaultBranches = this.handlerFunction?.getConfigDefaultBranches ?? getDefaultConfigConstituentBranches;
2489
+ getConfigDefaultBranches(element.code ?? '', element);
2490
+ }
2491
+ const branch = LibsUiDiagramDrawCanvasUtil.getBranchOfElement(element.id ?? '', this.FlatElements, canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW);
2492
+ this.addElementExitWhenDeleteDirectionLine(element, branch);
2493
+ element.subCodeOfElement = undefined;
2494
+ element.attributeSvgD = undefined;
2495
+ element.position_end = undefined;
2496
+ }
2497
+ });
2498
+ return;
2499
+ }
2500
+ if (elementDel.next_other_id && elementDel.next_other_id.length) {
2501
+ elementDel.next_other_id.forEach((item) => {
2502
+ const element = flatElement?.find((el) => el.id === item);
2503
+ if (!element || !element.pre_other_id) {
2504
+ return;
2505
+ }
2506
+ const indexEl = element.pre_other_id.findIndex((el) => el === elementDel.id);
2507
+ if (indexEl !== -1) {
2508
+ element.pre_other_id.splice(indexEl, 1);
2509
+ }
2510
+ });
2511
+ }
2512
+ if (elementDel.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW && elementDel.branches?.length) {
2513
+ elementDel.branches.forEach((branch) => {
2514
+ if (!branch.next_other_id || !branch.next_other_id.length) {
2515
+ return;
2516
+ }
2517
+ branch.next_other_id.forEach((item) => {
2518
+ const element = flatElement?.find((el) => el.id === item);
2519
+ if (!element || !element.pre_other_id) {
2520
+ return;
2521
+ }
2522
+ const indexEl = element.pre_other_id.findIndex((el) => el === elementDel.id);
2523
+ if (indexEl !== -1) {
2524
+ element.pre_other_id.splice(indexEl, 1);
2525
+ }
2526
+ });
2527
+ });
2528
+ }
2529
+ });
2530
+ }
2531
+ async removeOrChangeElement(elementRemove, elementChange, branchChoose) {
2532
+ if (!elementRemove || elementRemove.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2533
+ return;
2534
+ }
2535
+ const flatElements = this.FlatElements;
2536
+ const preElement = flatElements?.find((element) => element.id === elementRemove.pre_id);
2537
+ const infoOfElementRemove = LibsUiDiagramDrawCanvasUtil.getElementHasCurrentBranchOrCurrentElement(undefined, elementRemove, this.FlatElements);
2538
+ const exit = {
2539
+ element_type: canvasConfigReadonly().TYPE_ELEMENT_EXIT,
2540
+ code: canvasConfigReadonly().TYPE_ELEMENT_EXIT,
2541
+ name: this.translateService.instant('i18n_out_journey'),
2542
+ id: `${uuid()}`,
2543
+ specific_width: canvasConfigReadonly().WIDTH_ELEMENT_DEFAULT,
2544
+ specific_height: canvasConfigReadonly().HEIGHT_ELEMENT_DEFAULT,
2545
+ specific_x: 0,
2546
+ specific_y: 0,
2547
+ };
2548
+ const elementNextBeforeDelete = flatElements?.find((el) => el.pre_id === elementRemove.id && el.id === elementRemove.next_id);
2549
+ if (elementNextBeforeDelete && elementNextBeforeDelete.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2550
+ if (preElement && preElement.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW && infoOfElementRemove.currentBranch) {
2551
+ infoOfElementRemove.currentBranch.nodeOtherConfig = undefined;
2552
+ }
2553
+ if (preElement && preElement.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2554
+ preElement.nodeOtherConfig = undefined;
2555
+ }
2556
+ }
2557
+ if (preElement && !elementNextBeforeDelete) {
2558
+ preElement.nodeOtherConfig = undefined;
2559
+ if (preElement && preElement.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW && infoOfElementRemove.currentBranch) {
2560
+ infoOfElementRemove.currentBranch.nodeOtherConfig = undefined;
2561
+ }
2562
+ }
2563
+ this.deleteElement(elementRemove);
2564
+ // xóa thẳng
2565
+ if (!elementChange) {
2566
+ if (!elementNextBeforeDelete) {
2567
+ this.addElement(preElement, infoOfElementRemove.currentBranch, exit);
2568
+ }
2569
+ return;
2570
+ }
2571
+ // thay đơn == đơn, thay đơn = nhánh
2572
+ if (elementRemove.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2573
+ if (!elementRemove.next_id && elementChange.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2574
+ // 48229
2575
+ this.addElement(preElement, infoOfElementRemove.currentBranch, exit);
2576
+ }
2577
+ this.addElement(preElement, infoOfElementRemove.currentBranch, elementChange, branchChoose);
2578
+ if (elementChange.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW && elementNextBeforeDelete && elementNextBeforeDelete.code === canvasConfigReadonly().TYPE_ELEMENT_EXIT) {
2579
+ this.deleteElement(elementNextBeforeDelete);
2580
+ }
2581
+ return;
2582
+ }
2583
+ // thay nhánh = đơn
2584
+ if (elementChange.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2585
+ this.addElement(preElement, infoOfElementRemove.currentBranch, exit);
2586
+ this.addElement(preElement, infoOfElementRemove.currentBranch, elementChange, branchChoose);
2587
+ return;
2588
+ }
2589
+ // thay nhánh = nhánh
2590
+ this.addElement(preElement, infoOfElementRemove.currentBranch, exit);
2591
+ this.addElement(preElement, infoOfElementRemove.currentBranch, elementChange, branchChoose);
2592
+ this.deleteElement(exit);
2593
+ }
2594
+ async deleteChangeElementAndInsertBranchKeep(elementRemove, elementChange, branchKeepElement, branchChoice) {
2595
+ const flatElements = this.FlatElements;
2596
+ const preElement = flatElements?.find((element) => element.id === elementRemove.pre_id);
2597
+ const infoOfElementRemove = LibsUiDiagramDrawCanvasUtil.getElementHasCurrentBranchOrCurrentElement(undefined, elementRemove, this.FlatElements);
2598
+ this.addBranchKeepToFlow(branchKeepElement, preElement, infoOfElementRemove.currentBranch);
2599
+ this.deleteElement(elementRemove);
2600
+ if (elementChange) {
2601
+ this.addElement(preElement, infoOfElementRemove.currentBranch, elementChange, branchChoice);
2602
+ }
2603
+ }
2604
+ addBranchKeepToFlow(branchKeepElement, itemDrop, branchDrop) {
2605
+ if (!branchKeepElement) {
2606
+ return;
2607
+ }
2608
+ const elements = branchKeepElement.elements;
2609
+ const nextCurrentItemDrop = elements[0];
2610
+ if (nextCurrentItemDrop) {
2611
+ if (itemDrop.element_type !== canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2612
+ itemDrop.next_id = nextCurrentItemDrop && nextCurrentItemDrop.id;
2613
+ }
2614
+ nextCurrentItemDrop.pre_id = itemDrop.id;
2615
+ }
2616
+ if (branchDrop) {
2617
+ branchDrop.elements.push(...elements);
2618
+ return;
2619
+ }
2620
+ this.Elements.push(...elements);
2621
+ }
2622
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawCanvasService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2623
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawCanvasService, providedIn: 'root' });
2624
+ }
2625
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawCanvasService, decorators: [{
2626
+ type: Injectable,
2627
+ args: [{ providedIn: 'root' }]
2628
+ }] });
2629
+
2630
+ const buildElementDirectionTo = (element, flatElements, checkRule, branch) => {
2631
+ let elementsFlat = cloneDeep(flatElements) ?? [];
2632
+ if (element.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2633
+ if (!branch) {
2634
+ return undefined;
2635
+ }
2636
+ getAllItemPreOfElement(element, elementsFlat);
2637
+ getAllItemNextOfBranch(branch, elementsFlat);
2638
+ elementsFlat = elementsFlat.filter((item) => item.id !== element.id && item.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT);
2639
+ if (element.pre_other_id?.length) {
2640
+ elementsFlat = elementsFlat.filter((item) => !element.pre_other_id?.includes(item.id ?? ''));
2641
+ }
2642
+ elementsFlat = elementsFlat.filter((item) => {
2643
+ return checkRule(element, item);
2644
+ });
2645
+ return elementsFlat;
2646
+ }
2647
+ getAllItemPreOfElement(element, elementsFlat);
2648
+ getAllItemNextOfElement(element, elementsFlat);
2649
+ elementsFlat = elementsFlat.filter((item) => item.id !== element.id && item.code !== canvasConfigReadonly().TYPE_ELEMENT_EXIT);
2650
+ if (element.pre_other_id?.length) {
2651
+ elementsFlat = elementsFlat.filter((item) => !element.pre_other_id?.includes(item.id ?? ''));
2652
+ }
2653
+ elementsFlat = elementsFlat.filter((item) => {
2654
+ return checkRule(element, item);
2655
+ });
2656
+ return elementsFlat;
2657
+ };
2658
+ const getAllItemPreOfElement = (element, flatElements) => {
2659
+ const pre_id = [];
2660
+ if (element.pre_id) {
2661
+ pre_id.push(element.pre_id);
2662
+ }
2663
+ if (element.pre_other_id) {
2664
+ pre_id.push(...element.pre_other_id);
2665
+ }
2666
+ if (!pre_id.length) {
2667
+ return;
2668
+ }
2669
+ pre_id.forEach((preId) => {
2670
+ const elementPre = flatElements.find((item) => item.id === preId);
2671
+ if (!elementPre) {
2672
+ return;
2673
+ }
2674
+ const itemIndex = flatElements.findIndex((item) => item.id === preId);
2675
+ flatElements.splice(itemIndex, 1);
2676
+ getAllItemPreOfElement(elementPre, flatElements);
2677
+ });
2678
+ };
2679
+ const getAllItemNextOfElement = (element, flatElements) => {
2680
+ if (!element.next_id) {
2681
+ if (!element.branches || !element.branches.length) {
2682
+ return;
2683
+ }
2684
+ const flatElementInElementCurrent = [];
2685
+ buildFlatElement([element], flatElementInElementCurrent);
2686
+ flatElementInElementCurrent.forEach((el) => {
2687
+ const itemIndex = flatElements.findIndex((item) => item.id === el.id);
2688
+ if (itemIndex === -1) {
2689
+ return;
2690
+ }
2691
+ flatElements.splice(itemIndex, 1);
2692
+ });
2693
+ return;
2694
+ }
2695
+ const elementNextIndex = flatElements.findIndex((item) => item.id === element.next_id);
2696
+ if (elementNextIndex === -1) {
2697
+ return;
2698
+ }
2699
+ const nextElement = flatElements[elementNextIndex];
2700
+ flatElements.splice(elementNextIndex, 1);
2701
+ getAllItemNextOfElement(nextElement, flatElements);
2702
+ };
2703
+ const getAllItemNextOfBranch = (branch, flatElements) => {
2704
+ const flatElementInElementCurrent = [];
2705
+ buildFlatElement(branch.elements, flatElementInElementCurrent);
2706
+ flatElementInElementCurrent.forEach((el) => {
2707
+ const itemIndex = flatElements.findIndex((item) => item.id === el.id);
2708
+ if (itemIndex === -1) {
2709
+ return;
2710
+ }
2711
+ flatElements.splice(itemIndex, 1);
2712
+ });
2713
+ };
2714
+ const collectNextChainElements = (element, flatContainerElement, listElementDelete) => {
2715
+ const elementNext = flatContainerElement.find((item) => item.id === element.next_id);
2716
+ if (!elementNext) {
2717
+ return;
2718
+ }
2719
+ const flatElementInElementCurrent = [];
2720
+ buildFlatElement([elementNext], flatElementInElementCurrent);
2721
+ flatElementInElementCurrent.forEach((el) => {
2722
+ const itemInNext = flatContainerElement.find((item) => item.id === el.id);
2723
+ if (itemInNext) {
2724
+ listElementDelete.push(itemInNext);
2725
+ }
2726
+ });
2727
+ collectNextChainElements(elementNext, flatContainerElement, listElementDelete);
2728
+ };
2729
+
2730
+ class LibsUiDiagramDrawDirectionService {
2731
+ elementDirection;
2732
+ branchDirection;
2733
+ elementViewDirectionId;
2734
+ onViewDirection = new Subject();
2735
+ canvasService = inject(LibsUiDiagramDrawCanvasService);
2736
+ set ElementDirection(element) {
2737
+ this.elementDirection = element;
2738
+ }
2739
+ get ElementDirection() {
2740
+ return this.elementDirection;
2741
+ }
2742
+ set BranchDirection(branch) {
2743
+ this.branchDirection = branch;
2744
+ }
2745
+ get BranchDirection() {
2746
+ return this.branchDirection;
2747
+ }
2748
+ set ElementViewDirectionId(id) {
2749
+ this.elementViewDirectionId = id;
2750
+ }
2751
+ get ElementViewDirectionId() {
2752
+ return this.elementViewDirectionId;
2753
+ }
2754
+ get OnViewDirection() {
2755
+ return this.onViewDirection;
2756
+ }
2757
+ checkRuleDirectionToElement(element, elementConnectTo) {
2758
+ const ruleAfterItemDrag = this.canvasService.RuleConnectableElements.find((item) => elementConnectTo && item.elementCode === elementConnectTo.code);
2759
+ if (!ruleAfterItemDrag) {
2760
+ return true;
2761
+ }
2762
+ if (ruleAfterItemDrag.elementCodeConnectableBefore.find((code) => code === element?.code)) {
2763
+ return true;
2764
+ }
2765
+ return false;
2766
+ }
2767
+ buildElementConnectTo(element, flatElements, branch, checkRule) {
2768
+ const checkRuleDirectionToElement = checkRule ?? this.checkRuleDirectionToElement.bind(this);
2769
+ return buildElementDirectionTo(element, flatElements, checkRuleDirectionToElement, branch);
2770
+ }
2771
+ /**Xóa các element nằm phía sau khi nhánh được điều hướng */
2772
+ deleteElementsBehindBranchConnectTo(branch) {
2773
+ const flatElements = this.canvasService.FlatElements ?? [];
2774
+ const flatElementInElementCurrent = [];
2775
+ const listElementDelete = [];
2776
+ buildFlatElement(branch.elements, flatElementInElementCurrent);
2777
+ listElementDelete.push(...flatElementInElementCurrent);
2778
+ this.deleteLineConnectFromElement(listElementDelete, flatElements);
2779
+ listElementDelete.forEach((elementDelete) => {
2780
+ findAndDeleteElementFromContainer(this.canvasService.Elements, elementDelete);
2781
+ });
2782
+ }
2783
+ /**xóa line đến và xóa line đi ở toàn bộ các khối và nhánh bị xóa */
2784
+ deleteLineConnectFromElement(elements, flatElements) {
2785
+ elements.forEach((item) => {
2786
+ if (item.branches && item.branches.length) {
2787
+ item.branches.forEach((branch) => {
2788
+ if (branch.next_other_id && branch.next_other_id.length) {
2789
+ this.deletePreOtherId(item.id ?? '', branch.next_other_id, flatElements);
2790
+ }
2791
+ });
2792
+ }
2793
+ if (item.next_other_id && item.next_other_id.length) {
2794
+ this.deletePreOtherId(item.id ?? '', item.next_other_id, flatElements);
2795
+ }
2796
+ if (item.pre_other_id && item.pre_other_id.length) {
2797
+ // nhánh chỉ lưu id next đi thôi chứ không lưu được pre => chỉ cần xóa pre trong khối.
2798
+ this.deleteNextOtherId(item.id ?? '', item.pre_other_id, flatElements);
2799
+ }
2800
+ });
2801
+ }
2802
+ /** Xóa pre_other_id từ khác khối mà khối bị xóa nối đến BlockDelete => Other*/
2803
+ deletePreOtherId(itemId, nextOtherId, flatElements) {
2804
+ nextOtherId.forEach((nextToId) => {
2805
+ const nextElement = flatElements.find((item) => item.id === nextToId);
2806
+ if (!nextElement || !nextElement.pre_other_id || !nextElement.pre_other_id.length) {
2807
+ return;
2808
+ }
2809
+ const indexIdPre = nextElement.pre_other_id.findIndex((id) => id === itemId);
2810
+ if (indexIdPre !== -1) {
2811
+ nextElement.pre_other_id.splice(indexIdPre, 1);
2812
+ }
2813
+ });
2814
+ }
2815
+ /** Xóa next_other_id từ khác khối nối đến khối bị xóa Other => BlockDelete và kiểm tra + thêm khối thoát cho những khối nối tới BlockDelete*/
2816
+ deleteNextOtherId(itemId, preOtherId, flatElements) {
2817
+ preOtherId.forEach((nextToId) => {
2818
+ const preElement = flatElements.find((item) => item.id === nextToId);
2819
+ if (!preElement) {
2820
+ return;
2821
+ }
2822
+ if (preElement.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2823
+ preElement.branches?.forEach((branch) => {
2824
+ if (!branch.next_other_id || !branch.next_other_id.length) {
2825
+ return;
2826
+ }
2827
+ const indexIdNextInBranch = branch.next_other_id.findIndex((id) => id === itemId);
2828
+ if (indexIdNextInBranch !== -1) {
2829
+ branch.next_other_id.splice(indexIdNextInBranch, 1);
2830
+ }
2831
+ if (!branch.next_other_id || !branch.next_other_id.length) {
2832
+ branch.nodeOtherConfig = undefined;
2833
+ this.canvasService.addElementExitWhenDeleteDirectionLine(preElement, branch);
2834
+ }
2835
+ });
2836
+ return;
2837
+ }
2838
+ if (!preElement.next_other_id || !preElement.next_other_id.length) {
2839
+ return;
2840
+ }
2841
+ const indexIdNext = preElement.next_other_id.findIndex((id) => id === itemId);
2842
+ if (indexIdNext !== -1) {
2843
+ preElement.next_other_id.splice(indexIdNext, 1);
2844
+ }
2845
+ if (!preElement.next_other_id || !preElement.next_other_id.length) {
2846
+ // thêm khối thoát vì không còn khối nào connect tới nó
2847
+ if (preElement.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW) {
2848
+ const getConfigDefaultBranches = this.canvasService.HandlerFunction?.getConfigDefaultBranches ?? getDefaultConfigConstituentBranches;
2849
+ getConfigDefaultBranches(preElement.code ?? '', preElement);
2850
+ }
2851
+ const branch = LibsUiDiagramDrawCanvasUtil.getBranchOfElement(preElement.id ?? '', flatElements, canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW);
2852
+ this.canvasService.addElementExitWhenDeleteDirectionLine(preElement, branch);
2853
+ preElement.subCodeOfElement = undefined;
2854
+ preElement.attributeSvgD = undefined;
2855
+ preElement.position_end = undefined;
2856
+ }
2857
+ });
2858
+ }
2859
+ addBranchDirection(targetId, element, branch, flatElements) {
2860
+ if (!branch.next_other_id) {
2861
+ branch.next_other_id = [];
2862
+ }
2863
+ branch.next_other_id.push(targetId);
2864
+ const elementTo = flatElements.find((item) => item.id === targetId);
2865
+ if (elementTo) {
2866
+ if (!elementTo.pre_other_id) {
2867
+ elementTo.pre_other_id = [];
2868
+ }
2869
+ elementTo.pre_other_id.push(element.id ?? '');
2870
+ }
2871
+ }
2872
+ addElementDirection(targetId, element, flatElements) {
2873
+ if (!element.next_other_id) {
2874
+ element.next_other_id = [];
2875
+ }
2876
+ element.next_other_id.push(targetId);
2877
+ const elementTo = flatElements.find((item) => item.id === targetId);
2878
+ if (elementTo) {
2879
+ if (!elementTo.pre_other_id) {
2880
+ elementTo.pre_other_id = [];
2881
+ }
2882
+ elementTo.pre_other_id.push(element.id ?? '');
2883
+ }
2884
+ }
2885
+ deleteElementsBehindElementConnectTo(element, flatElements) {
2886
+ const listElementDelete = [];
2887
+ let flatElementInElementCurrent = [];
2888
+ if (element.branches && element.branches.length) {
2889
+ buildFlatElement([element], flatElementInElementCurrent);
2890
+ flatElementInElementCurrent = flatElementInElementCurrent.filter((item) => item.id !== element.id);
2891
+ }
2892
+ delete element.branches;
2893
+ listElementDelete.push(...flatElementInElementCurrent);
2894
+ collectNextChainElements(element, flatElements, listElementDelete);
2895
+ this.deleteLineConnectFromElement(listElementDelete, flatElements);
2896
+ listElementDelete.forEach((elementDelete) => {
2897
+ this.findAndDeleteElementFromContainer(this.canvasService.Elements, elementDelete);
2898
+ });
2899
+ }
2900
+ findAndDeleteElementFromContainer(containerElement, elementDelete) {
2901
+ for (const index in containerElement) {
2902
+ const elementOfIndex = containerElement[index];
2903
+ if (elementOfIndex.id === elementDelete.id) {
2904
+ containerElement.splice(+index, 1);
2905
+ return true;
2906
+ }
2907
+ if (!elementOfIndex.branches || !elementOfIndex.branches.length) {
2908
+ continue;
2909
+ }
2910
+ for (const branch of elementOfIndex.branches) {
2911
+ if (!branch.elements || !branch.elements.length) {
2912
+ continue;
2913
+ }
2914
+ if (this.findAndDeleteElementFromContainer(branch.elements, elementDelete)) {
2915
+ return true;
2916
+ }
2917
+ }
2918
+ }
2919
+ return false;
2920
+ }
2921
+ editDirection(flatElements, targetId, element, branch) {
2922
+ if (element.element_type === canvasConfigReadonly().TYPE_ELEMENT_WORKFLOW && branch) {
2923
+ this.editDirectionWithBranch(flatElements, branch, targetId, element);
2924
+ return;
2925
+ }
2926
+ const idOldNextTo = element.next_other_id?.[0];
2927
+ const elementOldTo = flatElements?.find((item) => item.id === idOldNextTo);
2928
+ // xóa line cũ
2929
+ if (elementOldTo && elementOldTo.pre_other_id) {
2930
+ const itemIndex = elementOldTo.pre_other_id?.findIndex((id) => id === element.id);
2931
+ if (itemIndex !== -1) {
2932
+ elementOldTo.pre_other_id?.splice(itemIndex, 1);
2933
+ }
2934
+ }
2935
+ element.attributeSvgD = undefined;
2936
+ element.position_end = undefined;
2937
+ // update line mới
2938
+ const elementNewTo = flatElements?.find((item) => item.id === targetId);
2939
+ if (elementNewTo) {
2940
+ if (!elementNewTo.pre_other_id) {
2941
+ elementNewTo.pre_other_id = [];
2942
+ }
2943
+ elementNewTo.pre_other_id.push(element.id ?? '');
2944
+ }
2945
+ element.next_other_id = [];
2946
+ element.next_other_id.push(targetId);
2947
+ }
2948
+ editDirectionWithBranch(flatElements, branch, targetId, element) {
2949
+ const idOldNextTo = branch.next_other_id?.[0];
2950
+ const elementOldTo = flatElements?.find((item) => item.id === idOldNextTo);
2951
+ // xóa line cũ
2952
+ if (elementOldTo && elementOldTo.pre_other_id) {
2953
+ const itemIndex = elementOldTo.pre_other_id?.findIndex((id) => id === element.id);
2954
+ if (itemIndex !== -1) {
2955
+ elementOldTo.pre_other_id?.splice(itemIndex, 1);
2956
+ }
2957
+ }
2958
+ // update line mới
2959
+ const elementNewTo = flatElements?.find((item) => item.id === targetId);
2960
+ if (elementNewTo) {
2961
+ if (!elementNewTo.pre_other_id) {
2962
+ elementNewTo.pre_other_id = [];
2963
+ }
2964
+ elementNewTo.pre_other_id.push(element.id ?? '');
2965
+ }
2966
+ branch.next_other_id = [];
2967
+ branch.next_other_id.push(targetId);
2968
+ }
2969
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawDirectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2970
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawDirectionService, providedIn: 'root' });
2971
+ }
2972
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiDiagramDrawDirectionService, decorators: [{
2973
+ type: Injectable,
2974
+ args: [{ providedIn: 'root' }]
2975
+ }] });
2976
+
2977
+ /**
2978
+ * Generated bundle index. Do not edit.
2979
+ */
2980
+
2981
+ export { LibsUiDiagramDrawCanvasService, LibsUiDiagramDrawDirectionService, LibsUiDiagramDrawService, canvasConfigReadonly, storeDataDefault };
2982
+ //# sourceMappingURL=libs-ui-services-diagram-draw.mjs.map