@mx-sose-front/mx-sose-graph 1.2.9 → 1.3.1
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.esm.js +94373 -1365
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/src/components/InteractionLayer.vue.d.ts.map +1 -1
- package/dist/src/components/Matrix/Matrix.vue.d.ts.map +1 -1
- package/dist/src/hooks/useConnectionPreview.d.ts +500 -0
- package/dist/src/hooks/useConnectionPreview.d.ts.map +1 -0
- package/dist/src/store/graphStore.d.ts +5 -1
- package/dist/src/store/graphStore.d.ts.map +1 -1
- package/dist/src/utils/autoLayout.d.ts +44 -0
- package/dist/src/utils/autoLayout.d.ts.map +1 -0
- package/dist/src/utils/pinUtils.d.ts +2 -0
- package/dist/src/utils/pinUtils.d.ts.map +1 -1
- package/dist/src/utils/shapeInitialBounds.d.ts +10 -0
- package/dist/src/utils/shapeInitialBounds.d.ts.map +1 -0
- package/dist/src/view/graph.vue.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/InteractionLayer.vue +34 -406
- package/src/components/Matrix/Matrix.vue +71 -5
- package/src/hooks/useConnectionPreview.ts +405 -0
- package/src/store/graphStore.ts +24 -0
- package/src/utils/autoLayout.ts +388 -0
- package/src/utils/pinUtils.ts +5 -0
- package/src/utils/shapeInitialBounds.ts +42 -0
- package/src/view/graph.vue +2 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="matrix">
|
|
2
|
+
<div ref="matrixRef" class="matrix">
|
|
3
3
|
|
|
4
4
|
<!-- 矩阵表格:左侧行树(文件夹+方框 / 橙圆C+三角),表头仅 1-7,虚线层级 -->
|
|
5
|
-
<div class="matrix-table-wrap">
|
|
5
|
+
<div class="matrix-table-wrap" :style="tableWrapStyle">
|
|
6
|
+
<div class="matrix-table-scroll">
|
|
6
7
|
<table class="matrix-table">
|
|
7
8
|
<thead>
|
|
8
9
|
<tr v-for="(row, rowIndex) in columnHeaderRows" :key="rowIndex">
|
|
@@ -147,6 +148,7 @@
|
|
|
147
148
|
</tr>
|
|
148
149
|
</tbody>
|
|
149
150
|
</table>
|
|
151
|
+
</div>
|
|
150
152
|
</div>
|
|
151
153
|
<!-- 矩阵右键菜单 -->
|
|
152
154
|
<MatrixContextMenu
|
|
@@ -162,7 +164,7 @@
|
|
|
162
164
|
</template>
|
|
163
165
|
|
|
164
166
|
<script setup lang="ts">
|
|
165
|
-
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
167
|
+
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
|
166
168
|
import type { TreeNode, ColumnHeaderCell } from '../../types'
|
|
167
169
|
import type { Matrix } from '../../types'
|
|
168
170
|
import {
|
|
@@ -177,6 +179,27 @@ import { useGraphStore } from '../../store/graphStore'
|
|
|
177
179
|
import { getIcon } from '../../utils/iconLoader'
|
|
178
180
|
import MatrixContextMenu from '../MatrixContextMenu/MatrixContextMenu.vue'
|
|
179
181
|
|
|
182
|
+
/** 表格滚动区高度 = .matrix 内容区高度 - 预留高度 */
|
|
183
|
+
const MATRIX_TABLE_WRAP_HEIGHT_OFFSET = 50
|
|
184
|
+
|
|
185
|
+
const matrixRef = ref<HTMLElement | null>(null)
|
|
186
|
+
const tableWrapHeight = ref<number | null>(null)
|
|
187
|
+
|
|
188
|
+
const tableWrapStyle = computed(() => {
|
|
189
|
+
if (tableWrapHeight.value == null) return undefined
|
|
190
|
+
return { height: `${tableWrapHeight.value}px` }
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
function updateTableWrapHeight(): void {
|
|
194
|
+
const el = matrixRef.value
|
|
195
|
+
if (!el) return
|
|
196
|
+
const h = el.clientHeight
|
|
197
|
+
if (h <= 0) return
|
|
198
|
+
tableWrapHeight.value = Math.max(0, h - MATRIX_TABLE_WRAP_HEIGHT_OFFSET)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let matrixResizeObserver: ResizeObserver | null = null
|
|
202
|
+
|
|
180
203
|
/** 当前选中的行 id、列 id(点击行头/列头/单元格时更新并通过 eventBus 发出) */
|
|
181
204
|
const selectedRowId = ref<string | null>(null)
|
|
182
205
|
const selectedColumnId = ref<string | null>(null)
|
|
@@ -228,12 +251,35 @@ function onMatrixDataChange(payload: Matrix): void {
|
|
|
228
251
|
}
|
|
229
252
|
|
|
230
253
|
const handleMatrixDataChange = (payload: Matrix) => onMatrixDataChange(payload)
|
|
254
|
+
|
|
255
|
+
function setupMatrixResizeObserver(): void {
|
|
256
|
+
const el = matrixRef.value
|
|
257
|
+
if (!el) return
|
|
258
|
+
updateTableWrapHeight()
|
|
259
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
260
|
+
matrixResizeObserver?.disconnect()
|
|
261
|
+
matrixResizeObserver = new ResizeObserver(() => updateTableWrapHeight())
|
|
262
|
+
matrixResizeObserver.observe(el)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function teardownMatrixResizeObserver(): void {
|
|
266
|
+
matrixResizeObserver?.disconnect()
|
|
267
|
+
matrixResizeObserver = null
|
|
268
|
+
}
|
|
269
|
+
|
|
231
270
|
onMounted(() => {
|
|
232
271
|
eventBus.on('matrix-data-change', handleMatrixDataChange)
|
|
272
|
+
nextTick(() => {
|
|
273
|
+
setupMatrixResizeObserver()
|
|
274
|
+
window.addEventListener('resize', updateTableWrapHeight)
|
|
275
|
+
})
|
|
233
276
|
})
|
|
234
277
|
onUnmounted(() => {
|
|
235
278
|
eventBus.off('matrix-data-change', handleMatrixDataChange)
|
|
279
|
+
teardownMatrixResizeObserver()
|
|
280
|
+
window.removeEventListener('resize', updateTableWrapHeight)
|
|
236
281
|
})
|
|
282
|
+
|
|
237
283
|
const rowTreeNodes = computed(() => matrixData.value.rowTree)
|
|
238
284
|
const columnTreeNodes = computed(() => matrixData.value.columnTree)
|
|
239
285
|
const matrixNodes = computed(() => matrixData.value.crossData)
|
|
@@ -283,6 +329,12 @@ const columnHeaderRows = computed((): ColHeaderCell[][] => {
|
|
|
283
329
|
})
|
|
284
330
|
})
|
|
285
331
|
|
|
332
|
+
watch(
|
|
333
|
+
[matrixData, columnHeaderRows, flattenedRows, rowExpandedIds, columnExpandedIds],
|
|
334
|
+
() => nextTick(updateTableWrapHeight),
|
|
335
|
+
{ deep: true },
|
|
336
|
+
)
|
|
337
|
+
|
|
286
338
|
/** 行树连接器:计算每行在每个祖先深度是否还有后续兄弟(用于画 │ 或 └) */
|
|
287
339
|
const flatRowsWithConnectors = computed(() => {
|
|
288
340
|
const items = flattenedRows.value
|
|
@@ -474,6 +526,8 @@ defineExpose({ setMatrixData })
|
|
|
474
526
|
.matrix {
|
|
475
527
|
width: 100%;
|
|
476
528
|
height: 100%;
|
|
529
|
+
min-width: 0;
|
|
530
|
+
min-height: 0;
|
|
477
531
|
display: flex;
|
|
478
532
|
flex-direction: column;
|
|
479
533
|
overflow: hidden;
|
|
@@ -552,13 +606,25 @@ defineExpose({ setMatrixData })
|
|
|
552
606
|
}
|
|
553
607
|
|
|
554
608
|
.matrix-table-wrap {
|
|
555
|
-
flex:
|
|
556
|
-
|
|
609
|
+
flex: 0 0 auto;
|
|
610
|
+
width: 100%;
|
|
611
|
+
min-width: 0;
|
|
612
|
+
overflow-x: auto;
|
|
613
|
+
overflow-y: auto;
|
|
557
614
|
padding: 12px;
|
|
615
|
+
box-sizing: border-box;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* 内层撑开表格宽度,外层 wrap 才能正确出现横向滚动条 */
|
|
619
|
+
.matrix-table-scroll {
|
|
620
|
+
display: inline-block;
|
|
621
|
+
min-width: 100%;
|
|
622
|
+
vertical-align: top;
|
|
558
623
|
}
|
|
559
624
|
|
|
560
625
|
.matrix-table {
|
|
561
626
|
border-collapse: collapse;
|
|
627
|
+
width: max-content;
|
|
562
628
|
min-width: 100%;
|
|
563
629
|
font-size: 12px;
|
|
564
630
|
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { ref, computed, watch, type Ref, type ComputedRef } from "vue";
|
|
2
|
+
import type { Shape } from "../types";
|
|
3
|
+
import type { InteractionLayerProps } from "../types/interactionLayer";
|
|
4
|
+
import type { IHighlightUtils } from "./useHighlight";
|
|
5
|
+
import { EdgeUtils } from "../utils/edgeUtils";
|
|
6
|
+
import { getLayerStyle } from "../utils/diagram";
|
|
7
|
+
import { useGraphStore } from "../store/graphStore";
|
|
8
|
+
import { toLocalPoint, getBounds, inBounds } from "../utils/geom";
|
|
9
|
+
import { pickTarget } from "../utils/hittest";
|
|
10
|
+
import { eventBus } from "../store";
|
|
11
|
+
import { createRAFThrottle } from "../utils/rafThrottle";
|
|
12
|
+
|
|
13
|
+
export interface UseConnectionPreviewOptions {
|
|
14
|
+
props: InteractionLayerProps;
|
|
15
|
+
graphStore: ReturnType<typeof useGraphStore>;
|
|
16
|
+
highlightUtils: IHighlightUtils;
|
|
17
|
+
connectMode: Ref<string>;
|
|
18
|
+
layerRef: Ref<HTMLDivElement | null>;
|
|
19
|
+
highlightedShape: ComputedRef<Shape | null>;
|
|
20
|
+
pendingActionButton: Ref<boolean>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 连线预览层:连接点状态、预览线样式、初始化/取消连接、悬停校验与 document 鼠标移动。
|
|
25
|
+
*/
|
|
26
|
+
export function useConnectionPreview(options: UseConnectionPreviewOptions) {
|
|
27
|
+
const {
|
|
28
|
+
props,
|
|
29
|
+
graphStore,
|
|
30
|
+
highlightUtils,
|
|
31
|
+
connectMode,
|
|
32
|
+
layerRef,
|
|
33
|
+
highlightedShape,
|
|
34
|
+
pendingActionButton,
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
const { highlightShape, clearHighlightTimeout } = highlightUtils;
|
|
38
|
+
|
|
39
|
+
const mousePosition = ref({ x: 0, y: 0 });
|
|
40
|
+
const showLine = ref(false);
|
|
41
|
+
const currentConnectPoint = ref({ x: 0, y: 0 });
|
|
42
|
+
const isConnecting = ref(false);
|
|
43
|
+
const targetConnectPoint = ref({ x: 0, y: 0 });
|
|
44
|
+
const targetShape = ref<Shape | null>(null);
|
|
45
|
+
const recordClickPoint = ref({ x: 0, y: 0 });
|
|
46
|
+
const isConnectAllowed = ref(false);
|
|
47
|
+
|
|
48
|
+
const getCurrentTargetModels = () => {
|
|
49
|
+
if (connectMode.value === "action") {
|
|
50
|
+
return (
|
|
51
|
+
props.connectShapeData?.scenarioMenus?.find(
|
|
52
|
+
(menu) => menu.code === props.connectShapeData?.shapeKey
|
|
53
|
+
)?.targetModels ?? props.connectShapeData?.targetModels
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return props.connectShapeData?.targetModels;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const resolveConnectTargetAtPoint = (pt: { x: number; y: number }) => {
|
|
61
|
+
const hit = pickTarget(graphStore.shapes, pt);
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
["shape", "pin"].includes(hit.kind) &&
|
|
65
|
+
hit.shape?.id &&
|
|
66
|
+
hit.shape.id !== props.connectShapeData?.sourceId
|
|
67
|
+
) {
|
|
68
|
+
return hit.shape;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const hoveredShape = highlightedShape.value;
|
|
72
|
+
if (
|
|
73
|
+
!hoveredShape?.id ||
|
|
74
|
+
hoveredShape.id === props.connectShapeData?.sourceId
|
|
75
|
+
) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const bounds = getBounds(hoveredShape);
|
|
80
|
+
const minSide = Math.min(bounds.width, bounds.height);
|
|
81
|
+
const padding = minSide <= 30 ? 8 : minSide <= 40 ? 6 : 0;
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
inBounds(pt, {
|
|
85
|
+
x: bounds.x - padding,
|
|
86
|
+
y: bounds.y - padding,
|
|
87
|
+
width: bounds.width + padding * 2,
|
|
88
|
+
height: bounds.height + padding * 2,
|
|
89
|
+
})
|
|
90
|
+
) {
|
|
91
|
+
return hoveredShape;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const initializeConnectPoint = () => {
|
|
98
|
+
if (props.connectShapeData?.sourceId && props.diagramBounds) {
|
|
99
|
+
const sourceShape = props.connectShapeData?.sourceId
|
|
100
|
+
? graphStore.shapeMap.get(props.connectShapeData.sourceId)
|
|
101
|
+
: undefined;
|
|
102
|
+
|
|
103
|
+
const initialPoint = EdgeUtils.initializeConnectPoint(
|
|
104
|
+
sourceShape,
|
|
105
|
+
recordClickPoint.value,
|
|
106
|
+
props.diagramBounds
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (initialPoint && sourceShape) {
|
|
110
|
+
currentConnectPoint.value = initialPoint;
|
|
111
|
+
|
|
112
|
+
if (!isConnecting.value) {
|
|
113
|
+
graphStore.clearSelection();
|
|
114
|
+
isConnecting.value = true;
|
|
115
|
+
showLine.value = true;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
isConnecting.value = false;
|
|
119
|
+
showLine.value = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const dotStyle = computed(() => {
|
|
125
|
+
if (!props.connectShapeData || !props.diagramBounds) {
|
|
126
|
+
return { display: "none" };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { x, y } = currentConnectPoint.value;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
position: "absolute" as const,
|
|
133
|
+
left: `${x}px`,
|
|
134
|
+
top: `${y}px`,
|
|
135
|
+
width: "8px",
|
|
136
|
+
height: "8px",
|
|
137
|
+
pointerEvents: "none" as const,
|
|
138
|
+
zIndex: 1001,
|
|
139
|
+
transform: "translate(-50%, -50%)",
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const svgStyle = computed(() => {
|
|
144
|
+
if (
|
|
145
|
+
!props.connectShapeData ||
|
|
146
|
+
!props.connectShapeData.bounds ||
|
|
147
|
+
!props.diagramBounds
|
|
148
|
+
) {
|
|
149
|
+
return { display: "none" };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
position: "absolute" as const,
|
|
154
|
+
left: "0px",
|
|
155
|
+
top: "0px",
|
|
156
|
+
width: "100%",
|
|
157
|
+
height: "100%",
|
|
158
|
+
pointerEvents: "none" as const,
|
|
159
|
+
zIndex: 1000,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const linePoints = computed(() => {
|
|
164
|
+
if (
|
|
165
|
+
!isConnecting.value ||
|
|
166
|
+
!showLine.value ||
|
|
167
|
+
!props.connectShapeData ||
|
|
168
|
+
!props.diagramBounds ||
|
|
169
|
+
currentConnectPoint.value.x === 0 ||
|
|
170
|
+
currentConnectPoint.value.y === 0
|
|
171
|
+
) {
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const { x: startX, y: startY } = currentConnectPoint.value;
|
|
176
|
+
|
|
177
|
+
let endX: number;
|
|
178
|
+
let endY: number;
|
|
179
|
+
if (isConnecting.value) {
|
|
180
|
+
endX = mousePosition.value.x;
|
|
181
|
+
endY = mousePosition.value.y;
|
|
182
|
+
} else if (targetConnectPoint.value) {
|
|
183
|
+
endX = targetConnectPoint.value.x;
|
|
184
|
+
endY = targetConnectPoint.value.y;
|
|
185
|
+
} else {
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return EdgeUtils.calculateLinePoints(
|
|
190
|
+
{ x: startX, y: startY },
|
|
191
|
+
{ x: endX, y: endY }
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const layerStyle = computed(() => getLayerStyle(props.diagramBounds));
|
|
196
|
+
|
|
197
|
+
const cancelConnection = () => {
|
|
198
|
+
EdgeUtils.cancelConnection(
|
|
199
|
+
{
|
|
200
|
+
isConnecting,
|
|
201
|
+
currentConnectPoint,
|
|
202
|
+
mousePosition,
|
|
203
|
+
targetConnectPoint,
|
|
204
|
+
targetShape,
|
|
205
|
+
showLine,
|
|
206
|
+
},
|
|
207
|
+
highlightUtils
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const connectMouseMoveThrottle = createRAFThrottle();
|
|
212
|
+
|
|
213
|
+
let lastHoverShapeId: string | null = null;
|
|
214
|
+
let edgeCheckDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
215
|
+
const EDGE_CHECK_DEBOUNCE_DELAY = 150;
|
|
216
|
+
|
|
217
|
+
const checkHoverTarget = (x: number, y: number) => {
|
|
218
|
+
if (!props.diagramBounds) return;
|
|
219
|
+
|
|
220
|
+
const hoverShape = EdgeUtils.checkHoverTarget(
|
|
221
|
+
x,
|
|
222
|
+
y,
|
|
223
|
+
graphStore.shapes,
|
|
224
|
+
props.diagramBounds,
|
|
225
|
+
props.connectShapeData?.sourceId
|
|
226
|
+
);
|
|
227
|
+
clearHighlightTimeout();
|
|
228
|
+
|
|
229
|
+
if (hoverShape) {
|
|
230
|
+
const targetModels = getCurrentTargetModels();
|
|
231
|
+
let isAllowed = true;
|
|
232
|
+
if (
|
|
233
|
+
targetModels &&
|
|
234
|
+
Array.isArray(targetModels) &&
|
|
235
|
+
targetModels.length > 0
|
|
236
|
+
) {
|
|
237
|
+
const hoverShapeType = hoverShape.shapeKey || "";
|
|
238
|
+
isAllowed = targetModels.includes(hoverShapeType);
|
|
239
|
+
}
|
|
240
|
+
if (props.connectShapeData?.sourceId) {
|
|
241
|
+
const sourceShape = graphStore.shapeMap.get(
|
|
242
|
+
props.connectShapeData.sourceId
|
|
243
|
+
);
|
|
244
|
+
if (
|
|
245
|
+
sourceShape &&
|
|
246
|
+
sourceShape.parenShapeId !== hoverShape.parenShapeId &&
|
|
247
|
+
hoverShape.shapeType !== "pin" &&
|
|
248
|
+
sourceShape.shapeType !== "pin"
|
|
249
|
+
) {
|
|
250
|
+
isAllowed = false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (lastHoverShapeId !== hoverShape.id) {
|
|
255
|
+
if (edgeCheckDebounceTimer) {
|
|
256
|
+
clearTimeout(edgeCheckDebounceTimer);
|
|
257
|
+
edgeCheckDebounceTimer = null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
highlightShape(null, false);
|
|
261
|
+
isConnectAllowed.value = false;
|
|
262
|
+
|
|
263
|
+
lastHoverShapeId = hoverShape.id;
|
|
264
|
+
|
|
265
|
+
if (props.connectShapeData) {
|
|
266
|
+
edgeCheckDebounceTimer = setTimeout(() => {
|
|
267
|
+
let sourceModelId;
|
|
268
|
+
if (
|
|
269
|
+
props.connectShapeData?.modelId &&
|
|
270
|
+
props.connectShapeData.modelId.toString().trim() !== ""
|
|
271
|
+
) {
|
|
272
|
+
sourceModelId = props.connectShapeData.modelId;
|
|
273
|
+
} else if (
|
|
274
|
+
props.connectShapeData?.sourceModelId &&
|
|
275
|
+
props.connectShapeData.sourceModelId.toString().trim() !== ""
|
|
276
|
+
) {
|
|
277
|
+
sourceModelId = props.connectShapeData.sourceModelId;
|
|
278
|
+
}
|
|
279
|
+
if (sourceModelId) {
|
|
280
|
+
eventBus.emit("edge-check", {
|
|
281
|
+
sourceModelId: sourceModelId,
|
|
282
|
+
targetModelId: hoverShape.modelId,
|
|
283
|
+
isAllowed: isAllowed,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
highlightShape(hoverShape, true, isAllowed);
|
|
288
|
+
isConnectAllowed.value = isAllowed;
|
|
289
|
+
}, EDGE_CHECK_DEBOUNCE_DELAY);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
if (edgeCheckDebounceTimer) {
|
|
294
|
+
clearTimeout(edgeCheckDebounceTimer);
|
|
295
|
+
edgeCheckDebounceTimer = null;
|
|
296
|
+
}
|
|
297
|
+
lastHoverShapeId = null;
|
|
298
|
+
clearHighlightTimeout();
|
|
299
|
+
highlightShape(null, false);
|
|
300
|
+
isConnectAllowed.value = false;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const updateConnectPointToNearest = (mouseX: number, mouseY: number) => {
|
|
305
|
+
if (!props.connectShapeData?.sourceId || !props.diagramBounds) return;
|
|
306
|
+
|
|
307
|
+
const sourceShape = props.connectShapeData?.sourceId
|
|
308
|
+
? graphStore.shapeMap.get(props.connectShapeData.sourceId)
|
|
309
|
+
: undefined;
|
|
310
|
+
|
|
311
|
+
const nearestPoint = EdgeUtils.findNearestConnectPoint(
|
|
312
|
+
{ x: mouseX, y: mouseY },
|
|
313
|
+
sourceShape
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
if (
|
|
317
|
+
nearestPoint &&
|
|
318
|
+
nearestPoint.x !== undefined &&
|
|
319
|
+
nearestPoint.y !== undefined
|
|
320
|
+
) {
|
|
321
|
+
currentConnectPoint.value = {
|
|
322
|
+
x: nearestPoint.x,
|
|
323
|
+
y: nearestPoint.y,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const handleMouseMove = (event: MouseEvent) => {
|
|
329
|
+
connectMouseMoveThrottle.throttle((evt: MouseEvent) => {
|
|
330
|
+
const { clientX, clientY } = evt;
|
|
331
|
+
const localPoint = toLocalPoint(evt, layerRef.value);
|
|
332
|
+
if (EdgeUtils.isWithinCanvas(clientX, clientY)) {
|
|
333
|
+
mousePosition.value = { x: localPoint.x, y: localPoint.y };
|
|
334
|
+
|
|
335
|
+
if (isConnecting.value && !pendingActionButton.value) {
|
|
336
|
+
showLine.value = true;
|
|
337
|
+
updateConnectPointToNearest(localPoint.x, localPoint.y);
|
|
338
|
+
checkHoverTarget(localPoint.x, localPoint.y);
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
if (isConnecting.value) {
|
|
342
|
+
showLine.value = false;
|
|
343
|
+
}
|
|
344
|
+
highlightShape(null, false);
|
|
345
|
+
}
|
|
346
|
+
})(event);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const handleMouseLeave = () => {
|
|
350
|
+
if (isConnecting.value) {
|
|
351
|
+
showLine.value = false;
|
|
352
|
+
}
|
|
353
|
+
highlightShape(null, false);
|
|
354
|
+
clearHighlightTimeout();
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
watch(
|
|
358
|
+
() => isConnecting.value,
|
|
359
|
+
(newVal) => {
|
|
360
|
+
if (!newVal) {
|
|
361
|
+
if (edgeCheckDebounceTimer) {
|
|
362
|
+
clearTimeout(edgeCheckDebounceTimer);
|
|
363
|
+
edgeCheckDebounceTimer = null;
|
|
364
|
+
}
|
|
365
|
+
lastHoverShapeId = null;
|
|
366
|
+
isConnectAllowed.value = false;
|
|
367
|
+
highlightShape(null, false);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
watch(
|
|
373
|
+
() => props.edgeCheck,
|
|
374
|
+
(newEdgeCheck) => {
|
|
375
|
+
if (
|
|
376
|
+
isConnecting.value &&
|
|
377
|
+
highlightedShape.value &&
|
|
378
|
+
newEdgeCheck !== undefined
|
|
379
|
+
) {
|
|
380
|
+
highlightShape(highlightedShape.value, true, newEdgeCheck);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
mousePosition,
|
|
387
|
+
showLine,
|
|
388
|
+
currentConnectPoint,
|
|
389
|
+
isConnecting,
|
|
390
|
+
targetConnectPoint,
|
|
391
|
+
targetShape,
|
|
392
|
+
recordClickPoint,
|
|
393
|
+
isConnectAllowed,
|
|
394
|
+
dotStyle,
|
|
395
|
+
svgStyle,
|
|
396
|
+
linePoints,
|
|
397
|
+
layerStyle,
|
|
398
|
+
cancelConnection,
|
|
399
|
+
initializeConnectPoint,
|
|
400
|
+
getCurrentTargetModels,
|
|
401
|
+
resolveConnectTargetAtPoint,
|
|
402
|
+
handleMouseMove,
|
|
403
|
+
handleMouseLeave,
|
|
404
|
+
};
|
|
405
|
+
}
|
package/src/store/graphStore.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { batchAutoExpandParents } from '../utils/batchAutoExpand'
|
|
|
27
27
|
import { adjustCanvasToFitAllShapes } from '../utils/diagram'
|
|
28
28
|
import { applyReparentAndClone, autoExpandMovedCompartmentsAfterDrag, buildDragEndPayloads, buildPrevParentMap, collectAffectedShapeIds, createOnNestDoneCallback } from '../utils/graphDragService'
|
|
29
29
|
import { createShapeOperator, type ShapeOp, type ShapeId } from "../utils/shapeOps/shapeOps"
|
|
30
|
+
import { autoLayout, type AutoLayoutOptions, type LayoutResult } from '../utils/autoLayout'
|
|
30
31
|
type Rect = { x: number; y: number; width: number; height: number };
|
|
31
32
|
|
|
32
33
|
const GRAPH_SELECTION_STORAGE_KEY = 'mx-sose-graph:selected-state'
|
|
@@ -1052,6 +1053,28 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
1052
1053
|
// eventBus.emit('shape-size-update', payload)
|
|
1053
1054
|
}
|
|
1054
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
* 自动布局画布上的所有图元
|
|
1058
|
+
* 基于 ELK 分层算法,支持横向/纵向布局
|
|
1059
|
+
*/
|
|
1060
|
+
const applyAutoLayout = async (options?: AutoLayoutOptions): Promise<LayoutResult> => {
|
|
1061
|
+
const result = await autoLayout(shapes.value, options)
|
|
1062
|
+
|
|
1063
|
+
for (const [id, bounds] of result.nodeUpdates) {
|
|
1064
|
+
updateShape(id, { bounds })
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
await EdgeUtils.updateRelatedEdgesAsync(
|
|
1068
|
+
shapes.value,
|
|
1069
|
+
result.affectedNodeIds,
|
|
1070
|
+
(shape) => updateShape(shape.id, shape),
|
|
1071
|
+
edgesByNodeId.value,
|
|
1072
|
+
shapeMap.value
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
return result
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1055
1078
|
// 设置鼠标是否在图元区域
|
|
1056
1079
|
const setIsMouseInGraphView = (isIn: boolean) => {
|
|
1057
1080
|
museInGraphView.value = isIn
|
|
@@ -1155,5 +1178,6 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
1155
1178
|
clearCutShapeIds,
|
|
1156
1179
|
setCopiedShapesCount,
|
|
1157
1180
|
setMatrixData,
|
|
1181
|
+
applyAutoLayout,
|
|
1158
1182
|
}
|
|
1159
1183
|
})
|