@flowgram-vue/free-lines-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.
Files changed (38) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +1026 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.css +120 -0
  5. package/dist/index.css.map +1 -0
  6. package/dist/index.d.ts +269 -0
  7. package/dist/index.js +1013 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +63 -0
  10. package/src/__tests__/__snapshots__/bezier-controls.spec.ts.snap +20 -0
  11. package/src/__tests__/bezier-controls.spec.ts +25 -0
  12. package/src/components/index.ts +8 -0
  13. package/src/components/workflow-line-render/arrow.ts +60 -0
  14. package/src/components/workflow-line-render/index.css +22 -0
  15. package/src/components/workflow-line-render/index.ts +6 -0
  16. package/src/components/workflow-line-render/line-svg.ts +131 -0
  17. package/src/components/workflow-port-render/cross-hair.ts +16 -0
  18. package/src/components/workflow-port-render/index.css +121 -0
  19. package/src/components/workflow-port-render/index.ts +183 -0
  20. package/src/constants/lines.ts +9 -0
  21. package/src/constants/points.ts +12 -0
  22. package/src/contributions/bezier/bezier-controls.ts +105 -0
  23. package/src/contributions/bezier/index.ts +121 -0
  24. package/src/contributions/fold/fold-line.ts +293 -0
  25. package/src/contributions/fold/index.ts +97 -0
  26. package/src/contributions/index.ts +8 -0
  27. package/src/contributions/straight/index.ts +89 -0
  28. package/src/contributions/straight/point-on-line.ts +37 -0
  29. package/src/contributions/utils.ts +37 -0
  30. package/src/create-free-lines-plugin.ts +44 -0
  31. package/src/css.d.ts +6 -0
  32. package/src/env.d.ts +10 -0
  33. package/src/index.ts +12 -0
  34. package/src/layer/index.ts +8 -0
  35. package/src/layer/workflow-lines-layer.ts +201 -0
  36. package/src/type.ts +40 -0
  37. package/src/types/arrow-renderer.ts +32 -0
  38. package/src/wrap-layer-render.ts +38 -0
