@flowgram-vue/free-snap-plugin 0.2.0

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1036 @@
1
+ 'use strict';
2
+
3
+ var core = require('@flowgram-vue/core');
4
+ var inversify = require('inversify');
5
+ var utils = require('@flowgram-vue/utils');
6
+ var freeLayoutCore = require('@flowgram-vue/free-layout-core');
7
+ var document = require('@flowgram-vue/document');
8
+ var vue = require('vue');
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __decorateClass = (decorators, target, key, kind) => {
13
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
14
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
15
+ if (decorator = decorators[i])
16
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
17
+ if (kind && result) __defProp(target, key, result);
18
+ return result;
19
+ };
20
+
21
+ // src/constant.ts
22
+ var SnapDefaultOptions = {
23
+ enableEdgeSnapping: true,
24
+ edgeThreshold: 7,
25
+ enableGridSnapping: false,
26
+ gridSize: 20,
27
+ enableMultiSnapping: true,
28
+ enableOnlyViewportSnapping: true,
29
+ edgeColor: "#4E40E5",
30
+ alignColor: "#4E40E5",
31
+ edgeLineWidth: 2,
32
+ alignLineWidth: 2,
33
+ alignCrossWidth: 16
34
+ };
35
+ var Epsilon = 1e-5;
36
+
37
+ // src/utils.ts
38
+ var isEqual = (a, b) => {
39
+ if (a === void 0 || b === void 0) {
40
+ return false;
41
+ }
42
+ return Math.abs(a - b) < Epsilon;
43
+ };
44
+ var isLessThan = (a, b) => {
45
+ if (a === void 0 || b === void 0) {
46
+ return false;
47
+ }
48
+ return b - a > Epsilon;
49
+ };
50
+ var isGreaterThan = (a, b) => {
51
+ if (a === void 0 || b === void 0) {
52
+ return false;
53
+ }
54
+ return a - b > Epsilon;
55
+ };
56
+ var isLessThanOrEqual = (a, b) => isEqual(a, b) || isLessThan(a, b);
57
+ var isNumber = (value) => typeof value === "number" && !isNaN(value);
58
+
59
+ // src/service.ts
60
+ exports.WorkflowSnapService = class WorkflowSnapService {
61
+ constructor() {
62
+ this.disposers = [];
63
+ this.snapEmitter = new utils.Emitter();
64
+ this.onSnap = this.snapEmitter.event;
65
+ this._disabled = false;
66
+ }
67
+ init(params = {}) {
68
+ this.options = {
69
+ ...SnapDefaultOptions,
70
+ ...params
71
+ };
72
+ this.mountListener();
73
+ }
74
+ dispose() {
75
+ this.disposers.forEach((disposer) => disposer.dispose());
76
+ }
77
+ get disabled() {
78
+ return this._disabled;
79
+ }
80
+ disable() {
81
+ if (this._disabled) {
82
+ return;
83
+ }
84
+ this._disabled = true;
85
+ this.clear();
86
+ }
87
+ enable() {
88
+ if (!this._disabled) {
89
+ return;
90
+ }
91
+ this._disabled = false;
92
+ this.clear();
93
+ }
94
+ mountListener() {
95
+ const dragAdjusterDisposer = this.dragService.registerPosAdjuster((params) => {
96
+ const { selectedNodes: targetNodes, position } = params;
97
+ const isMultiSnapping = this.options.enableMultiSnapping ? false : targetNodes.length !== 1;
98
+ if (this._disabled || !this.options.enableEdgeSnapping || isMultiSnapping) {
99
+ return {
100
+ x: 0,
101
+ y: 0
102
+ };
103
+ }
104
+ return this.snapping({
105
+ targetNodes,
106
+ position
107
+ });
108
+ });
109
+ const dragEndDisposer = this.dragService.onNodesDrag((event) => {
110
+ if (event.type !== "onDragEnd" || this._disabled) {
111
+ return;
112
+ }
113
+ if (this.options.enableGridSnapping) {
114
+ this.gridSnapping({
115
+ targetNodes: event.nodes,
116
+ gridSize: this.options.gridSize
117
+ });
118
+ }
119
+ if (this.options.enableEdgeSnapping) {
120
+ this.clear();
121
+ }
122
+ });
123
+ this.disposers.push(dragAdjusterDisposer, dragEndDisposer);
124
+ }
125
+ snapping(params) {
126
+ const { targetNodes, position } = params;
127
+ const targetBounds = this.getBounds(targetNodes);
128
+ const targetRect = new utils.Rectangle(
129
+ position.x,
130
+ position.y,
131
+ targetBounds.width,
132
+ targetBounds.height
133
+ );
134
+ const snapNodeRects = this.getSnapNodeRects({
135
+ targetNodes,
136
+ targetRect
137
+ });
138
+ const { alignOffset, alignRects, alignSpacing } = this.calcAlignOffset({
139
+ targetRect,
140
+ alignThreshold: this.options.edgeThreshold,
141
+ snapNodeRects
142
+ });
143
+ const { snapOffset, snapEdgeLines } = this.calcSnapOffset({
144
+ targetRect,
145
+ edgeThreshold: this.options.edgeThreshold,
146
+ snapNodeRects
147
+ });
148
+ const offset = {
149
+ x: snapOffset.x || alignOffset.x,
150
+ y: snapOffset.y || alignOffset.y
151
+ };
152
+ const snapRect = new utils.Rectangle(
153
+ position.x + offset.x,
154
+ position.y + offset.y,
155
+ targetRect.width,
156
+ targetRect.height
157
+ );
158
+ this.snapEmitter.fire({
159
+ snapRect,
160
+ snapEdgeLines,
161
+ alignRects,
162
+ alignSpacing
163
+ });
164
+ return offset;
165
+ }
166
+ calcSnapOffset(params) {
167
+ const { snapNodeRects, edgeThreshold, targetRect } = params;
168
+ const snapLines = this.getSnapLines({
169
+ snapNodeRects
170
+ });
171
+ const topYClosestLine = snapLines.horizontal.find(
172
+ (line) => isLessThanOrEqual(Math.abs(line.y - targetRect.top), edgeThreshold)
173
+ );
174
+ const bottomYClosestLine = snapLines.horizontal.find(
175
+ (line) => isLessThanOrEqual(Math.abs(line.y - targetRect.bottom), edgeThreshold)
176
+ );
177
+ const leftXClosestLine = snapLines.vertical.find(
178
+ (line) => isLessThanOrEqual(Math.abs(line.x - targetRect.left), edgeThreshold)
179
+ );
180
+ const rightXClosestLine = snapLines.vertical.find(
181
+ (line) => isLessThanOrEqual(Math.abs(line.x - targetRect.right), edgeThreshold)
182
+ );
183
+ const midYClosestLine = snapLines.midHorizontal.find(
184
+ (line) => isLessThanOrEqual(Math.abs(line.y - targetRect.center.y), edgeThreshold)
185
+ );
186
+ const midXClosestLine = snapLines.midVertical.find(
187
+ (line) => isLessThanOrEqual(Math.abs(line.x - targetRect.center.x), edgeThreshold)
188
+ );
189
+ const topYClosest = topYClosestLine?.y;
190
+ const bottomYClosest = isNumber(bottomYClosestLine?.y) ? bottomYClosestLine.y - targetRect.height : void 0;
191
+ const leftXClosest = leftXClosestLine?.x;
192
+ const rightXClosest = isNumber(rightXClosestLine?.x) ? rightXClosestLine.x - targetRect.width : void 0;
193
+ const midYClosest = isNumber(midYClosestLine?.y) ? midYClosestLine.y - targetRect.height / 2 : void 0;
194
+ const midXClosest = isNumber(midXClosestLine?.x) ? midXClosestLine.x - targetRect.width / 2 : void 0;
195
+ const snappingPosition = {
196
+ x: midXClosest ?? leftXClosest ?? rightXClosest ?? targetRect.x,
197
+ y: midYClosest ?? topYClosest ?? bottomYClosest ?? targetRect.y
198
+ };
199
+ const snapOffset = {
200
+ x: snappingPosition.x - targetRect.x,
201
+ y: snappingPosition.y - targetRect.y
202
+ };
203
+ const snapEdgeLines = {
204
+ top: isEqual(topYClosest, snappingPosition.y) ? topYClosestLine : void 0,
205
+ bottom: isEqual(bottomYClosest, snappingPosition.y) ? bottomYClosestLine : void 0,
206
+ left: isEqual(leftXClosest, snappingPosition.x) ? leftXClosestLine : void 0,
207
+ right: isEqual(rightXClosest, snappingPosition.x) ? rightXClosestLine : void 0,
208
+ midVertical: isEqual(midXClosest, snappingPosition.x) ? midXClosestLine : void 0,
209
+ midHorizontal: isEqual(midYClosest, snappingPosition.y) ? midYClosestLine : void 0
210
+ };
211
+ return { snapOffset, snapEdgeLines };
212
+ }
213
+ gridSnapping(params) {
214
+ const { gridSize, targetNodes } = params;
215
+ const rect = this.getBounds(targetNodes);
216
+ const snap = (value) => Math.round(value / gridSize) * gridSize;
217
+ const snappedPosition = {
218
+ x: snap(rect.x),
219
+ y: snap(rect.y)
220
+ };
221
+ const offset = {
222
+ x: snappedPosition.x - rect.x,
223
+ y: snappedPosition.y - rect.y
224
+ };
225
+ targetNodes.forEach(
226
+ (node) => this.updateNodePositionWithOffset({
227
+ node,
228
+ offset
229
+ })
230
+ );
231
+ }
232
+ clear() {
233
+ this.snapEmitter.fire({
234
+ snapEdgeLines: {},
235
+ snapRect: utils.Rectangle.EMPTY,
236
+ alignRects: {
237
+ top: [],
238
+ bottom: [],
239
+ left: [],
240
+ right: []
241
+ },
242
+ alignSpacing: {}
243
+ });
244
+ }
245
+ getSnapLines(params) {
246
+ const { snapNodeRects } = params;
247
+ const horizontalLines = [];
248
+ const verticalLines = [];
249
+ const midHorizontalLines = [];
250
+ const midVerticalLines = [];
251
+ snapNodeRects.forEach((snapNodeRect) => {
252
+ const nodeBounds = snapNodeRect.rect;
253
+ const nodeCenter = nodeBounds.center;
254
+ const top = {
255
+ y: nodeBounds.top,
256
+ sourceNodeId: snapNodeRect.id
257
+ };
258
+ const bottom = {
259
+ y: nodeBounds.bottom,
260
+ sourceNodeId: snapNodeRect.id
261
+ };
262
+ const left = {
263
+ x: nodeBounds.left,
264
+ sourceNodeId: snapNodeRect.id
265
+ };
266
+ const right = {
267
+ x: nodeBounds.right,
268
+ sourceNodeId: snapNodeRect.id
269
+ };
270
+ const midHorizontal = {
271
+ y: nodeCenter.y,
272
+ sourceNodeId: snapNodeRect.id
273
+ };
274
+ const midVertical = {
275
+ x: nodeCenter.x,
276
+ sourceNodeId: snapNodeRect.id
277
+ };
278
+ horizontalLines.push(top, bottom);
279
+ verticalLines.push(left, right);
280
+ midHorizontalLines.push(midHorizontal);
281
+ midVerticalLines.push(midVertical);
282
+ });
283
+ return {
284
+ horizontal: horizontalLines,
285
+ vertical: verticalLines,
286
+ midHorizontal: midHorizontalLines,
287
+ midVertical: midVerticalLines
288
+ };
289
+ }
290
+ getAvailableNodes(params) {
291
+ const { targetNodes, targetRect } = params;
292
+ const targetCenter = targetRect.center;
293
+ const targetContainerId = targetNodes[0].parent?.id ?? this.document.root.id;
294
+ const disabledNodeIds = targetNodes.map((n) => n.id);
295
+ disabledNodeIds.push(document.FlowNodeBaseType.ROOT);
296
+ const availableNodes = this.nodes.filter((n) => n.parent?.id === targetContainerId).filter((n) => !disabledNodeIds.includes(n.id)).sort((nodeA, nodeB) => {
297
+ const nodeCenterA = nodeA.getData(document.FlowNodeTransformData).bounds.center;
298
+ const nodeCenterB = nodeB.getData(document.FlowNodeTransformData).bounds.center;
299
+ const distanceA = Math.abs(nodeCenterA.x - targetCenter.x) + Math.abs(nodeCenterA.y - targetCenter.y);
300
+ const distanceB = Math.abs(nodeCenterB.x - targetCenter.x) + Math.abs(nodeCenterB.y - targetCenter.y);
301
+ return distanceA - distanceB;
302
+ });
303
+ return availableNodes;
304
+ }
305
+ viewRect() {
306
+ const { width, height, scrollX, scrollY, zoom } = this.playgroundConfig.config;
307
+ return new utils.Rectangle(scrollX / zoom, scrollY / zoom, width / zoom, height / zoom);
308
+ }
309
+ getSnapNodeRects(params) {
310
+ const availableNodes = this.getAvailableNodes(params);
311
+ const viewRect = this.viewRect();
312
+ return availableNodes.map((node) => {
313
+ const snapNodeRect = {
314
+ id: node.id,
315
+ rect: node.getData(document.FlowNodeTransformData).bounds,
316
+ entity: node
317
+ };
318
+ if (this.options.enableOnlyViewportSnapping && node.parent?.flowNodeType === document.FlowNodeBaseType.ROOT && !utils.Rectangle.intersects(viewRect, snapNodeRect.rect)) {
319
+ return;
320
+ }
321
+ return snapNodeRect;
322
+ }).filter(Boolean);
323
+ }
324
+ get nodes() {
325
+ return this.entityManager.getEntities(freeLayoutCore.WorkflowNodeEntity);
326
+ }
327
+ getBounds(nodes) {
328
+ if (nodes.length === 0) {
329
+ return utils.Rectangle.EMPTY;
330
+ }
331
+ return utils.Rectangle.enlarge(nodes.map((n) => n.getData(document.FlowNodeTransformData).bounds));
332
+ }
333
+ updateNodePositionWithOffset(params) {
334
+ const { node, offset } = params;
335
+ const transform = node.getData(core.TransformData);
336
+ const positionWithOffset = {
337
+ x: transform.position.x + offset.x,
338
+ y: transform.position.y + offset.y
339
+ };
340
+ transform.update({
341
+ position: positionWithOffset
342
+ });
343
+ this.document.layout.updateAffectedTransform(node);
344
+ }
345
+ calcAlignOffset(params) {
346
+ const { snapNodeRects, targetRect, alignThreshold } = params;
347
+ const alignRects = this.getAlignRects({
348
+ targetRect,
349
+ snapNodeRects
350
+ });
351
+ const alignSpacing = this.calcAlignSpacing({
352
+ targetRect,
353
+ alignRects
354
+ });
355
+ let topY;
356
+ let bottomY;
357
+ let leftX;
358
+ let rightX;
359
+ let midY;
360
+ let midX;
361
+ if (alignSpacing.top) {
362
+ const topAlignY = alignRects.top[0].rect.bottom + alignSpacing.top;
363
+ const isAlignTop = isLessThanOrEqual(Math.abs(targetRect.top - topAlignY), alignThreshold);
364
+ if (isAlignTop) {
365
+ topY = topAlignY;
366
+ } else {
367
+ alignSpacing.top = void 0;
368
+ }
369
+ }
370
+ if (alignSpacing.bottom) {
371
+ const bottomAlignY = alignRects.bottom[0].rect.top - alignSpacing.bottom;
372
+ const isAlignBottom = isLessThan(Math.abs(targetRect.bottom - bottomAlignY), alignThreshold);
373
+ if (isAlignBottom) {
374
+ bottomY = bottomAlignY - targetRect.height;
375
+ } else {
376
+ alignSpacing.bottom = void 0;
377
+ }
378
+ }
379
+ if (alignSpacing.left) {
380
+ const leftAlignX = alignRects.left[0].rect.right + alignSpacing.left;
381
+ const isAlignLeft = isLessThanOrEqual(Math.abs(targetRect.left - leftAlignX), alignThreshold);
382
+ if (isAlignLeft) {
383
+ leftX = leftAlignX;
384
+ } else {
385
+ alignSpacing.left = void 0;
386
+ }
387
+ }
388
+ if (alignSpacing.right) {
389
+ const rightAlignX = alignRects.right[0].rect.left - alignSpacing.right;
390
+ const isAlignRight = isLessThanOrEqual(
391
+ Math.abs(targetRect.right - rightAlignX),
392
+ alignThreshold
393
+ );
394
+ if (isAlignRight) {
395
+ rightX = rightAlignX - targetRect.width;
396
+ } else {
397
+ alignSpacing.right = void 0;
398
+ }
399
+ }
400
+ if (alignSpacing.midHorizontal) {
401
+ const leftAlignX = alignRects.left[0].rect.right + alignSpacing.midHorizontal;
402
+ const isAlignMidHorizontal = isLessThanOrEqual(
403
+ Math.abs(targetRect.left - leftAlignX),
404
+ alignThreshold
405
+ );
406
+ if (isAlignMidHorizontal) {
407
+ midX = leftAlignX;
408
+ } else {
409
+ alignSpacing.midHorizontal = void 0;
410
+ }
411
+ }
412
+ if (alignSpacing.midVertical) {
413
+ const topAlignY = alignRects.top[0].rect.bottom + alignSpacing.midVertical;
414
+ const isAlignMidVertical = isLessThanOrEqual(
415
+ Math.abs(targetRect.top - topAlignY),
416
+ alignThreshold
417
+ );
418
+ if (isAlignMidVertical) {
419
+ midY = topAlignY;
420
+ } else {
421
+ alignSpacing.midVertical = void 0;
422
+ }
423
+ }
424
+ const alignPosition = {
425
+ x: midX ?? leftX ?? rightX ?? targetRect.x,
426
+ y: midY ?? topY ?? bottomY ?? targetRect.y
427
+ };
428
+ const alignOffset = {
429
+ x: alignPosition.x - targetRect.x,
430
+ y: alignPosition.y - targetRect.y
431
+ };
432
+ return { alignOffset, alignRects, alignSpacing };
433
+ }
434
+ calcAlignSpacing(params) {
435
+ const { targetRect, alignRects } = params;
436
+ const topSpacing = this.getDirectionAlignSpacing({
437
+ rects: alignRects.top,
438
+ isHorizontal: false
439
+ });
440
+ const bottomSpacing = this.getDirectionAlignSpacing({
441
+ rects: alignRects.bottom,
442
+ isHorizontal: false
443
+ });
444
+ const leftSpacing = this.getDirectionAlignSpacing({
445
+ rects: alignRects.left,
446
+ isHorizontal: true
447
+ });
448
+ const rightSpacing = this.getDirectionAlignSpacing({
449
+ rects: alignRects.right,
450
+ isHorizontal: true
451
+ });
452
+ const midHorizontalSpacing = this.getMidAlignSpacing({
453
+ rectA: alignRects.left[0]?.rect,
454
+ rectB: alignRects.right[0]?.rect,
455
+ targetRect,
456
+ isHorizontal: true
457
+ });
458
+ const midVerticalSpacing = this.getMidAlignSpacing({
459
+ rectA: alignRects.top[0]?.rect,
460
+ rectB: alignRects.bottom[0]?.rect,
461
+ targetRect,
462
+ isHorizontal: false
463
+ });
464
+ return {
465
+ top: topSpacing,
466
+ bottom: bottomSpacing,
467
+ left: leftSpacing,
468
+ right: rightSpacing,
469
+ midHorizontal: midHorizontalSpacing,
470
+ midVertical: midVerticalSpacing
471
+ };
472
+ }
473
+ getAlignRects(params) {
474
+ const { targetRect, snapNodeRects } = params;
475
+ const topVerticalRects = [];
476
+ const bottomVerticalRects = [];
477
+ const leftHorizontalRects = [];
478
+ const rightHorizontalRects = [];
479
+ snapNodeRects.forEach((snapNodeRect) => {
480
+ const nodeRect = snapNodeRect.rect;
481
+ const { isVerticalIntersection, isHorizontalIntersection, isIntersection } = this.intersection(nodeRect, targetRect);
482
+ if (isIntersection) {
483
+ return;
484
+ } else if (isVerticalIntersection) {
485
+ if (isGreaterThan(nodeRect.center.y, targetRect.center.y)) {
486
+ bottomVerticalRects.push({
487
+ rect: nodeRect,
488
+ sourceNodeId: snapNodeRect.id
489
+ });
490
+ } else {
491
+ topVerticalRects.push({
492
+ rect: nodeRect,
493
+ sourceNodeId: snapNodeRect.id
494
+ });
495
+ }
496
+ } else if (isHorizontalIntersection) {
497
+ if (isGreaterThan(nodeRect.center.x, targetRect.center.x)) {
498
+ rightHorizontalRects.push({
499
+ rect: nodeRect,
500
+ sourceNodeId: snapNodeRect.id
501
+ });
502
+ } else {
503
+ leftHorizontalRects.push({
504
+ rect: nodeRect,
505
+ sourceNodeId: snapNodeRect.id
506
+ });
507
+ }
508
+ }
509
+ });
510
+ return {
511
+ top: topVerticalRects,
512
+ bottom: bottomVerticalRects,
513
+ left: leftHorizontalRects,
514
+ right: rightHorizontalRects
515
+ };
516
+ }
517
+ getMidAlignSpacing(params) {
518
+ const { rectA, rectB, targetRect, isHorizontal } = params;
519
+ if (!rectA || !rectB) {
520
+ return;
521
+ }
522
+ const { isVerticalIntersection, isHorizontalIntersection, isIntersection } = this.intersection(
523
+ rectA,
524
+ rectB
525
+ );
526
+ if (isIntersection) {
527
+ return;
528
+ }
529
+ if (isHorizontal && isHorizontalIntersection && !isVerticalIntersection) {
530
+ const betweenSpacing = Math.min(
531
+ Math.abs(rectA.left - rectB.right),
532
+ Math.abs(rectA.right - rectB.left)
533
+ );
534
+ return (betweenSpacing - targetRect.width) / 2;
535
+ } else if (!isHorizontal && isVerticalIntersection && !isHorizontalIntersection) {
536
+ const betweenSpacing = Math.min(
537
+ Math.abs(rectA.top - rectB.bottom),
538
+ Math.abs(rectA.bottom - rectB.top)
539
+ );
540
+ return (betweenSpacing - targetRect.height) / 2;
541
+ }
542
+ }
543
+ getDirectionAlignSpacing(params) {
544
+ const { rects, isHorizontal } = params;
545
+ if (rects.length < 2) {
546
+ return;
547
+ }
548
+ const rectA = rects[0].rect;
549
+ const rectB = rects[1].rect;
550
+ const { isVerticalIntersection, isHorizontalIntersection, isIntersection } = this.intersection(
551
+ rectA,
552
+ rectB
553
+ );
554
+ if (isIntersection) {
555
+ return;
556
+ }
557
+ if (isHorizontal && isHorizontalIntersection && !isVerticalIntersection) {
558
+ return Math.min(Math.abs(rectA.left - rectB.right), Math.abs(rectA.right - rectB.left));
559
+ } else if (!isHorizontal && isVerticalIntersection && !isHorizontalIntersection) {
560
+ return Math.min(Math.abs(rectA.top - rectB.bottom), Math.abs(rectA.bottom - rectB.top));
561
+ }
562
+ return;
563
+ }
564
+ intersection(rectA, rectB) {
565
+ const isVerticalIntersection = isLessThan(rectA.left, rectB.right) && isGreaterThan(rectA.right, rectB.left);
566
+ const isHorizontalIntersection = isLessThan(rectA.top, rectB.bottom) && isGreaterThan(rectA.bottom, rectB.top);
567
+ const isIntersection = isHorizontalIntersection && isVerticalIntersection;
568
+ return {
569
+ isHorizontalIntersection,
570
+ isVerticalIntersection,
571
+ isIntersection
572
+ };
573
+ }
574
+ };
575
+ __decorateClass([
576
+ inversify.inject(freeLayoutCore.WorkflowDocument)
577
+ ], exports.WorkflowSnapService.prototype, "document", 2);
578
+ __decorateClass([
579
+ inversify.inject(core.EntityManager)
580
+ ], exports.WorkflowSnapService.prototype, "entityManager", 2);
581
+ __decorateClass([
582
+ inversify.inject(freeLayoutCore.WorkflowDragService)
583
+ ], exports.WorkflowSnapService.prototype, "dragService", 2);
584
+ __decorateClass([
585
+ inversify.inject(core.PlaygroundConfigEntity)
586
+ ], exports.WorkflowSnapService.prototype, "playgroundConfig", 2);
587
+ exports.WorkflowSnapService = __decorateClass([
588
+ inversify.injectable()
589
+ ], exports.WorkflowSnapService);
590
+ var WorkflowSnapLayer = class extends core.Layer {
591
+ constructor() {
592
+ super(...arguments);
593
+ this.node = utils.domUtils.createDivWithClass(
594
+ "gedit-playground-layer gedit-flow-snap-layer"
595
+ );
596
+ this.edgeLines = [];
597
+ this.alignLines = [];
598
+ }
599
+ onReady() {
600
+ this.node.style.zIndex = "9999";
601
+ this.toDispose.pushAll([
602
+ this.service.onSnap((event) => {
603
+ this.edgeLines = this.calcEdgeLines(event);
604
+ this.alignLines = this.calcAlignLines(event);
605
+ this.render();
606
+ })
607
+ ]);
608
+ }
609
+ render() {
610
+ const children = [];
611
+ if (this.alignLines.length > 0) {
612
+ children.push(
613
+ vue.h("div", { class: "workflow-snap-align-lines" }, this.renderAlignLines())
614
+ );
615
+ }
616
+ if (this.edgeLines.length > 0) {
617
+ children.push(vue.h("div", { class: "workflow-snap-edge-lines" }, this.renderEdgeLines()));
618
+ }
619
+ return vue.h(vue.Fragment, null, children);
620
+ }
621
+ onZoom(scale) {
622
+ this.node.style.transform = `scale(${scale})`;
623
+ }
624
+ renderEdgeLines() {
625
+ return this.edgeLines.map((renderLine) => {
626
+ const { className, sourceNode, top, left, width, height, dashed } = renderLine;
627
+ const id = `${className}-${sourceNode}-${top}-${left}-${width}-${height}`;
628
+ const isHorizontal = width < height;
629
+ const border = `${this.options.edgeLineWidth}px ${dashed ? "dashed" : "solid"} ${this.options.edgeColor}`;
630
+ return vue.h("div", {
631
+ class: `workflow-snap-edge-line ${className}`,
632
+ "data-testid": "sdk.workflow.canvas.snap.edgeLine",
633
+ "data-snap-line-id": id,
634
+ "data-snap-line-source-node": sourceNode,
635
+ key: id,
636
+ style: {
637
+ top: `${top}px`,
638
+ left: `${left}px`,
639
+ width: `${width}px`,
640
+ height: `${height}px`,
641
+ position: "absolute",
642
+ borderLeft: isHorizontal ? border : "none",
643
+ borderTop: !isHorizontal ? border : "none"
644
+ }
645
+ });
646
+ });
647
+ }
648
+ renderAlignLines() {
649
+ return this.alignLines.map((renderLine) => {
650
+ const id = `${renderLine.className}-${renderLine.sourceNode}-${renderLine.top}-${renderLine.left}-${renderLine.width}-${renderLine.height}`;
651
+ const isHorizontal = isGreaterThan(renderLine.width, renderLine.height);
652
+ const alignLineWidth = this.options.alignLineWidth;
653
+ const alignCrossWidth = this.options.alignCrossWidth;
654
+ const adjustedTop = isHorizontal ? renderLine.top - alignLineWidth / 2 : renderLine.top;
655
+ const adjustedLeft = isHorizontal ? renderLine.left : renderLine.left - alignLineWidth / 2;
656
+ return vue.h(
657
+ "div",
658
+ {
659
+ class: `workflow-snap-align-line ${renderLine.className}`,
660
+ "data-testid": "sdk.workflow.canvas.snap.alignLine",
661
+ "data-snap-line-id": id,
662
+ "data-snap-line-source-node": renderLine.sourceNode,
663
+ key: id,
664
+ style: {
665
+ position: "absolute"
666
+ }
667
+ },
668
+ [
669
+ vue.h("div", {
670
+ style: {
671
+ position: "absolute",
672
+ top: `${adjustedTop}px`,
673
+ left: `${adjustedLeft}px`,
674
+ width: `${isHorizontal ? renderLine.width : alignLineWidth}px`,
675
+ height: `${isHorizontal ? alignLineWidth : renderLine.height}px`,
676
+ backgroundColor: this.options.alignColor
677
+ }
678
+ }),
679
+ vue.h("div", {
680
+ style: {
681
+ position: "absolute",
682
+ top: `${isHorizontal ? adjustedTop - (alignCrossWidth - alignLineWidth) / 2 : adjustedTop}px`,
683
+ left: `${isHorizontal ? adjustedLeft : adjustedLeft - (alignCrossWidth - alignLineWidth) / 2}px`,
684
+ width: `${isHorizontal ? alignLineWidth : alignCrossWidth}px`,
685
+ height: `${isHorizontal ? alignCrossWidth : alignLineWidth}px`,
686
+ backgroundColor: this.options.alignColor
687
+ }
688
+ }),
689
+ vue.h("div", {
690
+ style: {
691
+ position: "absolute",
692
+ top: `${isHorizontal ? adjustedTop - (alignCrossWidth - alignLineWidth) / 2 : adjustedTop + renderLine.height - alignLineWidth}px`,
693
+ left: `${isHorizontal ? adjustedLeft + renderLine.width - alignLineWidth : adjustedLeft - (alignCrossWidth - alignLineWidth) / 2}px`,
694
+ width: `${isHorizontal ? alignLineWidth : alignCrossWidth}px`,
695
+ height: `${isHorizontal ? alignCrossWidth : alignLineWidth}px`,
696
+ backgroundColor: this.options.alignColor
697
+ }
698
+ })
699
+ ]
700
+ );
701
+ });
702
+ }
703
+ calcEdgeLines(event) {
704
+ const { alignRects, snapRect, snapEdgeLines } = event;
705
+ const edgeLines = [];
706
+ const topFullAlign = this.directionFullAlign({
707
+ alignRects: alignRects.top,
708
+ targetRect: snapRect,
709
+ isVertical: true
710
+ });
711
+ const bottomFullAlign = this.directionFullAlign({
712
+ alignRects: alignRects.bottom,
713
+ targetRect: snapRect,
714
+ isVertical: true
715
+ });
716
+ const leftFullAlign = this.directionFullAlign({
717
+ alignRects: alignRects.left,
718
+ targetRect: snapRect,
719
+ isVertical: false
720
+ });
721
+ const rightFullAlign = this.directionFullAlign({
722
+ alignRects: alignRects.right,
723
+ targetRect: snapRect,
724
+ isVertical: false
725
+ });
726
+ if (topFullAlign) {
727
+ const top = topFullAlign.rect.top;
728
+ const height = bottomFullAlign ? snapRect.bottom - snapRect.height / 2 - top : snapRect.bottom - top;
729
+ const width = this.options.edgeLineWidth;
730
+ const lineData = {
731
+ top,
732
+ width,
733
+ height
734
+ };
735
+ edgeLines.push({
736
+ className: "edge-full-top-left",
737
+ sourceNode: topFullAlign.sourceNodeId,
738
+ left: snapRect.left,
739
+ ...lineData
740
+ });
741
+ edgeLines.push({
742
+ className: "edge-full-top-right",
743
+ sourceNode: topFullAlign.sourceNodeId,
744
+ left: snapRect.right - 1,
745
+ ...lineData
746
+ });
747
+ edgeLines.push({
748
+ className: "edge-full-top-mid",
749
+ sourceNode: topFullAlign.sourceNodeId,
750
+ left: snapRect.left + snapRect.width / 2,
751
+ dashed: true,
752
+ ...lineData
753
+ });
754
+ }
755
+ if (bottomFullAlign) {
756
+ const top = topFullAlign ? snapRect.top + snapRect.height / 2 : snapRect.top;
757
+ const height = bottomFullAlign.rect.bottom - top;
758
+ const width = this.options.edgeLineWidth;
759
+ const lineData = {
760
+ top,
761
+ width,
762
+ height
763
+ };
764
+ edgeLines.push({
765
+ className: "edge-full-bottom-left",
766
+ sourceNode: bottomFullAlign.sourceNodeId,
767
+ left: snapRect.left,
768
+ ...lineData
769
+ });
770
+ edgeLines.push({
771
+ className: "edge-full-bottom-right",
772
+ sourceNode: bottomFullAlign.sourceNodeId,
773
+ left: snapRect.right - 1,
774
+ ...lineData
775
+ });
776
+ edgeLines.push({
777
+ className: "edge-full-bottom-mid",
778
+ sourceNode: bottomFullAlign.sourceNodeId,
779
+ left: snapRect.left + snapRect.width / 2,
780
+ dashed: true,
781
+ ...lineData
782
+ });
783
+ }
784
+ if (leftFullAlign) {
785
+ const left = leftFullAlign.rect.left;
786
+ const width = rightFullAlign ? snapRect.right - snapRect.width / 2 - left : snapRect.right - left;
787
+ const height = this.options.edgeLineWidth;
788
+ const lineData = {
789
+ left,
790
+ width,
791
+ height
792
+ };
793
+ edgeLines.push({
794
+ className: "edge-full-left-top",
795
+ sourceNode: leftFullAlign.sourceNodeId,
796
+ top: snapRect.top,
797
+ ...lineData
798
+ });
799
+ edgeLines.push({
800
+ className: "edge-full-left-bottom",
801
+ sourceNode: leftFullAlign.sourceNodeId,
802
+ top: snapRect.bottom - 1,
803
+ ...lineData
804
+ });
805
+ edgeLines.push({
806
+ className: "edge-full-left-mid",
807
+ sourceNode: leftFullAlign.sourceNodeId,
808
+ top: snapRect.top + snapRect.height / 2,
809
+ dashed: true,
810
+ ...lineData
811
+ });
812
+ }
813
+ if (rightFullAlign) {
814
+ const left = leftFullAlign ? snapRect.left + snapRect.width / 2 : snapRect.left;
815
+ const width = rightFullAlign.rect.right - left;
816
+ const height = this.options.edgeLineWidth;
817
+ const lineData = {
818
+ left,
819
+ width,
820
+ height
821
+ };
822
+ edgeLines.push({
823
+ className: "edge-full-right-top",
824
+ sourceNode: rightFullAlign.sourceNodeId,
825
+ top: snapRect.top,
826
+ ...lineData
827
+ });
828
+ edgeLines.push({
829
+ className: "edge-full-right-bottom",
830
+ sourceNode: rightFullAlign.sourceNodeId,
831
+ top: snapRect.bottom - 1,
832
+ ...lineData
833
+ });
834
+ edgeLines.push({
835
+ className: "edge-full-right-mid",
836
+ sourceNode: rightFullAlign.sourceNodeId,
837
+ top: snapRect.top + snapRect.height / 2,
838
+ dashed: true,
839
+ ...lineData
840
+ });
841
+ }
842
+ const snappedEdgeLines = Object.entries(snapEdgeLines).map(([direction, snapLine]) => {
843
+ if (!snapLine) {
844
+ return;
845
+ }
846
+ const sourceNode = this.document.getNode(snapLine.sourceNodeId);
847
+ if (!sourceNode) {
848
+ return;
849
+ }
850
+ const nodeRect = sourceNode.getData(document.FlowNodeTransformData).bounds;
851
+ if (isNumber(snapLine.x)) {
852
+ const top = Math.min(nodeRect.top, snapRect.top);
853
+ const bottom = Math.max(nodeRect.bottom, snapRect.bottom);
854
+ const height = bottom - top;
855
+ const left = direction === "right" ? snapLine.x - 1 : snapLine.x;
856
+ const width = this.options.edgeLineWidth;
857
+ const isMidX = direction === "midVertical";
858
+ const lineData = {
859
+ className: `edge-snapped-${direction}`,
860
+ sourceNode: snapLine.sourceNodeId,
861
+ top,
862
+ left,
863
+ width,
864
+ height,
865
+ dashed: isMidX
866
+ };
867
+ const onTop = top === nodeRect.top;
868
+ if (onTop && topFullAlign) {
869
+ return;
870
+ }
871
+ if (!onTop && bottomFullAlign) {
872
+ return;
873
+ }
874
+ return lineData;
875
+ } else if (isNumber(snapLine.y)) {
876
+ const left = Math.min(nodeRect.left, snapRect.left);
877
+ const right = Math.max(nodeRect.right, snapRect.right);
878
+ const width = right - left;
879
+ const top = direction === "bottom" ? snapLine.y - 1 : snapLine.y;
880
+ const height = this.options.edgeLineWidth;
881
+ const isMidY = direction === "midHorizontal";
882
+ const lineData = {
883
+ className: `edge-snapped-${direction}`,
884
+ sourceNode: snapLine.sourceNodeId,
885
+ top,
886
+ left,
887
+ width,
888
+ height,
889
+ dashed: isMidY
890
+ };
891
+ const onLeft = left === nodeRect.left;
892
+ if (onLeft && leftFullAlign) {
893
+ return;
894
+ }
895
+ if (!onLeft && rightFullAlign) {
896
+ return;
897
+ }
898
+ return lineData;
899
+ }
900
+ }).filter(Boolean);
901
+ edgeLines.push(...snappedEdgeLines);
902
+ return edgeLines;
903
+ }
904
+ directionFullAlign(params) {
905
+ const { alignRects, targetRect, isVertical } = params;
906
+ let fullAlignIndex = -1;
907
+ for (let i = 0; i < alignRects.length; i++) {
908
+ const alignRect = alignRects[i];
909
+ const prevRect = alignRects[i - 1]?.rect ?? targetRect;
910
+ const isFullAlign = this.rectFullAlign(alignRect.rect, prevRect, isVertical);
911
+ if (!isFullAlign) {
912
+ break;
913
+ }
914
+ fullAlignIndex = i;
915
+ }
916
+ const fullAlignRect = alignRects[fullAlignIndex];
917
+ return fullAlignRect;
918
+ }
919
+ rectFullAlign(rectA, rectB, isVertical) {
920
+ if (isVertical) {
921
+ return isEqual(rectA.left, rectB.left) && isEqual(rectA.right, rectB.right);
922
+ } else {
923
+ return isEqual(rectA.top, rectB.top) && isEqual(rectA.bottom, rectB.bottom);
924
+ }
925
+ }
926
+ calcAlignLines(event) {
927
+ const { alignRects, alignSpacing, snapRect } = event;
928
+ const topAlignLines = this.calcDirectionAlignLines({
929
+ alignRects: alignRects.top,
930
+ targetRect: snapRect,
931
+ isVertical: true,
932
+ spacing: alignSpacing.midVertical ?? alignSpacing.top
933
+ });
934
+ const bottomAlignLines = this.calcDirectionAlignLines({
935
+ alignRects: alignRects.bottom,
936
+ targetRect: snapRect,
937
+ isVertical: true,
938
+ spacing: alignSpacing.midVertical ?? alignSpacing.bottom
939
+ });
940
+ const leftAlignLines = this.calcDirectionAlignLines({
941
+ alignRects: alignRects.left,
942
+ targetRect: snapRect,
943
+ isVertical: false,
944
+ spacing: alignSpacing.midHorizontal ?? alignSpacing.left
945
+ });
946
+ const rightAlignLines = this.calcDirectionAlignLines({
947
+ alignRects: alignRects.right,
948
+ targetRect: snapRect,
949
+ isVertical: false,
950
+ spacing: alignSpacing.midHorizontal ?? alignSpacing.right
951
+ });
952
+ return [...topAlignLines, ...bottomAlignLines, ...leftAlignLines, ...rightAlignLines];
953
+ }
954
+ calcDirectionAlignLines(params) {
955
+ const { alignRects, targetRect, isVertical, spacing } = params;
956
+ const alignLines = [];
957
+ if (!spacing) {
958
+ return alignLines;
959
+ }
960
+ for (let i = 0; i < alignRects.length; i++) {
961
+ const alignRect = alignRects[i];
962
+ const rect = alignRect.rect;
963
+ const prevRect = alignRects[i - 1]?.rect ?? targetRect;
964
+ const betweenSpacing = isVertical ? Math.min(Math.abs(prevRect.top - rect.bottom), Math.abs(prevRect.bottom - rect.top)) : Math.min(Math.abs(prevRect.left - rect.right), Math.abs(prevRect.right - rect.left));
965
+ if (!isEqual(betweenSpacing, spacing)) {
966
+ break;
967
+ }
968
+ if (isVertical) {
969
+ const centerX = this.calcHorizontalIntersectionCenter(rect, targetRect);
970
+ alignLines.push({
971
+ className: "align-vertical",
972
+ sourceNode: alignRect.sourceNodeId,
973
+ top: Math.min(rect.bottom, prevRect.bottom),
974
+ left: centerX,
975
+ width: 1,
976
+ height: spacing
977
+ });
978
+ } else {
979
+ const centerY = this.calcVerticalIntersectionCenter(rect, targetRect);
980
+ alignLines.push({
981
+ className: "align-horizontal",
982
+ sourceNode: alignRect.sourceNodeId,
983
+ top: centerY,
984
+ left: Math.min(rect.right, prevRect.right),
985
+ width: spacing,
986
+ height: 1
987
+ });
988
+ }
989
+ }
990
+ return alignLines;
991
+ }
992
+ calcVerticalIntersectionCenter(rectA, rectB) {
993
+ const top = Math.max(rectA.top, rectB.top);
994
+ const bottom = Math.min(rectA.bottom, rectB.bottom);
995
+ return (top + bottom) / 2;
996
+ }
997
+ calcHorizontalIntersectionCenter(rectA, rectB) {
998
+ const left = Math.max(rectA.left, rectB.left);
999
+ const right = Math.min(rectA.right, rectB.right);
1000
+ return (left + right) / 2;
1001
+ }
1002
+ };
1003
+ WorkflowSnapLayer.type = "WorkflowSnapLayer";
1004
+ __decorateClass([
1005
+ inversify.inject(freeLayoutCore.WorkflowDocument)
1006
+ ], WorkflowSnapLayer.prototype, "document", 2);
1007
+ __decorateClass([
1008
+ inversify.inject(exports.WorkflowSnapService)
1009
+ ], WorkflowSnapLayer.prototype, "service", 2);
1010
+ WorkflowSnapLayer = __decorateClass([
1011
+ inversify.injectable()
1012
+ ], WorkflowSnapLayer);
1013
+
1014
+ // src/create-plugin.ts
1015
+ var createFreeSnapPlugin = core.definePluginCreator({
1016
+ onBind({ bind }) {
1017
+ bind(exports.WorkflowSnapService).toSelf().inSingletonScope();
1018
+ },
1019
+ onInit(ctx, opts) {
1020
+ const options = {
1021
+ ...SnapDefaultOptions,
1022
+ ...opts
1023
+ };
1024
+ ctx.playground.registerLayer(WorkflowSnapLayer, options);
1025
+ const snapService = ctx.get(exports.WorkflowSnapService);
1026
+ snapService.init(options);
1027
+ },
1028
+ onDispose(ctx) {
1029
+ const snapService = ctx.get(exports.WorkflowSnapService);
1030
+ snapService.dispose();
1031
+ }
1032
+ });
1033
+
1034
+ exports.createFreeSnapPlugin = createFreeSnapPlugin;
1035
+ //# sourceMappingURL=index.cjs.map
1036
+ //# sourceMappingURL=index.cjs.map