@leavepulse/ui 0.22.11 → 0.22.13
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/chunks/{LpFileTree-DWx77B1F.js → LpFileTree-Cw50eG_y.js} +2 -2
- package/dist/chunks/{LpTopologyCanvas-BTzQE78D.js → LpTopologyCanvas-ebP67CLx.js} +4 -0
- package/dist/chunks/LpWaypointEdge-D8yHlQiw.js +67 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.d.ts +9 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpWaypointEdge.vue.d.ts +21 -0
- package/dist/components/LpWaypointEdge.vue.js +4 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/src/component-names.ts +2 -1
- package/src/components/LpFileTree.vue +6 -4
- package/src/components/LpTopologyCanvas.vue +12 -1
- package/src/components/LpWaypointEdge.vue +77 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent, ref, watch, computed, openBlock, createElementBlock, Fragment, renderList, normalizeStyle, createBlock, createVNode, withCtx, createElementVNode, renderSlot, mergeProps, normalizeProps, guardReactiveProps, createTextVNode, toDisplayString, createCommentVNode } from "vue";
|
|
2
|
-
import { formatSize, sortNodes,
|
|
2
|
+
import { computeStats, formatSize, sortNodes, subtreeIds, ancestorIds } from "../components/fileTree.js";
|
|
3
3
|
import { _ as _sfc_main$1 } from "./LpEmptyState-CrfyRJUG.js";
|
|
4
4
|
import { _ as _sfc_main$3 } from "./LpFileTreeNode-DFg4PhOK.js";
|
|
5
5
|
import { _ as _sfc_main$2 } from "./LpScrollArea-D8xyKXcF.js";
|
|
@@ -67,6 +67,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
67
67
|
return { byId: byId2, parentOfId };
|
|
68
68
|
});
|
|
69
69
|
const byId = computed(() => index.value.byId);
|
|
70
|
+
const stats = computed(() => computeStats(props.nodes, checkedIds.value));
|
|
70
71
|
const summary = computed(() => {
|
|
71
72
|
let files = 0;
|
|
72
73
|
let dirs = 0;
|
|
@@ -146,7 +147,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
146
147
|
}
|
|
147
148
|
const requested = /* @__PURE__ */ new Set();
|
|
148
149
|
const roots = computed(() => props.sort ? sortNodes(props.nodes) : props.nodes);
|
|
149
|
-
const stats = computed(() => computeStats(props.nodes, checkedIds.value));
|
|
150
150
|
const visible = computed(() => {
|
|
151
151
|
const out = [];
|
|
152
152
|
function walk(list) {
|
|
@@ -6,6 +6,7 @@ import { MiniMap } from "@vue-flow/minimap";
|
|
|
6
6
|
import { _ as _sfc_main$3 } from "./LpInfraNode-qIL3l7IO.js";
|
|
7
7
|
import { _ as _sfc_main$1 } from "./LpLaneNode-HtnQf2FF.js";
|
|
8
8
|
import { _ as _sfc_main$2 } from "./LpServiceNode-D8uJ8sGk.js";
|
|
9
|
+
import { _ as _sfc_main$4 } from "./LpWaypointEdge-D8yHlQiw.js";
|
|
9
10
|
const _hoisted_1 = { class: "flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur" };
|
|
10
11
|
const _hoisted_2 = {
|
|
11
12
|
width: "22",
|
|
@@ -38,6 +39,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
38
39
|
service: markRaw(_sfc_main$2),
|
|
39
40
|
lane: markRaw(_sfc_main$1)
|
|
40
41
|
};
|
|
42
|
+
const edgeTypes = { waypoints: markRaw(_sfc_main$4) };
|
|
41
43
|
const laneNodes = computed(
|
|
42
44
|
() => props.lanes.map(
|
|
43
45
|
(l) => ({
|
|
@@ -107,6 +109,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
107
109
|
source: e.source,
|
|
108
110
|
target: e.target,
|
|
109
111
|
type: props.edgeType ?? "default",
|
|
112
|
+
data: { waypoints: e.waypoints },
|
|
110
113
|
animated: obs !== "applied" && !e.dimmed,
|
|
111
114
|
label: e.kind,
|
|
112
115
|
labelBgStyle: { fill: "var(--color-surface)" },
|
|
@@ -189,6 +192,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
189
192
|
nodes: flowNodes.value,
|
|
190
193
|
edges: flowEdges.value,
|
|
191
194
|
"node-types": nodeTypes,
|
|
195
|
+
"edge-types": edgeTypes,
|
|
192
196
|
"nodes-connectable": __props.connectable,
|
|
193
197
|
"selection-on-drag": __props.selectable,
|
|
194
198
|
"pan-on-drag": __props.selectable ? [1, 2] : true,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineComponent, computed, openBlock, createBlock, unref, normalizeStyle } from "vue";
|
|
2
|
+
import { getBezierPath, Position, BaseEdge } from "@vue-flow/core";
|
|
3
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
4
|
+
__name: "LpWaypointEdge",
|
|
5
|
+
props: {
|
|
6
|
+
id: {},
|
|
7
|
+
sourceX: {},
|
|
8
|
+
sourceY: {},
|
|
9
|
+
targetX: {},
|
|
10
|
+
targetY: {},
|
|
11
|
+
sourcePosition: {},
|
|
12
|
+
targetPosition: {},
|
|
13
|
+
data: {},
|
|
14
|
+
markerEnd: {},
|
|
15
|
+
style: {}
|
|
16
|
+
},
|
|
17
|
+
setup(__props) {
|
|
18
|
+
const props = __props;
|
|
19
|
+
function splineThrough(points) {
|
|
20
|
+
if (points.length < 2) return "";
|
|
21
|
+
const p = [points[0], ...points, points[points.length - 1]];
|
|
22
|
+
let d = `M${points[0].x},${points[0].y}`;
|
|
23
|
+
for (let i = 1; i < p.length - 2; i++) {
|
|
24
|
+
const p0 = p[i - 1];
|
|
25
|
+
const p1 = p[i];
|
|
26
|
+
const p2 = p[i + 1];
|
|
27
|
+
const p3 = p[i + 2];
|
|
28
|
+
const c1x = p1.x + (p2.x - p0.x) / 6;
|
|
29
|
+
const c1y = p1.y + (p2.y - p0.y) / 6;
|
|
30
|
+
const c2x = p2.x - (p3.x - p1.x) / 6;
|
|
31
|
+
const c2y = p2.y - (p3.y - p1.y) / 6;
|
|
32
|
+
d += ` C${c1x},${c1y} ${c2x},${c2y} ${p2.x},${p2.y}`;
|
|
33
|
+
}
|
|
34
|
+
return d;
|
|
35
|
+
}
|
|
36
|
+
const path = computed(() => {
|
|
37
|
+
const via = props.data?.waypoints ?? [];
|
|
38
|
+
if (!via.length) {
|
|
39
|
+
const [d] = getBezierPath({
|
|
40
|
+
sourceX: props.sourceX,
|
|
41
|
+
sourceY: props.sourceY,
|
|
42
|
+
targetX: props.targetX,
|
|
43
|
+
targetY: props.targetY,
|
|
44
|
+
sourcePosition: props.sourcePosition ?? Position.Right,
|
|
45
|
+
targetPosition: props.targetPosition ?? Position.Left
|
|
46
|
+
});
|
|
47
|
+
return d;
|
|
48
|
+
}
|
|
49
|
+
return splineThrough([
|
|
50
|
+
{ x: props.sourceX, y: props.sourceY },
|
|
51
|
+
...via,
|
|
52
|
+
{ x: props.targetX, y: props.targetY }
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
return (_ctx, _cache) => {
|
|
56
|
+
return openBlock(), createBlock(unref(BaseEdge), {
|
|
57
|
+
id: __props.id,
|
|
58
|
+
path: path.value,
|
|
59
|
+
"marker-end": __props.markerEnd,
|
|
60
|
+
style: normalizeStyle(__props.style)
|
|
61
|
+
}, null, 8, ["id", "path", "marker-end", "style"]);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
export {
|
|
66
|
+
_sfc_main as _
|
|
67
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const COMPONENT_NAMES: readonly ["LayoutCanvas", "LayoutNode", "LpAddressInput", "LpAlert", "LpAppShell", "LpAutocomplete", "LpAvatar", "LpBadge", "LpBreadcrumbs", "LpButton", "LpCalendar", "LpCard", "LpCheckbox", "LpCodeBlock", "LpCommandPalette", "LpConfirmDialog", "LpContextMenu", "LpDatePicker", "LpDisclosure", "LpDivider", "LpDrawer", "LpDropdownMenu", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "LpInfraNode", "LpInput", "LpLaneNode", "LpLightbox", "LpLink", "LpLogViewer", "LpMapPicker", "LpModal", "LpNotificationBell", "LpNumberField", "LpOtpInput", "LpPagination", "LpPasswordInput", "LpPhoneInput", "LpPopover", "LpProgress", "LpRadio", "LpRadioGroup", "LpScrollArea", "LpSegmented", "LpSelect", "LpServiceNode", "LpShortcutsDialog", "LpSidebar", "LpSidebarNav", "LpSkeleton", "LpSlider", "LpStat", "LpStepper", "LpSwitch", "LpTable", "LpTableOfContents", "LpTabs", "LpTextarea", "LpThemeSwitcher", "LpTilt", "LpToaster", "LpTooltip", "LpTopologyCanvas", "LpUptimeBar"];
|
|
1
|
+
export declare const COMPONENT_NAMES: readonly ["LayoutCanvas", "LayoutNode", "LpAddressInput", "LpAlert", "LpAppShell", "LpAutocomplete", "LpAvatar", "LpBadge", "LpBreadcrumbs", "LpButton", "LpCalendar", "LpCard", "LpCheckbox", "LpCodeBlock", "LpCommandPalette", "LpConfirmDialog", "LpContextMenu", "LpDatePicker", "LpDisclosure", "LpDivider", "LpDrawer", "LpDropdownMenu", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "LpInfraNode", "LpInput", "LpLaneNode", "LpLightbox", "LpLink", "LpLogViewer", "LpMapPicker", "LpModal", "LpNotificationBell", "LpNumberField", "LpOtpInput", "LpPagination", "LpPasswordInput", "LpPhoneInput", "LpPopover", "LpProgress", "LpRadio", "LpRadioGroup", "LpScrollArea", "LpSegmented", "LpSelect", "LpServiceNode", "LpShortcutsDialog", "LpSidebar", "LpSidebarNav", "LpSkeleton", "LpSlider", "LpStat", "LpStepper", "LpSwitch", "LpTable", "LpTableOfContents", "LpTabs", "LpTextarea", "LpThemeSwitcher", "LpTilt", "LpToaster", "LpTooltip", "LpTopologyCanvas", "LpUptimeBar", "LpWaypointEdge"];
|
|
@@ -46,6 +46,14 @@ export interface TopologyEdge {
|
|
|
46
46
|
* every unrelated line at full strength.
|
|
47
47
|
*/
|
|
48
48
|
dimmed?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Points the edge should curve through, when the host's layout knows where
|
|
51
|
+
* the gaps are. Requires `edgeType: "waypoints"`; ignored otherwise.
|
|
52
|
+
*/
|
|
53
|
+
waypoints?: {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
}[];
|
|
49
57
|
}
|
|
50
58
|
/**
|
|
51
59
|
* A background swimlane: a sized zone (one per project / overlay network) drawn
|
|
@@ -96,7 +104,7 @@ type __VLS_Props = {
|
|
|
96
104
|
* LAYERED one — a long edge crosses the cards standing between its columns.
|
|
97
105
|
* `smoothstep` routes orthogonally through the gaps instead.
|
|
98
106
|
*/
|
|
99
|
-
edgeType?: "default" | "smoothstep" | "step" | "straight";
|
|
107
|
+
edgeType?: "default" | "smoothstep" | "step" | "straight" | "waypoints";
|
|
100
108
|
/** Rename legend rows to match what this graph's edges actually mean. */
|
|
101
109
|
legendLabels?: Partial<Record<"network" | "structural" | "pending" | "drift", string>>;
|
|
102
110
|
/**
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Position } from '@vue-flow/core';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
id: string;
|
|
4
|
+
sourceX: number;
|
|
5
|
+
sourceY: number;
|
|
6
|
+
targetX: number;
|
|
7
|
+
targetY: number;
|
|
8
|
+
sourcePosition?: Position;
|
|
9
|
+
targetPosition?: Position;
|
|
10
|
+
data?: {
|
|
11
|
+
waypoints?: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
16
|
+
markerEnd?: string;
|
|
17
|
+
style?: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
20
|
+
declare const _default: typeof __VLS_export;
|
|
21
|
+
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import { _ as _19 } from "./chunks/LpDivider-CUX46KR0.js";
|
|
|
22
22
|
import { _ as _20 } from "./chunks/LpDrawer-DAJN5rGK.js";
|
|
23
23
|
import { _ as _21 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
|
|
24
24
|
import { _ as _22 } from "./chunks/LpEmptyState-CrfyRJUG.js";
|
|
25
|
-
import { _ as _23 } from "./chunks/LpFileTree-
|
|
25
|
+
import { _ as _23 } from "./chunks/LpFileTree-Cw50eG_y.js";
|
|
26
26
|
import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
|
|
27
27
|
import { _ as _24 } from "./chunks/LpFormField-Bn-ZwC6z.js";
|
|
28
28
|
import { _ as _25 } from "./chunks/LpIcon-CCnX5_2j.js";
|
|
@@ -64,7 +64,7 @@ import { D, _ as _56, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v }
|
|
|
64
64
|
import { _ as _57 } from "./chunks/LpTilt-DuyKbpkA.js";
|
|
65
65
|
import { _ as _58 } from "./chunks/LpToaster-gDB1EaU3.js";
|
|
66
66
|
import { _ as _59 } from "./chunks/LpTooltip-D1hbjQWG.js";
|
|
67
|
-
import { _ as _60 } from "./chunks/LpTopologyCanvas-
|
|
67
|
+
import { _ as _60 } from "./chunks/LpTopologyCanvas-ebP67CLx.js";
|
|
68
68
|
import { _ as _61 } from "./chunks/LpUptimeBar-CB366k0O.js";
|
|
69
69
|
import { deserializeLayout, serializeLayout } from "./layout/tree.js";
|
|
70
70
|
import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
|
package/package.json
CHANGED
package/src/component-names.ts
CHANGED
|
@@ -155,6 +155,12 @@ const index = computed(() => {
|
|
|
155
155
|
|
|
156
156
|
const byId = computed(() => index.value.byId)
|
|
157
157
|
|
|
158
|
+
// One pass for the whole tree; every row reads its entry by id instead of
|
|
159
|
+
// walking its own subtree on each render. Declared before `summary`, which
|
|
160
|
+
// reads it and is watched eagerly — a `const` below that watcher is still in
|
|
161
|
+
// its temporal dead zone when the watcher first runs.
|
|
162
|
+
const stats = computed(() => computeStats(props.nodes, checkedIds.value))
|
|
163
|
+
|
|
158
164
|
/**
|
|
159
165
|
* What the ticked set adds up to. Directories are counted but not sized — their
|
|
160
166
|
* bytes arrive through the files inside them, so summing both would double-count.
|
|
@@ -268,10 +274,6 @@ const requested = new Set<string>()
|
|
|
268
274
|
|
|
269
275
|
const roots = computed(() => (props.sort ? sortNodes(props.nodes) : props.nodes))
|
|
270
276
|
|
|
271
|
-
// One pass for the whole tree; every row reads its entry by id instead of
|
|
272
|
-
// walking its own subtree on each render.
|
|
273
|
-
const stats = computed(() => computeStats(props.nodes, checkedIds.value))
|
|
274
|
-
|
|
275
277
|
/** Rows currently visible, in render order — the axis the arrow keys walk. */
|
|
276
278
|
const visible = computed(() => {
|
|
277
279
|
const out: FileNode[] = []
|
|
@@ -26,6 +26,7 @@ import { computed, markRaw, watch, type Component } from "vue"
|
|
|
26
26
|
import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
|
|
27
27
|
import LpLaneNode from "./LpLaneNode.vue"
|
|
28
28
|
import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
|
|
29
|
+
import LpWaypointEdge from "./LpWaypointEdge.vue"
|
|
29
30
|
|
|
30
31
|
// Vue Flow base CSS + canvas chrome theming is shipped via the kit's
|
|
31
32
|
// "@leavepulse/ui/canvas.css" export (a plain CSS file). Component-local CSS
|
|
@@ -77,6 +78,11 @@ export interface TopologyEdge {
|
|
|
77
78
|
* every unrelated line at full strength.
|
|
78
79
|
*/
|
|
79
80
|
dimmed?: boolean
|
|
81
|
+
/**
|
|
82
|
+
* Points the edge should curve through, when the host's layout knows where
|
|
83
|
+
* the gaps are. Requires `edgeType: "waypoints"`; ignored otherwise.
|
|
84
|
+
*/
|
|
85
|
+
waypoints?: { x: number; y: number }[]
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
/**
|
|
@@ -131,7 +137,7 @@ const props = withDefaults(
|
|
|
131
137
|
* LAYERED one — a long edge crosses the cards standing between its columns.
|
|
132
138
|
* `smoothstep` routes orthogonally through the gaps instead.
|
|
133
139
|
*/
|
|
134
|
-
edgeType?: "default" | "smoothstep" | "step" | "straight"
|
|
140
|
+
edgeType?: "default" | "smoothstep" | "step" | "straight" | "waypoints"
|
|
135
141
|
/** Rename legend rows to match what this graph's edges actually mean. */
|
|
136
142
|
legendLabels?: Partial<
|
|
137
143
|
Record<"network" | "structural" | "pending" | "drift", string>
|
|
@@ -192,6 +198,9 @@ const nodeTypes: NodeTypesObject = {
|
|
|
192
198
|
service: markRaw(LpServiceNode) as Component,
|
|
193
199
|
lane: markRaw(LpLaneNode) as Component,
|
|
194
200
|
}
|
|
201
|
+
// `waypoints` curves through the points an edge carries, for layouts that know
|
|
202
|
+
// where the gaps are; with none it falls back to the plain bezier.
|
|
203
|
+
const edgeTypes = { waypoints: markRaw(LpWaypointEdge) as Component }
|
|
195
204
|
|
|
196
205
|
// Lanes render as non-interactive Vue Flow nodes BENEATH the graph: prepended
|
|
197
206
|
// so they paint first, with selecting/dragging/connecting off and a low z so
|
|
@@ -276,6 +285,7 @@ const flowEdges = computed(() =>
|
|
|
276
285
|
source: e.source,
|
|
277
286
|
target: e.target,
|
|
278
287
|
type: props.edgeType ?? "default",
|
|
288
|
+
data: { waypoints: e.waypoints },
|
|
279
289
|
animated: obs !== "applied" && !e.dimmed,
|
|
280
290
|
label: e.kind,
|
|
281
291
|
labelBgStyle: { fill: "var(--color-surface)" },
|
|
@@ -382,6 +392,7 @@ defineExpose({ fitView, setViewport: applyViewport })
|
|
|
382
392
|
:nodes="flowNodes"
|
|
383
393
|
:edges="flowEdges"
|
|
384
394
|
:node-types="nodeTypes"
|
|
395
|
+
:edge-types="edgeTypes"
|
|
385
396
|
:nodes-connectable="connectable"
|
|
386
397
|
:selection-on-drag="selectable"
|
|
387
398
|
:pan-on-drag="selectable ? [1, 2] : true"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* An edge that curves THROUGH a list of waypoints.
|
|
4
|
+
*
|
|
5
|
+
* The built-in shapes each fail one way in a layered graph: a bezier bulges off
|
|
6
|
+
* the line between its ends and cuts through whatever stands between the
|
|
7
|
+
* columns, and orthogonal routing clears the cards but draws the graph as a
|
|
8
|
+
* grid of right angles. Given the gaps a layout already knows about, a curve
|
|
9
|
+
* threaded through them is both — clear of the cards and still a curve.
|
|
10
|
+
*
|
|
11
|
+
* Waypoints ride on the edge's `data.waypoints`; with none, this is a plain
|
|
12
|
+
* bezier, so the type is safe to use for every edge in a graph.
|
|
13
|
+
*/
|
|
14
|
+
import { BaseEdge, getBezierPath, Position } from "@vue-flow/core"
|
|
15
|
+
import { computed } from "vue"
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
id: string
|
|
19
|
+
sourceX: number
|
|
20
|
+
sourceY: number
|
|
21
|
+
targetX: number
|
|
22
|
+
targetY: number
|
|
23
|
+
sourcePosition?: Position
|
|
24
|
+
targetPosition?: Position
|
|
25
|
+
data?: { waypoints?: { x: number; y: number }[] }
|
|
26
|
+
markerEnd?: string
|
|
27
|
+
style?: Record<string, unknown>
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Catmull-Rom through the points, converted to cubic beziers — the standard way
|
|
32
|
+
* to get a curve that actually passes through its control points rather than
|
|
33
|
+
* being pulled towards them. Tension 0.5 keeps it taut enough not to loop back
|
|
34
|
+
* on itself between closely spaced waypoints.
|
|
35
|
+
*/
|
|
36
|
+
function splineThrough(points: { x: number; y: number }[]): string {
|
|
37
|
+
if (points.length < 2) return ""
|
|
38
|
+
const p = [points[0]!, ...points, points[points.length - 1]!]
|
|
39
|
+
let d = `M${points[0]!.x},${points[0]!.y}`
|
|
40
|
+
for (let i = 1; i < p.length - 2; i++) {
|
|
41
|
+
const p0 = p[i - 1]!
|
|
42
|
+
const p1 = p[i]!
|
|
43
|
+
const p2 = p[i + 1]!
|
|
44
|
+
const p3 = p[i + 2]!
|
|
45
|
+
const c1x = p1.x + (p2.x - p0.x) / 6
|
|
46
|
+
const c1y = p1.y + (p2.y - p0.y) / 6
|
|
47
|
+
const c2x = p2.x - (p3.x - p1.x) / 6
|
|
48
|
+
const c2y = p2.y - (p3.y - p1.y) / 6
|
|
49
|
+
d += ` C${c1x},${c1y} ${c2x},${c2y} ${p2.x},${p2.y}`
|
|
50
|
+
}
|
|
51
|
+
return d
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const path = computed(() => {
|
|
55
|
+
const via = props.data?.waypoints ?? []
|
|
56
|
+
if (!via.length) {
|
|
57
|
+
const [d] = getBezierPath({
|
|
58
|
+
sourceX: props.sourceX,
|
|
59
|
+
sourceY: props.sourceY,
|
|
60
|
+
targetX: props.targetX,
|
|
61
|
+
targetY: props.targetY,
|
|
62
|
+
sourcePosition: props.sourcePosition ?? Position.Right,
|
|
63
|
+
targetPosition: props.targetPosition ?? Position.Left,
|
|
64
|
+
})
|
|
65
|
+
return d
|
|
66
|
+
}
|
|
67
|
+
return splineThrough([
|
|
68
|
+
{ x: props.sourceX, y: props.sourceY },
|
|
69
|
+
...via,
|
|
70
|
+
{ x: props.targetX, y: props.targetY },
|
|
71
|
+
])
|
|
72
|
+
})
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<BaseEdge :id="id" :path="path" :marker-end="markerEnd" :style="style" />
|
|
77
|
+
</template>
|