package/dist/index.js ADDED
@@ -0,0 +1,1013 @@
1
+ import { defineComponent, h, shallowRef, watch, onBeforeUnmount, Teleport, provide, Fragment } from 'vue';
2
+ import classNames from 'clsx';
3
+ import { WorkflowHoverService, WorkflowLinesManager, usePlaygroundReadonlyState, WorkflowLineRenderData, WorkflowSelectService, WorkflowLineEntity, WorkflowPortEntity, WorkflowNodeEntity, WorkflowDocument, LineType, nanoid, getLineCenter } from '@flowgram-vue/free-layout-core';
4
+ import { useService, MouseTouchEvent, PlaygroundVueContainerKey, PlaygroundVueRefKey, Playground, PlaygroundContainerFactory, observeEntities, observeEntityDatas, TransformData, definePluginCreator, Layer } from '@flowgram-vue/core';
5
+ import { inject, injectable } from 'inversify';
6
+ import { Point, domUtils, Rectangle } from '@flowgram-vue/utils';
7
+ import { FlowRendererKey, FlowRendererRegistry } from '@flowgram-vue/renderer';
8
+ import { StackingContextManager } from '@flowgram-vue/free-stack-plugin';
9
+ import { Bezier } from 'bezier-js';
10
+
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __decorateClass = (decorators, target, key, kind) => {
14
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
15
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
16
+ if (decorator = decorators[i])
17
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
18
+ if (kind && result) __defProp(target, key, result);
19
+ return result;
20
+ };
21
+
22
+ // src/constants/points.ts
23
+ var STROKE_WIDTH_SLECTED = 3;
24
+ var STROKE_WIDTH = 2;
25
+ var PORT_BG_CLASS_NAME = "workflow-port-bg";
26
+ var CrossHair = defineComponent({
27
+ name: "CrossHair",
28
+ setup() {
29
+ return () => h("div", { class: "symbol" }, [h("div", { class: "cross-hair" })]);
30
+ }
31
+ });
32
+ var cross_hair_default = CrossHair;
33
+
34
+ // src/components/workflow-port-render/index.ts
35
+ var WorkflowPortRender = defineComponent({
36
+ name: "WorkflowPortRender",
37
+ props: {
38
+ entity: { type: Object, required: true },
39
+ className: { type: String, default: "" },
40
+ style: { type: Object },
41
+ onClick: {
42
+ type: Function
43
+ },
44
+ primaryColor: { type: String },
45
+ secondaryColor: { type: String },
46
+ errorColor: { type: String },
47
+ backgroundColor: { type: String }
48
+ },
49
+ setup(props) {
50
+ const hoverService = useService(WorkflowHoverService);
51
+ const linesManager = useService(WorkflowLinesManager);
52
+ const targetElement = shallowRef(props.entity.targetElement);
53
+ const posX = shallowRef(props.entity.relativePosition.x);
54
+ const posY = shallowRef(props.entity.relativePosition.y);
55
+ const hovered = shallowRef(false);
56
+ const linked = shallowRef(Boolean(props.entity?.lines?.length));
57
+ const hasError = shallowRef(props.entity.hasError);
58
+ const readonly = usePlaygroundReadonlyState();
59
+ let disposers = [];
60
+ const bindEntity = () => {
61
+ disposers.forEach((dispose) => dispose.dispose());
62
+ disposers = [];
63
+ const { entity } = props;
64
+ entity.validate();
65
+ hasError.value = entity.hasError;
66
+ disposers.push(
67
+ entity.onEntityChange(() => {
68
+ if (entity.targetElement) {
69
+ if (entity.targetElement !== targetElement.value) {
70
+ targetElement.value = entity.targetElement;
71
+ }
72
+ return;
73
+ }
74
+ const newPos = entity.relativePosition;
75
+ posX.value = Math.round(newPos.x);
76
+ posY.value = Math.round(newPos.y);
77
+ }),
78
+ hoverService.onHoveredChange(() => {
79
+ hovered.value = hoverService.isHovered(entity.id);
80
+ }),
81
+ entity.onErrorChanged(() => {
82
+ hasError.value = entity.hasError;
83
+ }),
84
+ linesManager.onAvailableLinesChange(() => {
85
+ setTimeout(() => {
86
+ if (linesManager.disposed || entity.disposed) return;
87
+ linked.value = Boolean(entity.lines.length);
88
+ }, 0);
89
+ })
90
+ );
91
+ };
92
+ watch(
93
+ () => [props.entity, targetElement.value],
94
+ () => bindEntity(),
95
+ { immediate: true }
96
+ );
97
+ onBeforeUnmount(() => {
98
+ disposers.forEach((dispose) => dispose.dispose());
99
+ });
100
+ return () => {
101
+ const { entity, onClick } = props;
102
+ const { disabled } = entity;
103
+ const className = classNames("workflow-port-render", props.className || "", {
104
+ hovered: !readonly.value && hovered.value && !disabled,
105
+ linked: linked.value
106
+ });
107
+ const colorStyles = {};
108
+ if (props.primaryColor) {
109
+ colorStyles["--g-workflow-port-color-primary"] = props.primaryColor;
110
+ }
111
+ if (props.secondaryColor) {
112
+ colorStyles["--g-workflow-port-color-secondary"] = props.secondaryColor;
113
+ }
114
+ if (props.errorColor) {
115
+ colorStyles["--g-workflow-port-color-error"] = props.errorColor;
116
+ }
117
+ if (props.backgroundColor) {
118
+ colorStyles["--g-workflow-port-color-background"] = props.backgroundColor;
119
+ }
120
+ const combinedStyle = targetElement.value ? { ...props.style, ...colorStyles } : { ...props.style, ...colorStyles, left: `${posX.value}px`, top: `${posY.value}px` };
121
+ const content = h(
122
+ "div",
123
+ {
124
+ class: className,
125
+ style: combinedStyle,
126
+ onClick: (e) => onClick?.(e, entity),
127
+ onTouchstart: (e) => {
128
+ if (!onClick) {
129
+ return;
130
+ }
131
+ MouseTouchEvent.onTouched(e, (mouseEvent) => {
132
+ onClick(mouseEvent, entity);
133
+ });
134
+ },
135
+ "data-port-entity-id": entity.id,
136
+ "data-port-entity-type": entity.portType,
137
+ "data-testid": "sdk.workflow.canvas.node.port"
138
+ },
139
+ [
140
+ h("div", { class: classNames("bg-circle", "workflow-bg-circle") }),
141
+ h(
142
+ "div",
143
+ {
144
+ class: classNames({
145
+ bg: true,
146
+ [PORT_BG_CLASS_NAME]: true,
147
+ "workflow-point-bg": true,
148
+ hasError: hasError.value
149
+ })
150
+ },
151
+ [h(cross_hair_default)]
152
+ ),
153
+ h("div", { class: "focus-circle" })
154
+ ]
155
+ );
156
+ if (targetElement.value) {
157
+ return h(Teleport, { to: targetElement.value }, [content]);
158
+ }
159
+ return content;
160
+ };
161
+ }
162
+ });
163
+
164
+ // src/constants/lines.ts
165
+ var LINE_OFFSET = 6;
166
+ var LINE_PADDING = 12;
167
+ var LayerVueProvide = defineComponent({
168
+ name: "LayerVueProvide",
169
+ props: {
170
+ factory: {
171
+ type: Object,
172
+ required: true
173
+ }
174
+ },
175
+ setup(props) {
176
+ const factory = props.factory;
177
+ provide(PlaygroundVueContainerKey, factory);
178
+ try {
179
+ provide(PlaygroundVueRefKey, factory.get(Playground));
180
+ } catch {
181
+ }
182
+ },
183
+ render() {
184
+ return this.$slots.default?.();
185
+ }
186
+ });
187
+ function wrapLayerRender(factory, children) {
188
+ return h(LayerVueProvide, { factory }, () => children);
189
+ }
190
+
191
+ // src/contributions/utils.ts
192
+ function toRelative(p, bbox) {
193
+ return {
194
+ x: p.x - bbox.x + LINE_PADDING,
195
+ y: p.y - bbox.y + LINE_PADDING
196
+ };
197
+ }
198
+ function getShrinkOffset(location, shrink) {
199
+ switch (location) {
200
+ case "left":
201
+ return { x: -shrink, y: 0 };
202
+ case "right":
203
+ return { x: shrink, y: 0 };
204
+ case "bottom":
205
+ return { x: 0, y: shrink };
206
+ case "top":
207
+ return { x: 0, y: -shrink };
208
+ }
209
+ }
210
+ function posWithShrink(pos, location, shrink) {
211
+ const offset = getShrinkOffset(location, shrink);
212
+ return {
213
+ x: pos.x + offset.x,
214
+ y: pos.y + offset.y
215
+ };
216
+ }
217
+ function getArrowPath(pos, location) {
218
+ switch (location) {
219
+ case "left":
220
+ return `M ${pos.x - LINE_OFFSET},${pos.y - LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x - LINE_OFFSET},${pos.y + LINE_OFFSET}`;
221
+ case "right":
222
+ return `M ${pos.x + LINE_OFFSET},${pos.y + LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x + LINE_OFFSET},${pos.y - LINE_OFFSET}`;
223
+ case "bottom":
224
+ return `M ${pos.x - LINE_OFFSET},${pos.y + LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x + LINE_OFFSET},${pos.y + LINE_OFFSET}`;
225
+ case "top":
226
+ return `M ${pos.x - LINE_OFFSET},${pos.y - LINE_OFFSET} L ${pos.x},${pos.y} L ${pos.x + LINE_OFFSET},${pos.y - LINE_OFFSET}`;
227
+ }
228
+ }
229
+ var ArrowRenderer = defineComponent({
230
+ name: "ArrowRenderer",
231
+ props: {
232
+ id: { type: String, required: true },
233
+ pos: { type: Object, required: true },
234
+ location: { type: String, required: true },
235
+ strokeWidth: { type: Number, required: true },
236
+ hide: { type: Boolean, default: false },
237
+ line: { type: Object, required: true }
238
+ },
239
+ setup(props) {
240
+ return () => {
241
+ if (props.hide) {
242
+ return null;
243
+ }
244
+ const arrowPath = getArrowPath(props.pos, props.location);
245
+ return h("path", {
246
+ d: arrowPath,
247
+ "stroke-linecap": "round",
248
+ stroke: `url(#${props.id})`,
249
+ fill: "none",
250
+ "stroke-width": props.strokeWidth
251
+ });
252
+ };
253
+ }
254
+ });
255
+
256
+ // src/components/workflow-line-render/line-svg.ts
257
+ var PADDING = 12;
258
+ var LineSVG = defineComponent({
259
+ name: "LineSVG",
260
+ props: {
261
+ color: { type: String },
262
+ selected: { type: Boolean, default: false },
263
+ hovered: { type: Boolean, default: false },
264
+ line: { type: Object, required: true },
265
+ lineType: { type: [String, Number] },
266
+ version: { type: String, required: true },
267
+ strokePrefix: { type: String },
268
+ rendererRegistry: {
269
+ type: Object
270
+ }
271
+ },
272
+ setup(props, { slots }) {
273
+ return () => {
274
+ const { line, color, selected, strokePrefix, rendererRegistry } = props;
275
+ const { position, reverse, hideArrow, vertical } = line;
276
+ const renderData = line.getData(WorkflowLineRenderData);
277
+ const { bounds, path: bezierPath } = renderData;
278
+ const toRelative2 = (p) => ({
279
+ x: p.x - bounds.x + PADDING,
280
+ y: p.y - bounds.y + PADDING
281
+ });
282
+ const fromPos = toRelative2(position.from);
283
+ const toPos = toRelative2(position.to);
284
+ const arrowToPos = posWithShrink(toPos, position.to.location, line.uiState.shrink);
285
+ const arrowFromPos = posWithShrink(
286
+ fromPos,
287
+ position.from.location,
288
+ line.uiState.shrink
289
+ );
290
+ const strokeWidth = selected ? line.uiState.strokeWidthSelected ?? STROKE_WIDTH_SLECTED : line.uiState.strokeWidth ?? STROKE_WIDTH;
291
+ const strokeID = strokePrefix ? `${strokePrefix}-${line.id}` : line.id;
292
+ const CustomArrowRenderer = rendererRegistry?.tryToGetRendererComponent(
293
+ FlowRendererKey.ARROW_RENDERER
294
+ )?.renderer;
295
+ const ArrowComponent = CustomArrowRenderer || ArrowRenderer;
296
+ const pathNode = h("path", {
297
+ d: bezierPath,
298
+ fill: "none",
299
+ stroke: `url(#${strokeID})`,
300
+ "stroke-width": strokeWidth,
301
+ class: line.processing || line.flowing ? "dashed-line flowing-line" : ""
302
+ });
303
+ return h(
304
+ "div",
305
+ {
306
+ class: classNames("gedit-flow-activity-edge", line.className),
307
+ style: {
308
+ ...line.uiState.style,
309
+ left: `${bounds.x - PADDING}px`,
310
+ top: `${bounds.y - PADDING}px`,
311
+ position: "absolute"
312
+ }
313
+ },
314
+ [
315
+ slots.default?.(),
316
+ h(
317
+ "svg",
318
+ {
319
+ width: bounds.width + PADDING * 2,
320
+ height: bounds.height + PADDING * 2
321
+ },
322
+ [
323
+ h("defs", null, [
324
+ h(
325
+ "linearGradient",
326
+ {
327
+ x1: vertical ? "100%" : "0%",
328
+ y1: vertical ? "0%" : "100%",
329
+ x2: "100%",
330
+ y2: "100%",
331
+ id: strokeID,
332
+ gradientUnits: "userSpaceOnUse"
333
+ },
334
+ [
335
+ h("stop", { "stop-color": color, offset: "0%" }),
336
+ h("stop", { "stop-color": color, offset: "100%" })
337
+ ]
338
+ )
339
+ ]),
340
+ h("g", null, [
341
+ pathNode,
342
+ h(ArrowComponent, {
343
+ id: strokeID,
344
+ pos: reverse ? arrowFromPos : arrowToPos,
345
+ strokeWidth,
346
+ location: reverse ? position.from.location : position.to.location,
347
+ hide: hideArrow,
348
+ line
349
+ })
350
+ ])
351
+ ]
352
+ )
353
+ ]
354
+ );
355
+ };
356
+ }
357
+ });
358
+
359
+ // src/layer/workflow-lines-layer.ts
360
+ var WorkflowLinesLayer = class extends Layer {
361
+ constructor() {
362
+ super(...arguments);
363
+ this.layerID = nanoid();
364
+ this.mountedLines = /* @__PURE__ */ new Map();
365
+ this._version = 0;
366
+ /**
367
+ * 节点线条
368
+ */
369
+ this.node = domUtils.createDivWithClass("gedit-playground-layer gedit-flow-lines-layer");
370
+ }
371
+ onZoom(scale) {
372
+ this.node.style.transform = `scale(${scale})`;
373
+ }
374
+ onReady() {
375
+ this.pipelineNode.appendChild(this.node);
376
+ this.toDispose.pushAll([
377
+ this.selectService.onSelectionChanged(() => this.render()),
378
+ this.hoverService.onHoveredChange(() => this.render()),
379
+ this.workflowDocument.linesManager.onForceUpdate(() => {
380
+ this.mountedLines.clear();
381
+ this.bumpVersion();
382
+ this.render();
383
+ })
384
+ ]);
385
+ }
386
+ dispose() {
387
+ if (this.rafId != null) {
388
+ cancelAnimationFrame(this.rafId);
389
+ this.rafId = void 0;
390
+ }
391
+ this.mountedLines.clear();
392
+ }
393
+ render() {
394
+ this.scheduleLineRenderUpdate();
395
+ const lines = this.lines.map((line) => this.renderLine(line));
396
+ return wrapLayerRender(this.playgroundContainer, h(Fragment, null, lines));
397
+ }
398
+ scheduleLineRenderUpdate() {
399
+ if (this.rafId != null) {
400
+ cancelAnimationFrame(this.rafId);
401
+ }
402
+ this.rafId = requestAnimationFrame(() => {
403
+ this.rafId = void 0;
404
+ let needsUpdate = false;
405
+ this.lines.forEach((line) => {
406
+ const renderData = line.getData(WorkflowLineRenderData);
407
+ const oldVersion = renderData.renderVersion;
408
+ renderData.update();
409
+ if (renderData.renderVersion !== oldVersion) {
410
+ needsUpdate = true;
411
+ }
412
+ });
413
+ if (needsUpdate) {
414
+ this.render();
415
+ }
416
+ });
417
+ }
418
+ // 用来绕过 memo
419
+ bumpVersion() {
420
+ this._version = this._version + 1;
421
+ if (this._version === Number.MAX_SAFE_INTEGER) {
422
+ this._version = 0;
423
+ }
424
+ }
425
+ lineProps(line) {
426
+ const { lineType } = this.workflowDocument.linesManager;
427
+ const selected = this.selectService.isSelected(line.id);
428
+ const hovered = this.hoverService.isHovered(line.id);
429
+ const version = this.lineVersion(line);
430
+ const oldProps = {
431
+ key: line.id,
432
+ color: line.color,
433
+ selected,
434
+ hovered,
435
+ line,
436
+ lineType,
437
+ version,
438
+ strokePrefix: this.layerID,
439
+ rendererRegistry: this.rendererRegistry
440
+ };
441
+ return this.options.customLineProps ? this.options.customLineProps(line, oldProps) : oldProps;
442
+ }
443
+ lineVersion(line) {
444
+ const renderData = line.getData(WorkflowLineRenderData);
445
+ const { renderVersion } = renderData;
446
+ const selected = this.selectService.isSelected(line.id);
447
+ const hovered = this.hoverService.isHovered(line.id);
448
+ const { version: lineVersion, color } = line;
449
+ const version = `v:${this._version},lv:${lineVersion},rv:${renderVersion},c:${color},s:${selected ? "T" : "F"},h:${hovered ? "T" : "F"}`;
450
+ return version;
451
+ }
452
+ lineComponent(props) {
453
+ const RenderInsideLine = this.options.renderInsideLine;
454
+ const RenderLine = this.options.renderLine ?? LineSVG;
455
+ const inside = RenderInsideLine ? h(RenderInsideLine, props) : null;
456
+ return h(RenderLine, props, () => inside);
457
+ }
458
+ renderLine(line) {
459
+ const lineProps = this.lineProps(line);
460
+ const cache = this.mountedLines.get(line.id);
461
+ const isCached = cache !== void 0;
462
+ const { portal: cachedPortal, version: cachedVersion } = cache ?? {};
463
+ if (isCached && cachedVersion === lineProps.version) {
464
+ return cachedPortal;
465
+ }
466
+ if (!isCached) {
467
+ this.renderElement.appendChild(line.node);
468
+ line.onDispose(() => {
469
+ this.mountedLines.delete(line.id);
470
+ line.node.remove();
471
+ });
472
+ }
473
+ const portal = h(Teleport, { to: line.node, key: line.id }, [this.lineComponent(lineProps)]);
474
+ this.mountedLines.set(line.id, { line, portal, version: lineProps.version });
475
+ return portal;
476
+ }
477
+ get renderElement() {
478
+ return this.stackContext.node;
479
+ }
480
+ };
481
+ WorkflowLinesLayer.type = "WorkflowLinesLayer";
482
+ __decorateClass([
483
+ inject(WorkflowHoverService)
484
+ ], WorkflowLinesLayer.prototype, "hoverService", 2);
485
+ __decorateClass([
486
+ inject(WorkflowSelectService)
487
+ ], WorkflowLinesLayer.prototype, "selectService", 2);
488
+ __decorateClass([
489
+ inject(StackingContextManager)
490
+ ], WorkflowLinesLayer.prototype, "stackContext", 2);
491
+ __decorateClass([
492
+ inject(FlowRendererRegistry)
493
+ ], WorkflowLinesLayer.prototype, "rendererRegistry", 2);
494
+ __decorateClass([
495
+ inject(PlaygroundContainerFactory)
496
+ ], WorkflowLinesLayer.prototype, "playgroundContainer", 2);
497
+ __decorateClass([
498
+ observeEntities(WorkflowLineEntity)
499
+ ], WorkflowLinesLayer.prototype, "lines", 2);
500
+ __decorateClass([
501
+ observeEntities(WorkflowPortEntity)
502
+ ], WorkflowLinesLayer.prototype, "ports", 2);
503
+ __decorateClass([
504
+ observeEntityDatas(WorkflowNodeEntity, TransformData)
505
+ ], WorkflowLinesLayer.prototype, "trans", 2);
506
+ __decorateClass([
507
+ inject(WorkflowDocument)
508
+ ], WorkflowLinesLayer.prototype, "workflowDocument", 2);
509
+ WorkflowLinesLayer = __decorateClass([
510
+ injectable()
511
+ ], WorkflowLinesLayer);
512
+
513
+ // src/contributions/bezier/bezier-controls.ts
514
+ function getBezierEdgeCenter(fromPos, toPos, fromControl, toControl) {
515
+ const x = fromPos.x * 0.125 + fromControl.x * 0.375 + toControl.x * 0.375 + toPos.x * 0.125;
516
+ const y = fromPos.y * 0.125 + fromControl.y * 0.375 + toControl.y * 0.375 + toPos.y * 0.125;
517
+ return {
518
+ x,
519
+ y
520
+ };
521
+ }
522
+ function getControlOffset(distance, curvature) {
523
+ if (distance >= 0) {
524
+ return 0.5 * distance;
525
+ }
526
+ return curvature * 25 * Math.sqrt(-distance);
527
+ }
528
+ function getControlWithCurvature({
529
+ location,
530
+ x1,
531
+ y1,
532
+ x2,
533
+ y2,
534
+ curvature
535
+ }) {
536
+ switch (location) {
537
+ case "left":
538
+ return {
539
+ x: x1 - getControlOffset(x1 - x2, curvature),
540
+ y: y1
541
+ };
542
+ case "right":
543
+ return {
544
+ x: x1 + getControlOffset(x2 - x1, curvature),
545
+ y: y1
546
+ };
547
+ case "top":
548
+ return {
549
+ x: x1,
550
+ y: y1 - getControlOffset(y1 - y2, curvature)
551
+ };
552
+ case "bottom":
553
+ return {
554
+ x: x1,
555
+ y: y1 + getControlOffset(y2 - y1, curvature)
556
+ };
557
+ }
558
+ }
559
+ function getBezierControlPoints(fromPos, toPos, curvature = 0.25) {
560
+ const fromControl = getControlWithCurvature({
561
+ location: fromPos.location,
562
+ x1: fromPos.x,
563
+ y1: fromPos.y,
564
+ x2: toPos.x,
565
+ y2: toPos.y,
566
+ curvature
567
+ });
568
+ const toControl = getControlWithCurvature({
569
+ location: toPos.location,
570
+ x1: toPos.x,
571
+ y1: toPos.y,
572
+ x2: fromPos.x,
573
+ y2: fromPos.y,
574
+ curvature
575
+ });
576
+ const center = getBezierEdgeCenter(fromPos, toPos, fromControl, toControl);
577
+ return {
578
+ controls: [fromControl, toControl],
579
+ center
580
+ };
581
+ }
582
+
583
+ // src/contributions/bezier/index.ts
584
+ var WorkflowBezierLineContribution = class {
585
+ constructor(entity) {
586
+ this.entity = entity;
587
+ }
588
+ get path() {
589
+ return this.data?.path ?? "";
590
+ }
591
+ calcDistance(pos) {
592
+ if (!this.data) {
593
+ return Number.MAX_SAFE_INTEGER;
594
+ }
595
+ return Point.getDistance(pos, this.data.bezier.project(pos));
596
+ }
597
+ get bounds() {
598
+ if (!this.data) {
599
+ return Rectangle.EMPTY;
600
+ }
601
+ return this.data.bbox;
602
+ }
603
+ get center() {
604
+ return this.data?.center;
605
+ }
606
+ update(params) {
607
+ this.data = this.calcBezier(params.fromPos, params.toPos);
608
+ }
609
+ calcBezier(fromPos, toPos) {
610
+ const { controls, center } = getBezierControlPoints(
611
+ fromPos,
612
+ toPos,
613
+ this.entity.uiState.curvature
614
+ );
615
+ const bezier = new Bezier([fromPos, ...controls, toPos]);
616
+ const bbox = bezier.bbox();
617
+ const bboxBounds = new Rectangle(
618
+ bbox.x.min,
619
+ bbox.y.min,
620
+ bbox.x.max - bbox.x.min,
621
+ bbox.y.max - bbox.y.min
622
+ );
623
+ const centerPoint = toRelative(center, bboxBounds);
624
+ const path = this.getPath({ bbox: bboxBounds, fromPos, toPos, controls });
625
+ this.data = {
626
+ fromPos,
627
+ toPos,
628
+ bezier,
629
+ bbox: bboxBounds,
630
+ controls,
631
+ path,
632
+ center: {
633
+ ...center,
634
+ labelX: centerPoint.x,
635
+ labelY: centerPoint.y
636
+ }
637
+ };
638
+ return this.data;
639
+ }
640
+ getPath(params) {
641
+ const { bbox } = params;
642
+ const fromPos = toRelative(params.fromPos, bbox);
643
+ const toPos = toRelative(params.toPos, bbox);
644
+ const controls = params.controls.map((c) => toRelative(c, bbox));
645
+ const shrink = this.entity.uiState.shrink;
646
+ const renderFromPos = posWithShrink(fromPos, params.fromPos.location, shrink);
647
+ const renderToPos = posWithShrink(toPos, params.toPos.location, shrink);
648
+ const controlPoints = controls.map((s) => `${s.x} ${s.y}`).join(",");
649
+ return `M${renderFromPos.x} ${renderFromPos.y} C ${controlPoints}, ${renderToPos.x} ${renderToPos.y}`;
650
+ }
651
+ };
652
+ WorkflowBezierLineContribution.type = LineType.BEZIER;
653
+ var getPointToSegmentDistance = (point, segStart, segEnd) => {
654
+ const { x: px, y: py } = point;
655
+ const { x: x1, y: y1 } = segStart;
656
+ const { x: x2, y: y2 } = segEnd;
657
+ const A = px - x1;
658
+ const B = py - y1;
659
+ const C = x2 - x1;
660
+ const D = y2 - y1;
661
+ const dot = A * C + B * D;
662
+ const lenSq = C * C + D * D;
663
+ const param = lenSq === 0 ? -1 : dot / lenSq;
664
+ let xx;
665
+ let yy;
666
+ if (param < 0) {
667
+ xx = x1;
668
+ yy = y1;
669
+ } else if (param > 1) {
670
+ xx = x2;
671
+ yy = y2;
672
+ } else {
673
+ xx = x1 + param * C;
674
+ yy = y1 + param * D;
675
+ }
676
+ const dx = px - xx;
677
+ const dy = py - yy;
678
+ return Math.sqrt(dx * dx + dy * dy);
679
+ };
680
+ var FoldLine;
681
+ ((FoldLine2) => {
682
+ const EDGE_RADIUS = 5;
683
+ const OFFSET = 20;
684
+ function getEdgeCenter({ source, target }) {
685
+ const xOffset = Math.abs(target.x - source.x) / 2;
686
+ const centerX = target.x < source.x ? target.x + xOffset : target.x - xOffset;
687
+ const yOffset = Math.abs(target.y - source.y) / 2;
688
+ const centerY = target.y < source.y ? target.y + yOffset : target.y - yOffset;
689
+ return [centerX, centerY];
690
+ }
691
+ const getDirection = ({ source, target }) => {
692
+ if (source.location === "left" || source.location === "right") {
693
+ return source.x < target.x ? { x: 1, y: 0 } : { x: -1, y: 0 };
694
+ }
695
+ return source.y < target.y ? { x: 0, y: 1 } : { x: 0, y: -1 };
696
+ };
697
+ const handleDirections = {
698
+ left: { x: -1, y: 0 },
699
+ right: { x: 1, y: 0 },
700
+ top: { x: 0, y: -1 },
701
+ bottom: { x: 0, y: 1 }
702
+ };
703
+ function getPoints({ source, target }) {
704
+ const sourceDir = handleDirections[source.location];
705
+ const targetDir = handleDirections[target.location];
706
+ const sourceGapped = {
707
+ x: source.x + sourceDir.x * OFFSET,
708
+ y: source.y + sourceDir.y * OFFSET,
709
+ location: source.location
710
+ };
711
+ const targetGapped = {
712
+ x: target.x + targetDir.x * OFFSET,
713
+ y: target.y + targetDir.y * OFFSET,
714
+ location: target.location
715
+ };
716
+ const dir = getDirection({
717
+ source: sourceGapped,
718
+ target: targetGapped
719
+ });
720
+ const dirAccessor = dir.x !== 0 ? "x" : "y";
721
+ const currDir = dir[dirAccessor];
722
+ let points = [];
723
+ let centerX, centerY;
724
+ const [defaultCenterX, defaultCenterY] = getEdgeCenter({
725
+ source,
726
+ target
727
+ });
728
+ if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
729
+ centerX = defaultCenterX;
730
+ centerY = defaultCenterY;
731
+ const verticalSplit = [
732
+ { x: centerX, y: sourceGapped.y },
733
+ { x: centerX, y: targetGapped.y }
734
+ ];
735
+ const horizontalSplit = [
736
+ { x: sourceGapped.x, y: centerY },
737
+ { x: targetGapped.x, y: centerY }
738
+ ];
739
+ if (sourceDir[dirAccessor] === currDir) {
740
+ points = dirAccessor === "x" ? verticalSplit : horizontalSplit;
741
+ } else {
742
+ points = dirAccessor === "x" ? horizontalSplit : verticalSplit;
743
+ }
744
+ } else {
745
+ const sourceTarget = [{ x: sourceGapped.x, y: targetGapped.y }];
746
+ const targetSource = [{ x: targetGapped.x, y: sourceGapped.y }];
747
+ if (dirAccessor === "x") {
748
+ points = sourceDir.x === currDir ? targetSource : sourceTarget;
749
+ } else {
750
+ points = sourceDir.y === currDir ? sourceTarget : targetSource;
751
+ }
752
+ const dirAccessorOpposite = dirAccessor === "x" ? "y" : "x";
753
+ const isSameDir = sourceDir[dirAccessor] === targetDir[dirAccessorOpposite];
754
+ const sourceGtTargetOppo = sourceGapped[dirAccessorOpposite] > targetGapped[dirAccessorOpposite];
755
+ const sourceLtTargetOppo = sourceGapped[dirAccessorOpposite] < targetGapped[dirAccessorOpposite];
756
+ const flipSourceTarget = sourceDir[dirAccessor] === 1 && (!isSameDir && sourceGtTargetOppo || isSameDir && sourceLtTargetOppo) || sourceDir[dirAccessor] !== 1 && (!isSameDir && sourceLtTargetOppo || isSameDir && sourceGtTargetOppo);
757
+ if (flipSourceTarget) {
758
+ points = dirAccessor === "x" ? sourceTarget : targetSource;
759
+ }
760
+ const sourceGapPoint = { x: sourceGapped.x, y: sourceGapped.y };
761
+ const targetGapPoint = { x: targetGapped.x, y: targetGapped.y };
762
+ const maxXDistance = Math.max(
763
+ Math.abs(sourceGapPoint.x - points[0].x),
764
+ Math.abs(targetGapPoint.x - points[0].x)
765
+ );
766
+ const maxYDistance = Math.max(
767
+ Math.abs(sourceGapPoint.y - points[0].y),
768
+ Math.abs(targetGapPoint.y - points[0].y)
769
+ );
770
+ if (maxXDistance >= maxYDistance) {
771
+ centerX = (sourceGapPoint.x + targetGapPoint.x) / 2;
772
+ centerY = points[0].y;
773
+ } else {
774
+ centerX = points[0].x;
775
+ centerY = (sourceGapPoint.y + targetGapPoint.y) / 2;
776
+ }
777
+ }
778
+ const pathPoints = [
779
+ source,
780
+ { x: sourceGapped.x, y: sourceGapped.y },
781
+ ...points,
782
+ { x: targetGapped.x, y: targetGapped.y },
783
+ target
784
+ ];
785
+ return {
786
+ points: pathPoints,
787
+ center: {
788
+ x: centerX,
789
+ y: centerY
790
+ }
791
+ };
792
+ }
793
+ FoldLine2.getPoints = getPoints;
794
+ function getBend(a, b, c) {
795
+ const bendSize = Math.min(
796
+ Point.getDistance(a, b) / 2,
797
+ Point.getDistance(b, c) / 2,
798
+ EDGE_RADIUS
799
+ );
800
+ const { x, y } = b;
801
+ if (a.x === x && x === c.x || a.y === y && y === c.y) {
802
+ return `L${x} ${y}`;
803
+ }
804
+ if (a.y === y) {
805
+ const xDir2 = a.x < c.x ? -1 : 1;
806
+ const yDir2 = a.y < c.y ? 1 : -1;
807
+ return `L ${x + bendSize * xDir2},${y}Q ${x},${y} ${x},${y + bendSize * yDir2}`;
808
+ }
809
+ const xDir = a.x < c.x ? 1 : -1;
810
+ const yDir = a.y < c.y ? -1 : 1;
811
+ return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`;
812
+ }
813
+ function getSmoothStepPath(points) {
814
+ const path = points.reduce((res, p, i) => {
815
+ let segment = "";
816
+ if (i > 0 && i < points.length - 1) {
817
+ segment = getBend(points[i - 1], p, points[i + 1]);
818
+ } else {
819
+ segment = `${i === 0 ? "M" : "L"}${p.x} ${p.y}`;
820
+ }
821
+ res += segment;
822
+ return res;
823
+ }, "");
824
+ return path;
825
+ }
826
+ FoldLine2.getSmoothStepPath = getSmoothStepPath;
827
+ function getBounds(points) {
828
+ const xList = points.map((p) => p.x);
829
+ const yList = points.map((p) => p.y);
830
+ const left = Math.min(...xList);
831
+ const right = Math.max(...xList);
832
+ const top = Math.min(...yList);
833
+ const bottom = Math.max(...yList);
834
+ return Rectangle.createRectangleWithTwoPoints(
835
+ {
836
+ x: left,
837
+ y: top
838
+ },
839
+ {
840
+ x: right,
841
+ y: bottom
842
+ }
843
+ );
844
+ }
845
+ FoldLine2.getBounds = getBounds;
846
+ FoldLine2.getFoldLineToPointDistance = (points, pos) => {
847
+ if (points.length === 0) {
848
+ return Infinity;
849
+ }
850
+ if (points.length === 1) {
851
+ return Point.getDistance(points[0], pos);
852
+ }
853
+ const lines = [];
854
+ for (let i = 0; i < points.length - 1; i++) {
855
+ lines.push([points[i], points[i + 1]]);
856
+ }
857
+ const distances = lines.map((line) => {
858
+ const [p1, p2] = line;
859
+ return getPointToSegmentDistance(pos, p1, p2);
860
+ });
861
+ return Math.min(...distances);
862
+ };
863
+ })(FoldLine || (FoldLine = {}));
864
+
865
+ // src/contributions/fold/index.ts
866
+ var WorkflowFoldLineContribution = class {
867
+ constructor(entity) {
868
+ this.entity = entity;
869
+ }
870
+ get path() {
871
+ return this.data?.path ?? "";
872
+ }
873
+ calcDistance(pos) {
874
+ if (!this.data) {
875
+ return Number.MAX_SAFE_INTEGER;
876
+ }
877
+ return FoldLine.getFoldLineToPointDistance(this.data.points, pos);
878
+ }
879
+ get bounds() {
880
+ if (!this.data) {
881
+ return new Rectangle();
882
+ }
883
+ return this.data.bbox;
884
+ }
885
+ get center() {
886
+ return this.data?.center;
887
+ }
888
+ update(params) {
889
+ const { fromPos, toPos } = params;
890
+ const shrink = this.entity.uiState.shrink;
891
+ const source = posWithShrink(fromPos, fromPos.location, shrink);
892
+ const target = posWithShrink(toPos, toPos.location, shrink);
893
+ const { points, center } = FoldLine.getPoints({
894
+ source: {
895
+ ...source,
896
+ location: fromPos.location
897
+ },
898
+ target: {
899
+ ...target,
900
+ location: toPos.location
901
+ }
902
+ });
903
+ const bbox = FoldLine.getBounds(points);
904
+ const adjustedPoints = points.map((p) => toRelative(p, bbox));
905
+ const path = FoldLine.getSmoothStepPath(adjustedPoints);
906
+ const relativeCenter = toRelative(center, bbox);
907
+ this.data = {
908
+ points,
909
+ path,
910
+ bbox,
911
+ center: {
912
+ x: center.x,
913
+ y: center.y,
914
+ labelX: relativeCenter.x,
915
+ labelY: relativeCenter.y
916
+ }
917
+ };
918
+ }
919
+ };
920
+ WorkflowFoldLineContribution.type = LineType.LINE_CHART;
921
+
922
+ // src/contributions/straight/point-on-line.ts
923
+ function projectPointOnLine(point, lineStart, lineEnd) {
924
+ const dx = lineEnd.x - lineStart.x;
925
+ const dy = lineEnd.y - lineStart.y;
926
+ if (dx === 0) {
927
+ return { x: lineStart.x, y: point.y };
928
+ }
929
+ if (dy === 0) {
930
+ return { x: point.x, y: lineStart.y };
931
+ }
932
+ const t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / (dx * dx + dy * dy);
933
+ const clampedT = Math.max(0, Math.min(1, t));
934
+ return {
935
+ x: lineStart.x + clampedT * dx,
936
+ y: lineStart.y + clampedT * dy
937
+ };
938
+ }
939
+
940
+ // src/contributions/straight/index.ts
941
+ var WorkflowStraightLineContribution = class {
942
+ constructor(entity) {
943
+ this.entity = entity;
944
+ }
945
+ get path() {
946
+ return this.data?.path ?? "";
947
+ }
948
+ calcDistance(pos) {
949
+ if (!this.data) {
950
+ return Number.MAX_SAFE_INTEGER;
951
+ }
952
+ const [start, end] = this.data.points;
953
+ return Point.getDistance(pos, projectPointOnLine(pos, start, end));
954
+ }
955
+ get bounds() {
956
+ if (!this.data) {
957
+ return new Rectangle();
958
+ }
959
+ return this.data.bbox;
960
+ }
961
+ get center() {
962
+ return this.data?.center;
963
+ }
964
+ update(params) {
965
+ const { fromPos, toPos } = params;
966
+ const shrink = this.entity.uiState.shrink;
967
+ const source = posWithShrink(fromPos, fromPos.location, shrink);
968
+ const target = posWithShrink(toPos, toPos.location, shrink);
969
+ const points = [source, target];
970
+ const bbox = Rectangle.createRectangleWithTwoPoints(points[0], points[1]);
971
+ const adjustedPoints = points.map((p) => ({
972
+ x: p.x - bbox.x + LINE_PADDING,
973
+ y: p.y - bbox.y + LINE_PADDING
974
+ }));
975
+ const path = `M ${adjustedPoints[0].x} ${adjustedPoints[0].y} L ${adjustedPoints[1].x} ${adjustedPoints[1].y}`;
976
+ this.data = {
977
+ points,
978
+ path,
979
+ bbox,
980
+ center: getLineCenter(fromPos, toPos, bbox, LINE_PADDING)
981
+ };
982
+ }
983
+ };
984
+ WorkflowStraightLineContribution.type = LineType.STRAIGHT;
985
+
986
+ // src/create-free-lines-plugin.ts
987
+ var createFreeLinesPlugin = definePluginCreator({
988
+ singleton: true,
989
+ onInit: (ctx, opts) => {
990
+ ctx.playground.registerLayer(WorkflowLinesLayer, {
991
+ ...opts
992
+ });
993
+ if (opts.defaultLineUIState) {
994
+ ctx.container.get(WorkflowLinesManager).setDefaultUIState(opts.defaultLineUIState);
995
+ }
996
+ },
997
+ onReady: (ctx, opts) => {
998
+ const linesManager = ctx.container.get(WorkflowLinesManager);
999
+ linesManager.registerContribution(WorkflowBezierLineContribution).registerContribution(WorkflowFoldLineContribution).registerContribution(WorkflowStraightLineContribution);
1000
+ if (opts.contributions) {
1001
+ opts.contributions.forEach((contribution) => {
1002
+ linesManager.registerContribution(contribution);
1003
+ });
1004
+ }
1005
+ if (opts.defaultLineType) {
1006
+ linesManager.switchLineType(opts.defaultLineType);
1007
+ }
1008
+ }
1009
+ });
1010
+
1011
+ export { LINE_OFFSET, LINE_PADDING, WorkflowLinesLayer as LinesLayer, WorkflowBezierLineContribution, WorkflowFoldLineContribution, WorkflowLinesLayer, WorkflowPortRender, WorkflowStraightLineContribution, createFreeLinesPlugin };
1012
+ //# sourceMappingURL=index.js.map
1013
+ //# sourceMappingURL=index.js.map