@plait/mind 0.2.0-next.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 (125) hide show
  1. package/README.md +24 -0
  2. package/constants/default.d.ts +12 -0
  3. package/constants/index.d.ts +2 -0
  4. package/constants/node.d.ts +17 -0
  5. package/draw/abstract.d.ts +4 -0
  6. package/draw/indented-link.d.ts +3 -0
  7. package/draw/link/abstract-link.d.ts +3 -0
  8. package/draw/link/logic-link.d.ts +3 -0
  9. package/draw/link.d.ts +3 -0
  10. package/draw/richtext.d.ts +14 -0
  11. package/draw/shape.d.ts +3 -0
  12. package/esm2020/constants/default.mjs +13 -0
  13. package/esm2020/constants/index.mjs +3 -0
  14. package/esm2020/constants/node.mjs +19 -0
  15. package/esm2020/draw/abstract.mjs +48 -0
  16. package/esm2020/draw/indented-link.mjs +44 -0
  17. package/esm2020/draw/link/abstract-link.mjs +42 -0
  18. package/esm2020/draw/link/logic-link.mjs +51 -0
  19. package/esm2020/draw/link.mjs +158 -0
  20. package/esm2020/draw/richtext.mjs +35 -0
  21. package/esm2020/draw/shape.mjs +18 -0
  22. package/esm2020/interfaces/abstract.mjs +6 -0
  23. package/esm2020/interfaces/element-data.mjs +2 -0
  24. package/esm2020/interfaces/element.mjs +59 -0
  25. package/esm2020/interfaces/index.mjs +6 -0
  26. package/esm2020/interfaces/layout.mjs +19 -0
  27. package/esm2020/interfaces/node.mjs +23 -0
  28. package/esm2020/interfaces/types.mjs +13 -0
  29. package/esm2020/layout-option.mjs +72 -0
  30. package/esm2020/mind.component.mjs +50 -0
  31. package/esm2020/mind.module.mjs +21 -0
  32. package/esm2020/node.component.mjs +742 -0
  33. package/esm2020/plait-mind.mjs +5 -0
  34. package/esm2020/plugins/emoji/emoji-base.component.mjs +21 -0
  35. package/esm2020/plugins/emoji/emoji.drawer.mjs +79 -0
  36. package/esm2020/plugins/emoji/emoji.mjs +15 -0
  37. package/esm2020/plugins/emoji/index.mjs +4 -0
  38. package/esm2020/plugins/emoji/with-mind-emoji.mjs +8 -0
  39. package/esm2020/plugins/with-abstract.mjs +92 -0
  40. package/esm2020/plugins/with-dnd.mjs +276 -0
  41. package/esm2020/plugins/with-mind.mjs +183 -0
  42. package/esm2020/public-api.mjs +14 -0
  43. package/esm2020/queries/get-available-sublayouts-by-element.mjs +29 -0
  44. package/esm2020/queries/get-branch-mindmap-layouts-by-element.mjs +18 -0
  45. package/esm2020/queries/get-correct-layout-by-element.mjs +50 -0
  46. package/esm2020/queries/get-layout-by-element.mjs +16 -0
  47. package/esm2020/queries/get-layout-parent-by-element.mjs +17 -0
  48. package/esm2020/queries/index.mjs +13 -0
  49. package/esm2020/transforms/index.mjs +10 -0
  50. package/esm2020/transforms/layout.mjs +23 -0
  51. package/esm2020/transforms/node.mjs +44 -0
  52. package/esm2020/utils/abstract/common.mjs +25 -0
  53. package/esm2020/utils/abstract/resize.mjs +169 -0
  54. package/esm2020/utils/clipboard.mjs +71 -0
  55. package/esm2020/utils/colors.mjs +41 -0
  56. package/esm2020/utils/direction-corrector.mjs +54 -0
  57. package/esm2020/utils/direction-detector.mjs +56 -0
  58. package/esm2020/utils/draw-placeholder.mjs +311 -0
  59. package/esm2020/utils/drop-target-corrector.mjs +86 -0
  60. package/esm2020/utils/graph.mjs +24 -0
  61. package/esm2020/utils/index.mjs +14 -0
  62. package/esm2020/utils/is-virtual-key.mjs +13 -0
  63. package/esm2020/utils/layout.mjs +105 -0
  64. package/esm2020/utils/mindmap.mjs +295 -0
  65. package/esm2020/utils/node-space.mjs +72 -0
  66. package/esm2020/utils/node.mjs +6 -0
  67. package/esm2020/utils/path.mjs +11 -0
  68. package/esm2020/utils/point-placement.mjs +99 -0
  69. package/esm2020/utils/shape.mjs +17 -0
  70. package/esm2020/utils/weak-maps.mjs +3 -0
  71. package/fesm2015/plait-mind.mjs +3583 -0
  72. package/fesm2015/plait-mind.mjs.map +1 -0
  73. package/fesm2020/plait-mind.mjs +3595 -0
  74. package/fesm2020/plait-mind.mjs.map +1 -0
  75. package/index.d.ts +5 -0
  76. package/interfaces/abstract.d.ts +4 -0
  77. package/interfaces/element-data.d.ts +11 -0
  78. package/interfaces/element.d.ts +39 -0
  79. package/interfaces/index.d.ts +5 -0
  80. package/interfaces/layout.d.ts +10 -0
  81. package/interfaces/node.d.ts +33 -0
  82. package/interfaces/types.d.ts +11 -0
  83. package/layout-option.d.ts +2 -0
  84. package/mind.component.d.ts +16 -0
  85. package/mind.module.d.ts +11 -0
  86. package/node.component.d.ts +60 -0
  87. package/package.json +31 -0
  88. package/plugins/emoji/emoji-base.component.d.ts +12 -0
  89. package/plugins/emoji/emoji.d.ts +6 -0
  90. package/plugins/emoji/emoji.drawer.d.ts +22 -0
  91. package/plugins/emoji/index.d.ts +3 -0
  92. package/plugins/emoji/with-mind-emoji.d.ts +8 -0
  93. package/plugins/with-abstract.d.ts +2 -0
  94. package/plugins/with-dnd.d.ts +11 -0
  95. package/plugins/with-mind.d.ts +2 -0
  96. package/public-api.d.ts +10 -0
  97. package/queries/get-available-sublayouts-by-element.d.ts +8 -0
  98. package/queries/get-branch-mindmap-layouts-by-element.d.ts +3 -0
  99. package/queries/get-correct-layout-by-element.d.ts +9 -0
  100. package/queries/get-layout-by-element.d.ts +3 -0
  101. package/queries/get-layout-parent-by-element.d.ts +8 -0
  102. package/queries/index.d.ts +7 -0
  103. package/styles/styles.scss +99 -0
  104. package/transforms/index.d.ts +7 -0
  105. package/transforms/layout.d.ts +3 -0
  106. package/transforms/node.d.ts +8 -0
  107. package/utils/abstract/common.d.ts +5 -0
  108. package/utils/abstract/resize.d.ts +21 -0
  109. package/utils/clipboard.d.ts +7 -0
  110. package/utils/colors.d.ts +4 -0
  111. package/utils/direction-corrector.d.ts +3 -0
  112. package/utils/direction-detector.d.ts +9 -0
  113. package/utils/draw-placeholder.d.ts +42 -0
  114. package/utils/drop-target-corrector.d.ts +8 -0
  115. package/utils/graph.d.ts +5 -0
  116. package/utils/index.d.ts +13 -0
  117. package/utils/is-virtual-key.d.ts +1 -0
  118. package/utils/layout.d.ts +12 -0
  119. package/utils/mindmap.d.ts +33 -0
  120. package/utils/node-space.d.ts +9 -0
  121. package/utils/node.d.ts +2 -0
  122. package/utils/path.d.ts +4 -0
  123. package/utils/point-placement.d.ts +8 -0
  124. package/utils/shape.d.ts +3 -0
  125. package/utils/weak-maps.d.ts +5 -0
@@ -0,0 +1,3595 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, ChangeDetectionStrategy, NgModule, Directive, Input } from '@angular/core';
3
+ import * as i2 from '@plait/core';
4
+ import { distanceBetweenPointAndRectangle, drawRoundRectangle, getRectangleByElements, RectangleClient, PlaitBoard, PlaitNode, NODE_TO_PARENT, ELEMENT_TO_COMPONENT, idCreator, Transforms, isNullOrUndefined, addSelectedElement, createG, drawAbstractRoundRectangle, PlaitPluginElementComponent, PlaitPointerType, NODE_TO_INDEX, PlaitElement, createText, IS_TEXT_EDITABLE, MERGING, transformPoint, toPoint, depthFirstRecursion, PlaitModule, getSelectedElements, distanceBetweenPointAndPoint, Path, CLIP_BOARD_FORMAT_KEY, BOARD_TO_HOST, removeSelectedElement, PlaitHistoryBoard, hotkeys } from '@plait/core';
5
+ import { isIndentedLayout, MindmapLayoutType, isStandardLayout, isTopLayout, isLeftLayout, isHorizontalLogicLayout, isVerticalLogicLayout, isBottomLayout, isRightLayout, isHorizontalLayout, AbstractNode, getCorrectStartEnd, isChildOfAbstract, getAbstractLayout, ConnectingPosition, GlobalLayout } from '@plait/layouts';
6
+ import { getSizeByText, ROOT_DEFAULT_HEIGHT, TEXT_DEFAULT_HEIGHT, drawRichtext, updateForeignObject, createForeignObject, updateRichText, setFullSelectionAndFocus, getRichtextContentSize, hasEditableTarget, RichtextModule } from '@plait/richtext';
7
+ import { Subject, fromEvent, timer } from 'rxjs';
8
+ import { takeUntil, filter, take, debounceTime } from 'rxjs/operators';
9
+ import { Node, Editor, Operation, Path as Path$1 } from 'slate';
10
+ import { pointsOnBezierCurves } from 'points-on-curve';
11
+ import { isKeyHotkey } from 'is-hotkey';
12
+ import * as i1 from '@angular/common';
13
+ import { BrowserModule } from '@angular/platform-browser';
14
+
15
+ const MINDMAP_ELEMENT_TO_COMPONENT = new WeakMap();
16
+ const ELEMENT_TO_NODE = new WeakMap();
17
+
18
+ const BASE = 4;
19
+ const PRIMARY_COLOR = '#6698FF';
20
+ const MINDMAP_KEY = 'plait-mindmap';
21
+ const MAX_RADIUS = BASE * 4;
22
+ const TRANSPARENT = 'transparent';
23
+ const GRAY_COLOR = '#AAAAAA';
24
+ const STROKE_WIDTH = 3;
25
+ const EXTEND_OFFSET = 8;
26
+ const EXTEND_RADIUS = 16;
27
+ const QUICK_INSERT_CIRCLE_OFFSET = 9;
28
+ const QUICK_INSERT_CIRCLE_COLOR = '#6698FF';
29
+ const QUICK_INSERT_INNER_CROSS_COLOR = 'white';
30
+
31
+ const TOPIC_COLOR = '#333';
32
+ const TOPIC_FONT_SIZE = 14;
33
+ const NODE_FILL = '#FFFFFF';
34
+ const ROOT_NODE_FILL = '#F5F5F5';
35
+ const ROOT_NODE_STROKE = '#F5F5F5';
36
+ const ROOT_TOPIC_FONT_SIZE = 18;
37
+ const NODE_MIN_WIDTH = 18;
38
+ const COLORS = ['#A287E1', '#6F81DB', '#6EC4C4', '#DFB85D', '#B1C774', '#77C386', '#C28976', '#E48484', '#E482D4', '#69B1E4'];
39
+ var MindmapNodeShape;
40
+ (function (MindmapNodeShape) {
41
+ MindmapNodeShape["roundRectangle"] = "round-rectangle";
42
+ MindmapNodeShape["underline"] = "underline";
43
+ })(MindmapNodeShape || (MindmapNodeShape = {}));
44
+ const ABSTRACT_HANDLE_COLOR = '#6698FF80'; //PRIMARY_COLOR 50% 透明度
45
+ const ABSTRACT_INCLUDED_OUTLINE_OFFSET = 3.5;
46
+ const ABSTRACT_HANDLE_LENGTH = 12;
47
+ const TOPIC_DEFAULT_MAX_WORD_COUNT = 34;
48
+ const ABSTRACT_HANDLE_MASK_WIDTH = 8;
49
+
50
+ function getRectangleByNode(node) {
51
+ const x = node.x + node.hGap;
52
+ let y = node.y + node.vGap;
53
+ const width = node.width - node.hGap * 2;
54
+ const height = node.height - node.vGap * 2;
55
+ return {
56
+ x,
57
+ y,
58
+ width,
59
+ height
60
+ };
61
+ }
62
+ function hitMindmapElement(board, point, element) {
63
+ const node = ELEMENT_TO_NODE.get(element);
64
+ if (node && distanceBetweenPointAndRectangle(point[0], point[1], getRectangleByNode(node)) === 0) {
65
+ return true;
66
+ }
67
+ else {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ const getNodeShapeByElement = (element) => {
73
+ let nodeShape = element.shape;
74
+ if (nodeShape) {
75
+ return nodeShape;
76
+ }
77
+ let parent = findParentElement(element);
78
+ while (parent) {
79
+ if (parent.shape) {
80
+ return parent.shape;
81
+ }
82
+ parent = findParentElement(parent);
83
+ }
84
+ return MindmapNodeShape.roundRectangle;
85
+ };
86
+
87
+ function isVirtualKey(e) {
88
+ const isMod = e.ctrlKey || e.metaKey;
89
+ const isAlt = isKeyHotkey('alt', e);
90
+ const isShift = isKeyHotkey('shift', e);
91
+ const isCapsLock = e.key.includes('CapsLock');
92
+ const isTab = e.key.includes('Tab');
93
+ const isEsc = e.key.includes('Escape');
94
+ const isF = e.key.startsWith('F');
95
+ const isArrow = e.key.includes('Arrow') ? true : false;
96
+ return isCapsLock || isMod || isAlt || isArrow || isShift || isTab || isEsc || isF;
97
+ }
98
+
99
+ function drawLink(roughSVG, node, child, defaultStroke = null, isHorizontal = true, needDrawUnderline = true) {
100
+ let beginX, beginY, endX, endY, beginNode = node, endNode = child;
101
+ const layout = MindmapQueries.getCorrectLayoutByElement(node.origin);
102
+ if (isHorizontal) {
103
+ if (!isChildRight(node, child)) {
104
+ beginNode = child;
105
+ endNode = node;
106
+ }
107
+ beginX = beginNode.x + beginNode.width - beginNode.hGap;
108
+ beginY = beginNode.y + beginNode.height / 2;
109
+ endX = endNode.x + endNode.hGap;
110
+ endY = endNode.y + endNode.height / 2;
111
+ if (node.parent &&
112
+ isIndentedLayout(MindmapQueries.getLayoutByElement(node.parent?.origin)) &&
113
+ getNodeShapeByElement(node.origin) === MindmapNodeShape.underline) {
114
+ if (isChildRight(node, child)) {
115
+ beginY = node.y + node.height - node.vGap;
116
+ }
117
+ else {
118
+ endY = node.y + node.height - node.vGap;
119
+ }
120
+ }
121
+ }
122
+ else {
123
+ if (node.y > child.y) {
124
+ beginNode = child;
125
+ endNode = node;
126
+ }
127
+ beginX = beginNode.x + beginNode.width / 2;
128
+ beginY = beginNode.y + beginNode.height - beginNode.vGap;
129
+ endX = endNode.x + endNode.width / 2;
130
+ endY = endNode.y + endNode.vGap;
131
+ }
132
+ const stroke = defaultStroke || getLinkLineColorByMindmapElement(child.origin);
133
+ const strokeWidth = child.origin.linkLineWidth ? child.origin.linkLineWidth : STROKE_WIDTH;
134
+ if (endNode.origin.isRoot) {
135
+ if (layout === MindmapLayoutType.left || isStandardLayout(layout)) {
136
+ endX -= strokeWidth;
137
+ }
138
+ if (layout === MindmapLayoutType.upward) {
139
+ endY -= strokeWidth;
140
+ }
141
+ }
142
+ if (beginNode.origin.isRoot) {
143
+ if (layout === MindmapLayoutType.right || isStandardLayout(layout)) {
144
+ beginX += strokeWidth;
145
+ }
146
+ if (layout === MindmapLayoutType.downward) {
147
+ beginY += strokeWidth;
148
+ }
149
+ }
150
+ if (isHorizontal) {
151
+ let curve = [
152
+ [beginX, beginY],
153
+ [beginX + (beginNode.hGap + endNode.hGap) / 3, beginY],
154
+ [endX - (beginNode.hGap + endNode.hGap) / 2, endY],
155
+ [endX, endY]
156
+ ];
157
+ const shape = getNodeShapeByElement(child.origin);
158
+ if (!node.origin.isRoot) {
159
+ if (node.x > child.x) {
160
+ curve = [
161
+ [beginX, beginY],
162
+ [beginX + (beginNode.hGap + endNode.hGap) / 3, beginY],
163
+ [endX - (beginNode.hGap + endNode.hGap) / 2, endY],
164
+ [endX - 12, endY]
165
+ ];
166
+ const line = [
167
+ [endX - 12, endY],
168
+ [endX - 12, endY],
169
+ [endX, endY]
170
+ ];
171
+ curve = [...curve, ...line];
172
+ }
173
+ else {
174
+ curve = [
175
+ [beginX + 12, beginY],
176
+ [beginX + (beginNode.hGap + endNode.hGap) / 2, beginY],
177
+ [endX - (beginNode.hGap + endNode.hGap) / 3, endY],
178
+ [endX, endY]
179
+ ];
180
+ const line = [
181
+ [beginX, beginY],
182
+ [beginX + 12, beginY],
183
+ [beginX + 12, beginY]
184
+ ];
185
+ curve = [...line, ...curve];
186
+ }
187
+ }
188
+ if (needDrawUnderline && shape === MindmapNodeShape.underline) {
189
+ if (child.left) {
190
+ const underline = [
191
+ [beginX - (beginNode.width - beginNode.hGap * 2), beginY],
192
+ [beginX - (beginNode.width - beginNode.hGap * 2), beginY],
193
+ [beginX - (beginNode.width - beginNode.hGap * 2), beginY]
194
+ ];
195
+ curve = [...underline, ...curve];
196
+ }
197
+ else {
198
+ const underline = [
199
+ [endX + (endNode.width - endNode.hGap * 2), endY],
200
+ [endX + (endNode.width - endNode.hGap * 2), endY],
201
+ [endX + (endNode.width - endNode.hGap * 2), endY]
202
+ ];
203
+ curve = [...curve, ...underline];
204
+ }
205
+ }
206
+ const points = pointsOnBezierCurves(curve);
207
+ return roughSVG.curve(points, { stroke, strokeWidth });
208
+ }
209
+ else {
210
+ let curve = [
211
+ [beginX, beginY],
212
+ [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
213
+ [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
214
+ [endX, endY]
215
+ ];
216
+ if (!node.origin.isRoot) {
217
+ if (isTopLayout(layout)) {
218
+ curve = [
219
+ [beginX, beginY],
220
+ [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
221
+ [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
222
+ [endX, endY - 12]
223
+ ];
224
+ const line = [
225
+ [endX, endY - 12],
226
+ [endX, endY - 12],
227
+ [endX, endY]
228
+ ];
229
+ curve = [...curve, ...line];
230
+ }
231
+ else {
232
+ curve = [
233
+ [beginX, beginY + 12],
234
+ [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
235
+ [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
236
+ [endX, endY]
237
+ ];
238
+ const line = [
239
+ [beginX, beginY],
240
+ [beginX, beginY + 12],
241
+ [beginX, beginY + 12]
242
+ ];
243
+ curve = [...line, ...curve];
244
+ }
245
+ }
246
+ const points = pointsOnBezierCurves(curve);
247
+ return roughSVG.curve(points, { stroke, strokeWidth });
248
+ }
249
+ }
250
+
251
+ const drawPlaceholderDropNodeG = (dropTarget, roughSVG, fakeDropNodeG) => {
252
+ const targetComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(dropTarget.target);
253
+ const targetRect = getRectangleByNode(targetComponent.node);
254
+ if (dropTarget.detectResult && ['right', 'left'].includes(dropTarget.detectResult)) {
255
+ drawStraightDropNodeG(targetRect, dropTarget.detectResult, targetComponent, roughSVG, fakeDropNodeG);
256
+ }
257
+ if (targetComponent.parent && dropTarget.detectResult && ['top', 'bottom'].includes(dropTarget.detectResult)) {
258
+ const parentComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(targetComponent.parent.origin);
259
+ const targetIndex = parentComponent.node.origin.children.indexOf(targetComponent.node.origin);
260
+ drawCurvePlaceholderDropNodeG(targetRect, dropTarget.detectResult, targetIndex, targetComponent, roughSVG, parentComponent, fakeDropNodeG);
261
+ }
262
+ };
263
+ const drawCurvePlaceholderDropNodeG = (targetRect, detectResult, targetIndex, targetComponent, roughSVG, parentComponent, fakeDropNodeG) => {
264
+ const parentNodeLayout = MindmapQueries.getCorrectLayoutByElement(parentComponent.node.origin);
265
+ const layout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.parent.origin);
266
+ const strokeWidth = targetComponent.node.origin.linkLineWidth ? targetComponent.node.origin.linkLineWidth : STROKE_WIDTH;
267
+ let fakeX = targetComponent.node.x, fakeY = targetRect.y - 30, fakeRectangleStartX = targetRect.x, fakeRectangleEndX = targetRect.x + 30, fakeRectangleStartY = fakeY, fakeRectangleEndY = fakeRectangleStartY + 12, width = 30;
268
+ if (isLeftLayout(layout)) {
269
+ fakeX = targetComponent.node.x + targetComponent.node.width - 30;
270
+ fakeRectangleStartX = targetRect.x + targetRect.width - 30;
271
+ fakeRectangleEndX = targetRect.x + targetRect.width;
272
+ }
273
+ if (isHorizontalLogicLayout(parentNodeLayout)) {
274
+ fakeY = getHorizontalFakeY(detectResult, targetIndex, parentComponent.node, targetRect, layout, fakeY);
275
+ if (isStandardLayout(parentNodeLayout)) {
276
+ const rightNodeCount = parentComponent.node.origin.rightNodeCount || 0;
277
+ const idx = parentComponent.node.children.findIndex(x => x === targetComponent.node);
278
+ const isLeft = idx >= rightNodeCount;
279
+ // 标准布局的左,需要调整 x
280
+ if (isLeft) {
281
+ fakeX = targetComponent.node.x + targetComponent.node.width - 30;
282
+ fakeRectangleStartX = targetRect.x + targetRect.width - 30;
283
+ fakeRectangleEndX = targetRect.x + targetRect.width;
284
+ }
285
+ const isLeftFirst = idx === rightNodeCount;
286
+ const isRightLast = idx === rightNodeCount - 1;
287
+ // 拖拽至左第一个节点的情况
288
+ if (detectResult === 'top' && isLeftFirst) {
289
+ fakeY = targetRect.y - targetRect.height;
290
+ }
291
+ if (detectResult === 'bottom' && isRightLast) {
292
+ fakeY = targetRect.y + targetRect.height + 30;
293
+ }
294
+ }
295
+ fakeRectangleStartY = fakeY;
296
+ fakeRectangleEndY = fakeRectangleStartY + 12;
297
+ }
298
+ if (isVerticalLogicLayout(layout)) {
299
+ parentComponent = targetComponent;
300
+ targetComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(targetComponent.parent.origin);
301
+ fakeX = parentComponent.node.x;
302
+ width = parentComponent.node.width;
303
+ const vGap = BASE * 6 + strokeWidth;
304
+ if (isTopLayout(layout) && detectResult === 'top') {
305
+ fakeY = targetRect.y - vGap;
306
+ fakeRectangleStartY = fakeY - vGap + strokeWidth;
307
+ }
308
+ if (isBottomLayout(layout) && detectResult === 'bottom') {
309
+ fakeY = targetRect.y + targetRect.height + vGap;
310
+ fakeRectangleStartY = fakeY + vGap - strokeWidth;
311
+ }
312
+ fakeRectangleStartX = fakeX + Math.ceil(parentComponent.node.width / 2) - parentComponent.node.hGap - Math.ceil(strokeWidth / 2);
313
+ fakeRectangleEndX = fakeRectangleStartX + 30;
314
+ fakeRectangleEndY = fakeRectangleStartY + 12;
315
+ }
316
+ if (isIndentedLayout(layout)) {
317
+ // 偏移一个 Gap
318
+ if (isLeftLayout(layout)) {
319
+ fakeX -= BASE * 4;
320
+ }
321
+ if (isRightLayout(layout)) {
322
+ fakeX += BASE * 4;
323
+ }
324
+ if (isTopLayout(layout)) {
325
+ if (detectResult === 'top') {
326
+ const isLastNode = targetIndex === parentComponent.node.origin.children.length - 1;
327
+ if (isLastNode) {
328
+ fakeY = targetRect.y - targetRect.height - BASE;
329
+ }
330
+ else {
331
+ const nextComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(parentComponent.node.origin.children[targetIndex + 1]);
332
+ const nextRect = getRectangleByNode(nextComponent.node);
333
+ fakeY = targetRect.y - Math.abs((nextRect.y + nextRect.height - targetRect.y) / 2);
334
+ }
335
+ }
336
+ if (detectResult === 'bottom') {
337
+ const isFirstNode = targetIndex === 0;
338
+ if (isFirstNode) {
339
+ const parentRect = getRectangleByNode(parentComponent.node);
340
+ fakeY = parentRect.y - parentRect.height / 2 - BASE;
341
+ }
342
+ else {
343
+ const previousComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(parentComponent.node.origin.children[targetIndex - 1]);
344
+ const previousRect = getRectangleByNode(previousComponent.node);
345
+ fakeY = previousRect.y - Math.abs((targetRect.y + targetRect.height - previousRect.y) / 2);
346
+ }
347
+ }
348
+ }
349
+ fakeRectangleStartX = fakeX;
350
+ fakeRectangleEndX = fakeRectangleStartX + 30;
351
+ fakeRectangleStartY = fakeY;
352
+ fakeRectangleEndY = fakeRectangleStartY + 12;
353
+ }
354
+ // 构造一条曲线
355
+ const fakeNode = { ...targetComponent.node, x: fakeX, y: fakeY, width, height: 12 };
356
+ const linkSVGG = isIndentedLayout(layout)
357
+ ? drawIndentedLink(roughSVG, parentComponent.node, fakeNode, PRIMARY_COLOR, false)
358
+ : drawLink(roughSVG, parentComponent.node, fakeNode, PRIMARY_COLOR, isHorizontalLayout(layout), false);
359
+ // 构造一个矩形框坐标
360
+ const fakeRectangleG = drawRoundRectangle(roughSVG, fakeRectangleStartX, fakeRectangleStartY, fakeRectangleEndX, fakeRectangleEndY, {
361
+ stroke: PRIMARY_COLOR,
362
+ strokeWidth: 2,
363
+ fill: PRIMARY_COLOR,
364
+ fillStyle: 'solid'
365
+ });
366
+ fakeDropNodeG?.appendChild(linkSVGG);
367
+ fakeDropNodeG?.appendChild(fakeRectangleG);
368
+ };
369
+ const drawStraightDropNodeG = (targetRect, detectResult, targetComponent, roughSVG, fakeDropNodeG) => {
370
+ const { x, y, width, height } = targetRect;
371
+ const lineLength = 40;
372
+ let startLinePoint = x + width;
373
+ let endLinePoint = x + width + lineLength;
374
+ let startRectanglePointX = x + width + lineLength;
375
+ let endRectanglePointX = x + lineLength + width + 30;
376
+ let startRectanglePointY = y + height / 2 - 6;
377
+ let endRectanglePointY = y + height / 2 - 6 + 12;
378
+ if (detectResult === 'left') {
379
+ startLinePoint = x - lineLength;
380
+ endLinePoint = x;
381
+ startRectanglePointX = x - lineLength - 30;
382
+ endRectanglePointX = x - lineLength;
383
+ }
384
+ let fakeY = targetComponent.node.y;
385
+ let fakeX = targetRect.x;
386
+ const strokeWidth = targetComponent.node.origin.linkLineWidth ? targetComponent.node.origin.linkLineWidth : STROKE_WIDTH;
387
+ const pointOptions = {
388
+ fakeX,
389
+ fakeY,
390
+ x,
391
+ y,
392
+ width,
393
+ height,
394
+ strokeWidth
395
+ };
396
+ const parentLayout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
397
+ const layout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.origin);
398
+ if (!isMixedLayout(parentLayout, layout)) {
399
+ // 构造一条直线
400
+ let linePoints = [
401
+ [startLinePoint, y + height / 2],
402
+ [endLinePoint, y + height / 2]
403
+ ];
404
+ if (isIndentedLayout(parentLayout)) {
405
+ const fakePoint = getIndentedFakePoint(parentLayout, pointOptions);
406
+ drawIndentNodeG(fakeDropNodeG, roughSVG, fakePoint, targetComponent.node);
407
+ return;
408
+ }
409
+ else if (isVerticalLogicLayout(parentLayout)) {
410
+ if (!targetComponent.node.origin.isRoot) {
411
+ /**
412
+ * 计算逻辑:
413
+ * 1. 移动到左侧:当前节点 startX - 偏移值,偏移值计算如下:
414
+ * a. 第一个节点: 固定值(来源于 getMainAxle,第二级节点:BASE * 8,其他 BASE * 3 + strokeWidth / 2);
415
+ * b. 第二个节点到最后一个节点之间:上一个节点到当前节点间距的一半((当前节点 startX - 上一个节点的 endX) / 2),endX = 当前节点的 startX + width;
416
+ * 2. 移动到右侧:当前节点 x + width + 偏移值,偏移值计算如下:
417
+ * a. 第二个节点到最后一个节点之间的右侧:当前节点到下一个节点间距的一半((下一个节点 startX - 当前节点的 endX) / 2),endX = 当前节点的 startX + width;
418
+ * b. 最后一个节点的右侧:固定值(来源于 getMainAxle,第二级节点:BASE * 8,其他 BASE * 3 + strokeWidth / 2);
419
+ */
420
+ fakeY = targetComponent.node.y;
421
+ const parentComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(targetComponent.parent.origin);
422
+ const targetIndex = parentComponent.node.origin.children.indexOf(targetComponent.node.origin);
423
+ if (detectResult === 'left') {
424
+ let offsetX = 0;
425
+ const isFirstNode = targetIndex === 0;
426
+ if (isFirstNode) {
427
+ offsetX = parentComponent.node.origin.isRoot ? BASE * 8 : BASE * 3 + strokeWidth / 2;
428
+ }
429
+ else {
430
+ const previousComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(parentComponent.node.origin.children[targetIndex - 1]);
431
+ const previousRect = getRectangleByNode(previousComponent.node);
432
+ const space = targetRect.x - (previousRect.x + previousRect.width);
433
+ offsetX = space / 2;
434
+ }
435
+ fakeX = targetRect.x - offsetX - width / 2 - Math.ceil(strokeWidth / 2);
436
+ }
437
+ if (detectResult === 'right') {
438
+ let offsetX = 0;
439
+ const isLastNode = targetIndex === parentComponent.node.origin.children.length - 1;
440
+ if (isLastNode) {
441
+ offsetX = parentComponent.node.origin.isRoot ? BASE * 8 : BASE * 3 + strokeWidth / 2;
442
+ }
443
+ else {
444
+ const nextComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(parentComponent.node.origin.children[targetIndex + 1]);
445
+ const nextRect = getRectangleByNode(nextComponent.node);
446
+ const space = nextRect.x - (targetRect.x + targetRect.width);
447
+ offsetX = space / 2;
448
+ }
449
+ fakeX = targetRect.x + width + offsetX - width / 2 - Math.ceil(strokeWidth / 2);
450
+ }
451
+ startRectanglePointX = fakeX;
452
+ if (isTopLayout(parentLayout)) {
453
+ // 因为矩形是从左上角为起点向下画的,所以需要向上偏移一个矩形的高度(-12)
454
+ startRectanglePointY = fakeY + height + targetComponent.node.vGap - 12;
455
+ }
456
+ if (isBottomLayout(parentLayout)) {
457
+ startRectanglePointY = fakeY + targetComponent.node.vGap;
458
+ }
459
+ endRectanglePointX = startRectanglePointX + 30;
460
+ endRectanglePointY = startRectanglePointY + 12;
461
+ const fakeNode = { ...targetComponent.node, x: fakeX, y: fakeY, width: 30 };
462
+ const linkSVGG = drawLink(roughSVG, parentComponent.node, fakeNode, PRIMARY_COLOR, false, false);
463
+ fakeDropNodeG?.appendChild(linkSVGG);
464
+ }
465
+ }
466
+ else {
467
+ let linkSVGG = roughSVG.linearPath(linePoints, { stroke: PRIMARY_COLOR, strokeWidth });
468
+ fakeDropNodeG?.appendChild(linkSVGG);
469
+ }
470
+ // 构造一个矩形框坐标
471
+ let fakeRectangleG = drawRoundRectangle(roughSVG, startRectanglePointX, startRectanglePointY, endRectanglePointX, endRectanglePointY, {
472
+ stroke: PRIMARY_COLOR,
473
+ strokeWidth: 2,
474
+ fill: PRIMARY_COLOR,
475
+ fillStyle: 'solid'
476
+ });
477
+ fakeDropNodeG?.appendChild(fakeRectangleG);
478
+ }
479
+ else {
480
+ // 混合布局画线逻辑
481
+ if (isHorizontalLogicLayout(parentLayout)) {
482
+ if (isIndentedLayout(layout)) {
483
+ const fakePoint = getIndentedFakePoint(layout, pointOptions);
484
+ drawIndentNodeG(fakeDropNodeG, roughSVG, fakePoint, targetComponent.node);
485
+ return;
486
+ }
487
+ }
488
+ }
489
+ };
490
+ const getHorizontalFakeY = (detectResult, targetIndex, parentNode, targetRect, layout, fakeY) => {
491
+ if (detectResult === 'top') {
492
+ if (targetIndex === 0 && isTopLayout(layout)) {
493
+ fakeY = targetRect.y + targetRect.height;
494
+ }
495
+ if (targetIndex > 0) {
496
+ const previousComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(parentNode.origin.children[targetIndex - 1]);
497
+ const previousRect = getRectangleByNode(previousComponent.node);
498
+ const topY = previousRect.y + previousRect.height;
499
+ fakeY = topY + (targetRect.y - topY) / 5;
500
+ }
501
+ }
502
+ if (detectResult === 'bottom') {
503
+ fakeY = targetRect.y + targetRect.height + 30;
504
+ if (targetIndex < parentNode.origin.children.length - 1) {
505
+ const nextComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(parentNode.origin.children[targetIndex + 1]);
506
+ const nextRect = getRectangleByNode(nextComponent.node);
507
+ const topY = targetRect.y + targetRect.height;
508
+ fakeY = topY + (nextRect.y - topY) / 5;
509
+ }
510
+ if (targetIndex === parentNode.origin.children.length - 1) {
511
+ fakeY = targetRect.y + targetRect.height + 30;
512
+ }
513
+ }
514
+ return fakeY;
515
+ };
516
+ const getIndentedFakePoint = (layout, pointOptions) => {
517
+ let { fakeX, fakeY, x, y, width, height, strokeWidth } = pointOptions;
518
+ const hGap = BASE * 4;
519
+ const vGap = BASE * 6;
520
+ const offsetX = hGap + width / 2 + strokeWidth;
521
+ const offsetY = vGap + height / 2 + strokeWidth;
522
+ if (isLeftLayout(layout)) {
523
+ fakeX = x - offsetX;
524
+ }
525
+ if (isRightLayout(layout)) {
526
+ fakeX = x + offsetX;
527
+ }
528
+ if (isTopLayout(layout)) {
529
+ fakeY = y - offsetY;
530
+ }
531
+ if (isBottomLayout(layout)) {
532
+ fakeY = y + height + offsetY;
533
+ }
534
+ return { fakeX, fakeY };
535
+ };
536
+ const drawIndentNodeG = (fakeDropNodeG, roughSVG, fakePoint, node) => {
537
+ const { fakeX, fakeY } = fakePoint;
538
+ const fakeNode = { ...node, x: fakeX, y: fakeY, width: 30, height: 12 };
539
+ const linkSVGG = drawIndentedLink(roughSVG, node, fakeNode, PRIMARY_COLOR, false);
540
+ const startRectanglePointX = fakeX, startRectanglePointY = fakeY, endRectanglePointX = fakeX + 30, endRectanglePointY = fakeY + 12;
541
+ const fakeRectangleG = drawRoundRectangle(roughSVG, startRectanglePointX, startRectanglePointY, endRectanglePointX, endRectanglePointY, {
542
+ stroke: PRIMARY_COLOR,
543
+ strokeWidth: 2,
544
+ fill: PRIMARY_COLOR,
545
+ fillStyle: 'solid'
546
+ });
547
+ fakeDropNodeG?.appendChild(linkSVGG);
548
+ fakeDropNodeG?.appendChild(fakeRectangleG);
549
+ };
550
+
551
+ /**
552
+ *
553
+ * @param targetNode
554
+ * @param centerPoint
555
+ * @returns DetectResult[] | null
556
+ */
557
+ const directionDetector = (targetNode, centerPoint) => {
558
+ const { x, y, width, height } = getRectangleByNode(targetNode);
559
+ const yCenter = y + height / 2;
560
+ const xCenter = x + width / 2;
561
+ const top = targetNode.y;
562
+ const bottom = targetNode.y + targetNode.height;
563
+ const left = targetNode.x;
564
+ const right = targetNode.x + targetNode.width;
565
+ const direction = [];
566
+ // x 轴
567
+ if (centerPoint[1] > y && centerPoint[1] < y + height) {
568
+ if (centerPoint[0] > left && centerPoint[0] < xCenter) {
569
+ direction.push('left');
570
+ }
571
+ if (centerPoint[0] > xCenter && centerPoint[0] < right) {
572
+ direction.push('right');
573
+ }
574
+ // 重合区域,返回两个方向
575
+ if ((centerPoint[0] > x && centerPoint[0] < xCenter) || (centerPoint[0] > xCenter && centerPoint[0] < x + width)) {
576
+ if (centerPoint[1] < yCenter) {
577
+ direction.push('top');
578
+ }
579
+ else {
580
+ direction.push('bottom');
581
+ }
582
+ }
583
+ return direction.length ? direction : null;
584
+ }
585
+ // y 轴
586
+ if (centerPoint[0] > x && centerPoint[0] < x + width) {
587
+ if (centerPoint[1] > top && centerPoint[1] < yCenter) {
588
+ direction.push('top');
589
+ }
590
+ if (centerPoint[1] > yCenter && centerPoint[1] < bottom) {
591
+ direction.push('bottom');
592
+ }
593
+ if ((centerPoint[1] > y && centerPoint[1] < y + height) || (centerPoint[1] > yCenter && centerPoint[1] < y + height)) {
594
+ if (centerPoint[0] < xCenter) {
595
+ direction.push('left');
596
+ }
597
+ else {
598
+ direction.push('right');
599
+ }
600
+ }
601
+ return direction.length ? direction : null;
602
+ }
603
+ return null;
604
+ };
605
+
606
+ const directionCorrector = (node, detectResults) => {
607
+ if (!node.origin.isRoot) {
608
+ const parentlayout = MindmapQueries.getCorrectLayoutByElement(node?.parent.origin);
609
+ if (isStandardLayout(parentlayout)) {
610
+ const idx = node.parent.children.findIndex(x => x === node);
611
+ const isLeft = idx >= (node.parent.origin.rightNodeCount || 0);
612
+ return getAllowedDirection(detectResults, [isLeft ? 'right' : 'left']);
613
+ }
614
+ if (isLeftLayout(parentlayout)) {
615
+ return getAllowedDirection(detectResults, ['right']);
616
+ }
617
+ if (isRightLayout(parentlayout)) {
618
+ return getAllowedDirection(detectResults, ['left']);
619
+ }
620
+ if (parentlayout === MindmapLayoutType.upward) {
621
+ return getAllowedDirection(detectResults, ['bottom']);
622
+ }
623
+ if (parentlayout === MindmapLayoutType.downward) {
624
+ return getAllowedDirection(detectResults, ['top']);
625
+ }
626
+ }
627
+ else {
628
+ const layout = MindmapQueries.getCorrectLayoutByElement(node?.origin);
629
+ if (isStandardLayout(layout)) {
630
+ return getAllowedDirection(detectResults, ['top', 'bottom']);
631
+ }
632
+ if (isTopLayout(layout)) {
633
+ return getAllowedDirection(detectResults, ['left', 'right', 'bottom']);
634
+ }
635
+ if (isBottomLayout(layout)) {
636
+ return getAllowedDirection(detectResults, ['left', 'right', 'top']);
637
+ }
638
+ if (layout === MindmapLayoutType.left) {
639
+ return getAllowedDirection(detectResults, ['right', 'top', 'bottom']);
640
+ }
641
+ if (layout === MindmapLayoutType.right) {
642
+ return getAllowedDirection(detectResults, ['left', 'top', 'bottom']);
643
+ }
644
+ }
645
+ return null;
646
+ };
647
+ const getAllowedDirection = (detectResults, illegalDirections) => {
648
+ const directions = detectResults;
649
+ illegalDirections.forEach(item => {
650
+ const bottomDirectionIndex = directions.findIndex(direction => direction === item);
651
+ if (bottomDirectionIndex !== -1) {
652
+ directions.splice(bottomDirectionIndex, 1);
653
+ }
654
+ });
655
+ return directions.length ? directions : null;
656
+ };
657
+
658
+ /* 根据布局调整 target 以及 direction */
659
+ const readjustmentDropTarget = (dropTarget) => {
660
+ const { target, detectResult } = dropTarget;
661
+ const newDropTarget = { target, detectResult };
662
+ const targetComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(target);
663
+ if (targetComponent.node.children.length > 0 && dropTarget.detectResult) {
664
+ const layout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.origin);
665
+ const parentLayout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
666
+ if (['right', 'left'].includes(dropTarget.detectResult)) {
667
+ if (!isMixedLayout(parentLayout, layout)) {
668
+ if (targetComponent.node.origin.isRoot) {
669
+ const layout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.origin);
670
+ // 标准布局,根节点
671
+ if (isStandardLayout(layout)) {
672
+ const rightNodeCount = targetComponent.node.origin.rightNodeCount;
673
+ if (detectResult === 'left') {
674
+ // 作为左的第一个节点
675
+ if (targetComponent.node.children.length === rightNodeCount) {
676
+ return newDropTarget;
677
+ }
678
+ }
679
+ else {
680
+ // 作为右的第一个节点或最后一个节点
681
+ if (rightNodeCount === 0) {
682
+ newDropTarget.target = target;
683
+ }
684
+ else {
685
+ newDropTarget.target = targetComponent.node.children[rightNodeCount - 1].origin;
686
+ newDropTarget.detectResult = 'bottom';
687
+ }
688
+ return newDropTarget;
689
+ }
690
+ }
691
+ }
692
+ // 缩进布局探测到第一个子节点
693
+ if (isIndentedLayout(parentLayout)) {
694
+ newDropTarget.target = targetComponent.node.children[0].origin;
695
+ newDropTarget.detectResult = isTopLayout(parentLayout) ? 'bottom' : 'top';
696
+ return newDropTarget;
697
+ }
698
+ // 上下布局的根节点只可以探测到上或者下,子节点的左右探测不处理,跳过。
699
+ if (isVerticalLogicLayout(parentLayout)) {
700
+ return newDropTarget;
701
+ }
702
+ // 剩下是水平布局的默认情况:插入最后一个子节点的下方
703
+ const lastChildNodeIndex = targetComponent.node.children.length - 1;
704
+ newDropTarget.target = targetComponent.node.children[lastChildNodeIndex].origin;
705
+ newDropTarget.detectResult = 'bottom';
706
+ }
707
+ else {
708
+ // 处理左右布局下的混合布局
709
+ if ([MindmapLayoutType.left, MindmapLayoutType.right].includes(parentLayout)) {
710
+ const layout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.origin);
711
+ if (isIndentedLayout(layout)) {
712
+ newDropTarget.target = targetComponent.node.children[0].origin;
713
+ newDropTarget.detectResult = isTopLayout(layout) ? 'bottom' : 'top';
714
+ return newDropTarget;
715
+ }
716
+ }
717
+ }
718
+ }
719
+ if (['top', 'bottom'].includes(dropTarget.detectResult)) {
720
+ // 缩进布局移动至第一个节点
721
+ if (targetComponent.node.origin.isRoot && isIndentedLayout(layout)) {
722
+ newDropTarget.target = targetComponent.node.children[0].origin;
723
+ newDropTarget.detectResult = isTopLayout(layout) ? 'bottom' : 'top';
724
+ return newDropTarget;
725
+ }
726
+ // 上下布局,插到右边
727
+ const parentLayout = MindmapQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
728
+ if (isVerticalLogicLayout(parentLayout)) {
729
+ const lastChildNodeIndex = targetComponent.node.children.length - 1;
730
+ newDropTarget.target = targetComponent.node.children[lastChildNodeIndex].origin;
731
+ newDropTarget.detectResult = 'right';
732
+ return newDropTarget;
733
+ }
734
+ }
735
+ return newDropTarget;
736
+ }
737
+ return dropTarget;
738
+ };
739
+
740
+ const separateChildren = (parentElement) => {
741
+ const rightNodeCount = parentElement.rightNodeCount;
742
+ const children = parentElement.children;
743
+ let rightChildren = [], leftChildren = [];
744
+ for (let i = 0; i < children.length; i++) {
745
+ const child = children[i];
746
+ if (AbstractNode.isAbstract(child) && child.end < rightNodeCount) {
747
+ rightChildren.push(child);
748
+ continue;
749
+ }
750
+ if (AbstractNode.isAbstract(child) && child.start >= rightNodeCount) {
751
+ leftChildren.push(child);
752
+ continue;
753
+ }
754
+ if (i < rightNodeCount) {
755
+ rightChildren.push(child);
756
+ }
757
+ else {
758
+ leftChildren.push(child);
759
+ }
760
+ }
761
+ return { leftChildren, rightChildren };
762
+ };
763
+
764
+ const getRectangleByResizingLocation = (abstractRectangle, location, handlePosition, isHorizontal) => {
765
+ if (isHorizontal) {
766
+ if (handlePosition === AbstractHandlePosition.start) {
767
+ return {
768
+ ...abstractRectangle,
769
+ y: location,
770
+ height: abstractRectangle.height + abstractRectangle.y - location
771
+ };
772
+ }
773
+ else {
774
+ return {
775
+ ...abstractRectangle,
776
+ height: location - abstractRectangle.y
777
+ };
778
+ }
779
+ }
780
+ else {
781
+ if (handlePosition === AbstractHandlePosition.start) {
782
+ return {
783
+ ...abstractRectangle,
784
+ x: location,
785
+ width: abstractRectangle.width + abstractRectangle.x - location
786
+ };
787
+ }
788
+ else {
789
+ return {
790
+ ...abstractRectangle,
791
+ width: location - abstractRectangle.x
792
+ };
793
+ }
794
+ }
795
+ };
796
+ const getLocationScope = (board, handlePosition, parentChildren, element, parent, isHorizontal) => {
797
+ const node = MindElement.getNode(element);
798
+ const { start, end } = getCorrectStartEnd(node.origin, parent);
799
+ const startNode = parentChildren[start];
800
+ const endNode = parentChildren[end];
801
+ if (handlePosition === AbstractHandlePosition.start) {
802
+ const abstractNode = parentChildren.filter(child => AbstractNode.isAbstract(child) && child.end < element.start);
803
+ let minNode;
804
+ if (abstractNode.length) {
805
+ const index = abstractNode
806
+ .map(node => {
807
+ const { end } = getCorrectStartEnd(node, parent);
808
+ return end;
809
+ })
810
+ .sort((a, b) => b - a)[0];
811
+ minNode = parentChildren[index + 1];
812
+ }
813
+ else {
814
+ minNode = parentChildren[0];
815
+ }
816
+ const minNodeRectangle = getRectangleByElements(board, [minNode], true);
817
+ const endNodeRectangle = getRectangleByElements(board, [endNode], false);
818
+ if (isHorizontal) {
819
+ return {
820
+ max: endNodeRectangle.y - ABSTRACT_INCLUDED_OUTLINE_OFFSET,
821
+ min: minNodeRectangle.y - ABSTRACT_INCLUDED_OUTLINE_OFFSET
822
+ };
823
+ }
824
+ else {
825
+ return {
826
+ max: endNodeRectangle.x - ABSTRACT_INCLUDED_OUTLINE_OFFSET,
827
+ min: minNodeRectangle.x - ABSTRACT_INCLUDED_OUTLINE_OFFSET
828
+ };
829
+ }
830
+ }
831
+ else {
832
+ const abstractNode = parentChildren.filter(child => AbstractNode.isAbstract(child) && child.start > element.end);
833
+ let maxNode;
834
+ if (abstractNode.length) {
835
+ const index = abstractNode
836
+ .map(node => {
837
+ const { start } = getCorrectStartEnd(node, parent);
838
+ return start;
839
+ })
840
+ .sort((a, b) => a - b)[0];
841
+ maxNode = parentChildren[index - 1];
842
+ }
843
+ else {
844
+ const children = parentChildren.filter(child => !AbstractNode.isAbstract(child));
845
+ maxNode = parentChildren[children.length - 1];
846
+ }
847
+ const maxNodeRectangle = getRectangleByElements(board, [maxNode], true);
848
+ const startNodeRectangle = getRectangleByElements(board, [startNode], false);
849
+ if (isHorizontal) {
850
+ return {
851
+ max: maxNodeRectangle.y + maxNodeRectangle.height + ABSTRACT_INCLUDED_OUTLINE_OFFSET,
852
+ min: startNodeRectangle.y + startNodeRectangle.height + ABSTRACT_INCLUDED_OUTLINE_OFFSET
853
+ };
854
+ }
855
+ else {
856
+ return {
857
+ max: maxNodeRectangle.x + maxNodeRectangle.width + ABSTRACT_INCLUDED_OUTLINE_OFFSET,
858
+ min: startNodeRectangle.x + startNodeRectangle.width + ABSTRACT_INCLUDED_OUTLINE_OFFSET
859
+ };
860
+ }
861
+ }
862
+ };
863
+ const getHitAbstractHandle = (board, element, point) => {
864
+ const nodeLayout = MindmapQueries.getCorrectLayoutByElement(element);
865
+ const isHorizontal = isHorizontalLayout(nodeLayout);
866
+ const parentElement = MindElement.getParent(element);
867
+ const includedElements = parentElement.children.slice(element.start, element.end + 1);
868
+ let abstractRectangle = getRectangleByElements(board, includedElements, true);
869
+ abstractRectangle = RectangleClient.getOutlineRectangle(abstractRectangle, -ABSTRACT_INCLUDED_OUTLINE_OFFSET);
870
+ const startHandleRec = getAbstractHandleRectangle(abstractRectangle, isHorizontal, AbstractHandlePosition.start);
871
+ const endHandleRec = getAbstractHandleRectangle(abstractRectangle, isHorizontal, AbstractHandlePosition.end);
872
+ const pointRec = RectangleClient.toRectangleClient([point, point]);
873
+ if (RectangleClient.isIntersect(pointRec, startHandleRec))
874
+ return AbstractHandlePosition.start;
875
+ if (RectangleClient.isIntersect(pointRec, endHandleRec))
876
+ return AbstractHandlePosition.end;
877
+ return null;
878
+ };
879
+ const getAbstractHandleRectangle = (rectangle, isHorizontal, position) => {
880
+ let result;
881
+ if (position === AbstractHandlePosition.start) {
882
+ const location = isHorizontal ? rectangle.y : rectangle.x;
883
+ result = getRectangleByResizingLocation(rectangle, location + ABSTRACT_HANDLE_MASK_WIDTH / 2, AbstractHandlePosition.end, isHorizontal);
884
+ result = getRectangleByResizingLocation(result, location - ABSTRACT_HANDLE_MASK_WIDTH / 2, position, isHorizontal);
885
+ }
886
+ else {
887
+ const location = isHorizontal ? rectangle.y + rectangle.height : rectangle.x + rectangle.width;
888
+ result = getRectangleByResizingLocation(rectangle, location - ABSTRACT_HANDLE_MASK_WIDTH / 2, AbstractHandlePosition.start, isHorizontal);
889
+ result = getRectangleByResizingLocation(result, location + ABSTRACT_HANDLE_MASK_WIDTH / 2, position, isHorizontal);
890
+ }
891
+ return result;
892
+ };
893
+ function findLocationLeftIndex(board, parentChildren, location, isHorizontal) {
894
+ const children = parentChildren.filter(child => {
895
+ return !AbstractNode.isAbstract(child);
896
+ });
897
+ const recArray = children.map(child => {
898
+ return getRectangleByElements(board, [child], false);
899
+ });
900
+ const firstRec = getRectangleByElements(board, [children[0]], true);
901
+ const fakeLeftRec = {
902
+ x: firstRec.x - firstRec.width,
903
+ y: firstRec.y - firstRec.height,
904
+ width: firstRec.width,
905
+ height: firstRec.height
906
+ };
907
+ const lastRec = getRectangleByElements(board, [children[children.length - 1]], true);
908
+ const fakeRightRec = {
909
+ x: lastRec.x + lastRec.width,
910
+ y: lastRec.y + lastRec.height,
911
+ width: lastRec.width,
912
+ height: lastRec.height
913
+ };
914
+ recArray.push(fakeRightRec);
915
+ recArray.unshift(fakeLeftRec);
916
+ for (let i = 0; i < recArray.length - 1; i++) {
917
+ const recXOrY = isHorizontal ? recArray[i].y : recArray[i].x;
918
+ const recWidthOrHeight = isHorizontal ? recArray[i].height : recArray[i].width;
919
+ if (location >= recXOrY + recWidthOrHeight / 2 &&
920
+ location <= recArray[i + 1][isHorizontal ? 'y' : 'x'] + recArray[i + 1][isHorizontal ? 'height' : 'width'] / 2) {
921
+ return i - 1;
922
+ }
923
+ }
924
+ return 0;
925
+ }
926
+
927
+ /**
928
+ * get correctly layout:
929
+ * 1. root is standard -> left or right
930
+ * 2. correct layout by incorrect layout direction
931
+ * @param element
932
+ */
933
+ const getCorrectLayoutByElement = (element) => {
934
+ const { root } = findUpElement(element);
935
+ const rootLayout = root.layout || getDefaultMindmapLayout();
936
+ let correctRootLayout = rootLayout;
937
+ if (element.isRoot) {
938
+ return correctRootLayout;
939
+ }
940
+ const component = MINDMAP_ELEMENT_TO_COMPONENT.get(element);
941
+ let layout = component?.node.origin.layout;
942
+ let parentComponent;
943
+ let parent = component?.parent?.origin;
944
+ while (!layout && parent) {
945
+ parentComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(parent);
946
+ layout = parentComponent?.node.origin.layout;
947
+ parent = parentComponent?.parent?.origin;
948
+ }
949
+ if ((AbstractNode.isAbstract(element) || isChildOfAbstract(MindElement.getNode(element))) &&
950
+ isIndentedLayout(layout)) {
951
+ return getAbstractLayout(layout);
952
+ }
953
+ // handle root standard
954
+ if (rootLayout === MindmapLayoutType.standard) {
955
+ correctRootLayout = component?.node.left ? MindmapLayoutType.left : MindmapLayoutType.right;
956
+ }
957
+ if (parentComponent?.node.origin.isRoot) {
958
+ return correctRootLayout;
959
+ }
960
+ if (layout) {
961
+ const incorrectDirection = getInCorrectLayoutDirection(correctRootLayout, layout);
962
+ if (incorrectDirection) {
963
+ return correctLayoutByDirection(layout, incorrectDirection);
964
+ }
965
+ else {
966
+ return layout;
967
+ }
968
+ }
969
+ else {
970
+ return correctRootLayout;
971
+ }
972
+ };
973
+
974
+ const getBranchMindmapLayouts = (element) => {
975
+ const layouts = [];
976
+ if (element.layout) {
977
+ //getCorrectLayoutByElement含有递归操作,getBranchMindmapLayouts本身也有递归操作,有待优化
978
+ layouts.unshift(getCorrectLayoutByElement(element));
979
+ }
980
+ let parent = findParentElement(element);
981
+ while (parent) {
982
+ if (parent.layout) {
983
+ layouts.unshift(parent.layout);
984
+ }
985
+ parent = findParentElement(parent);
986
+ }
987
+ return layouts;
988
+ };
989
+
990
+ /**
991
+ * get available sub layouts by element
992
+ * @param element
993
+ * @returns MindmapLayoutType[]
994
+ */
995
+ const getAvailableSubLayoutsByElement = (element) => {
996
+ const parentElement = findParentElement(element);
997
+ if (parentElement) {
998
+ const branchLayouts = getBranchMindmapLayouts(parentElement);
999
+ if (branchLayouts[0] === MindmapLayoutType.standard) {
1000
+ const component = MINDMAP_ELEMENT_TO_COMPONENT.get(element);
1001
+ if (component) {
1002
+ branchLayouts[0] = component.node.left ? MindmapLayoutType.left : MindmapLayoutType.right;
1003
+ }
1004
+ }
1005
+ const currentLayoutDirections = getBranchDirectionsByLayouts(branchLayouts);
1006
+ let availableSubLayouts = getAvailableSubLayoutsByLayoutDirections(currentLayoutDirections);
1007
+ const parentLayout = [branchLayouts[branchLayouts.length - 1]];
1008
+ const parentDirections = getBranchDirectionsByLayouts(parentLayout);
1009
+ const parentAvailableSubLayouts = getAvailableSubLayoutsByLayoutDirections(parentDirections);
1010
+ availableSubLayouts = availableSubLayouts.filter(layout => parentAvailableSubLayouts.some(parentAvailableSubLayout => parentAvailableSubLayout === layout));
1011
+ return availableSubLayouts;
1012
+ }
1013
+ return undefined;
1014
+ };
1015
+
1016
+ /**
1017
+ * 获取父节点布局类型
1018
+ * @param element
1019
+ * @returns MindmapLayoutType
1020
+ */
1021
+ const getLayoutParentByElement = (element) => {
1022
+ let parent = findParentElement(element);
1023
+ while (parent) {
1024
+ if (parent.layout) {
1025
+ return parent.layout;
1026
+ }
1027
+ parent = findParentElement(parent);
1028
+ }
1029
+ return getDefaultMindmapLayout();
1030
+ };
1031
+
1032
+ const getLayoutByElement = (element) => {
1033
+ const layout = element.layout;
1034
+ if (layout) {
1035
+ return layout;
1036
+ }
1037
+ if (AbstractNode.isAbstract(element) ||
1038
+ (isChildOfAbstract(MindElement.getNode(element)) && isIndentedLayout(layout))) {
1039
+ const parentLayout = getLayoutParentByElement(element);
1040
+ return getAbstractLayout(parentLayout);
1041
+ }
1042
+ return getLayoutParentByElement(element);
1043
+ };
1044
+
1045
+ const MindmapQueries = {
1046
+ getAvailableSubLayoutsByElement,
1047
+ getLayoutParentByElement,
1048
+ getBranchMindmapLayouts,
1049
+ getLayoutByElement,
1050
+ getCorrectLayoutByElement
1051
+ };
1052
+
1053
+ const PlaitMind = {
1054
+ isMind: (value) => {
1055
+ return value.type === 'mindmap';
1056
+ }
1057
+ };
1058
+ const MindElement = {
1059
+ hasLayout(value, layout) {
1060
+ const _layout = MindmapQueries.getLayoutByElement(value);
1061
+ return _layout === layout;
1062
+ },
1063
+ isIndentedLayout(value) {
1064
+ const _layout = MindmapQueries.getLayoutByElement(value);
1065
+ return isIndentedLayout(_layout);
1066
+ },
1067
+ isMindElement(board, element) {
1068
+ const path = PlaitBoard.findPath(board, element);
1069
+ const rootElement = PlaitNode.get(board, path.slice(0, 1));
1070
+ if (PlaitMind.isMind(rootElement)) {
1071
+ return true;
1072
+ }
1073
+ else {
1074
+ return false;
1075
+ }
1076
+ },
1077
+ getParent(node) {
1078
+ if (PlaitMind.isMind(node)) {
1079
+ throw new Error('mind root node can not get parent');
1080
+ }
1081
+ const parent = NODE_TO_PARENT.get(node);
1082
+ return parent;
1083
+ },
1084
+ getRoot(board, element) {
1085
+ const path = PlaitBoard.findPath(board, element);
1086
+ return PlaitNode.get(board, path.slice(0, 1));
1087
+ },
1088
+ getNode(element) {
1089
+ const node = ELEMENT_TO_NODE.get(element);
1090
+ if (!node) {
1091
+ throw new Error(`can not get node from ${JSON.stringify(element)}`);
1092
+ }
1093
+ return node;
1094
+ },
1095
+ hasEmojis(element) {
1096
+ if (element.data.emojis) {
1097
+ return true;
1098
+ }
1099
+ else {
1100
+ return false;
1101
+ }
1102
+ },
1103
+ getEmojis(element) {
1104
+ return element.data.emojis;
1105
+ }
1106
+ };
1107
+
1108
+ const MindmapNode = {
1109
+ get(root, path) {
1110
+ let node = root;
1111
+ for (let i = 0; i < path.length; i++) {
1112
+ const p = path[i];
1113
+ if (!node || !node.children || !node.children[p]) {
1114
+ throw new Error(`Cannot find a descendant at path [${path}]`);
1115
+ }
1116
+ node = node.children[p];
1117
+ }
1118
+ return node;
1119
+ },
1120
+ isEquals(node, otherNode) {
1121
+ const hasSameSize = node.x === otherNode.x && node.y === otherNode.y && node.width === otherNode.width && node.height === otherNode.height;
1122
+ const hasSameOrigin = node.origin === otherNode.origin;
1123
+ let hasSameParentOriginChildren = false;
1124
+ if (node.parent && otherNode.parent) {
1125
+ hasSameParentOriginChildren = node.parent.origin.children == otherNode.parent.origin.children;
1126
+ }
1127
+ return hasSameSize && hasSameOrigin && hasSameParentOriginChildren;
1128
+ }
1129
+ };
1130
+
1131
+ var LayoutDirection;
1132
+ (function (LayoutDirection) {
1133
+ LayoutDirection["top"] = "top";
1134
+ LayoutDirection["right"] = "right";
1135
+ LayoutDirection["bottom"] = "bottom";
1136
+ LayoutDirection["left"] = "left";
1137
+ })(LayoutDirection || (LayoutDirection = {}));
1138
+ const LayoutDirectionsMap = {
1139
+ [MindmapLayoutType.right]: [LayoutDirection.right],
1140
+ [MindmapLayoutType.left]: [LayoutDirection.left],
1141
+ [MindmapLayoutType.upward]: [LayoutDirection.top],
1142
+ [MindmapLayoutType.downward]: [LayoutDirection.bottom],
1143
+ [MindmapLayoutType.rightBottomIndented]: [LayoutDirection.right, LayoutDirection.bottom],
1144
+ [MindmapLayoutType.rightTopIndented]: [LayoutDirection.right, LayoutDirection.top],
1145
+ [MindmapLayoutType.leftBottomIndented]: [LayoutDirection.left, LayoutDirection.bottom],
1146
+ [MindmapLayoutType.leftTopIndented]: [LayoutDirection.left, LayoutDirection.top]
1147
+ };
1148
+
1149
+ var AbstractHandlePosition;
1150
+ (function (AbstractHandlePosition) {
1151
+ AbstractHandlePosition["start"] = "start";
1152
+ AbstractHandlePosition["end"] = "end";
1153
+ })(AbstractHandlePosition || (AbstractHandlePosition = {}));
1154
+
1155
+ const getBranchDirectionsByLayouts = (branchLayouts) => {
1156
+ const branchDirections = [];
1157
+ branchLayouts.forEach(l => {
1158
+ const directions = LayoutDirectionsMap[l];
1159
+ directions.forEach(d => {
1160
+ if (!branchDirections.includes(d) && !branchDirections.includes(getLayoutReverseDirection(d))) {
1161
+ branchDirections.push(d);
1162
+ }
1163
+ });
1164
+ });
1165
+ return branchDirections;
1166
+ };
1167
+ const isCorrectLayout = (root, layout) => {
1168
+ const rootLayout = root.layout || getDefaultMindmapLayout();
1169
+ return !getInCorrectLayoutDirection(rootLayout, layout);
1170
+ };
1171
+ const isMixedLayout = (parentLayout, layout) => {
1172
+ return (!isIndentedLayout(parentLayout) && isIndentedLayout(layout)) || (isIndentedLayout(parentLayout) && !isIndentedLayout(layout));
1173
+ };
1174
+ const getInCorrectLayoutDirection = (rootLayout, layout) => {
1175
+ const mindmapDirections = LayoutDirectionsMap[rootLayout];
1176
+ const subLayoutDirections = LayoutDirectionsMap[layout];
1177
+ if (!subLayoutDirections) {
1178
+ throw new Error(`unexpection layout: ${layout} on correct layout`);
1179
+ }
1180
+ return subLayoutDirections.find(d => mindmapDirections.includes(getLayoutReverseDirection(d)));
1181
+ };
1182
+ const correctLayoutByDirection = (layout, direction) => {
1183
+ const isHorizontal = direction === LayoutDirection.left || direction === LayoutDirection.right ? true : false;
1184
+ let inverseDirectionLayout = MindmapLayoutType.standard;
1185
+ switch (layout) {
1186
+ case MindmapLayoutType.left:
1187
+ inverseDirectionLayout = MindmapLayoutType.right;
1188
+ break;
1189
+ case MindmapLayoutType.right:
1190
+ inverseDirectionLayout = MindmapLayoutType.left;
1191
+ break;
1192
+ case MindmapLayoutType.downward:
1193
+ inverseDirectionLayout = MindmapLayoutType.upward;
1194
+ break;
1195
+ case MindmapLayoutType.upward:
1196
+ inverseDirectionLayout = MindmapLayoutType.downward;
1197
+ break;
1198
+ case MindmapLayoutType.rightBottomIndented:
1199
+ inverseDirectionLayout = isHorizontal ? MindmapLayoutType.leftBottomIndented : MindmapLayoutType.rightTopIndented;
1200
+ break;
1201
+ case MindmapLayoutType.leftBottomIndented:
1202
+ inverseDirectionLayout = isHorizontal ? MindmapLayoutType.rightBottomIndented : MindmapLayoutType.leftTopIndented;
1203
+ break;
1204
+ case MindmapLayoutType.rightTopIndented:
1205
+ inverseDirectionLayout = isHorizontal ? MindmapLayoutType.leftTopIndented : MindmapLayoutType.rightBottomIndented;
1206
+ break;
1207
+ case MindmapLayoutType.leftTopIndented:
1208
+ inverseDirectionLayout = isHorizontal ? MindmapLayoutType.rightTopIndented : MindmapLayoutType.leftBottomIndented;
1209
+ break;
1210
+ }
1211
+ return inverseDirectionLayout;
1212
+ };
1213
+ const getMindmapDirection = (root) => {
1214
+ const layout = root.layout || getDefaultMindmapLayout();
1215
+ return LayoutDirectionsMap[layout];
1216
+ };
1217
+ const getDefaultMindmapLayout = () => {
1218
+ return MindmapLayoutType.standard;
1219
+ };
1220
+ const getAvailableSubLayoutsByLayoutDirections = (directions) => {
1221
+ const result = [];
1222
+ const reverseDirections = directions.map(getLayoutReverseDirection);
1223
+ for (const key in MindmapLayoutType) {
1224
+ const layout = MindmapLayoutType[key];
1225
+ const layoutDirections = LayoutDirectionsMap[layout];
1226
+ if (layoutDirections) {
1227
+ const hasSameDirection = layoutDirections.some(d => directions.includes(d));
1228
+ const hasReverseDirection = layoutDirections.some(r => reverseDirections.includes(r));
1229
+ if (hasSameDirection && !hasReverseDirection) {
1230
+ result.push(layout);
1231
+ }
1232
+ }
1233
+ }
1234
+ return result;
1235
+ };
1236
+ const getLayoutReverseDirection = (layoutDirection) => {
1237
+ let reverseDirection = LayoutDirection.right;
1238
+ switch (layoutDirection) {
1239
+ case LayoutDirection.top:
1240
+ reverseDirection = LayoutDirection.bottom;
1241
+ break;
1242
+ case LayoutDirection.bottom:
1243
+ reverseDirection = LayoutDirection.top;
1244
+ break;
1245
+ case LayoutDirection.right:
1246
+ reverseDirection = LayoutDirection.left;
1247
+ break;
1248
+ case LayoutDirection.left:
1249
+ reverseDirection = LayoutDirection.right;
1250
+ break;
1251
+ }
1252
+ return reverseDirection;
1253
+ };
1254
+ const getRootLayout = (root) => {
1255
+ return root.layout || getDefaultMindmapLayout();
1256
+ };
1257
+
1258
+ function enterNodeEditing(element) {
1259
+ const component = ELEMENT_TO_COMPONENT.get(element);
1260
+ component.startEditText(false, false);
1261
+ }
1262
+
1263
+ function findParentElement(element) {
1264
+ const component = MINDMAP_ELEMENT_TO_COMPONENT.get(element);
1265
+ if (component && component.parent) {
1266
+ return component.parent.origin;
1267
+ }
1268
+ return undefined;
1269
+ }
1270
+ function findUpElement(element) {
1271
+ let branch;
1272
+ let root = element;
1273
+ let parent = findParentElement(element);
1274
+ while (parent) {
1275
+ branch = root;
1276
+ root = parent;
1277
+ parent = findParentElement(parent);
1278
+ }
1279
+ return { root, branch };
1280
+ }
1281
+ const getChildrenCount = (element) => {
1282
+ const count = element.children.reduce((p, c) => {
1283
+ return p + getChildrenCount(c);
1284
+ }, 0);
1285
+ return count + element.children.length;
1286
+ };
1287
+ const isChildElement = (origin, child) => {
1288
+ let parent = findParentElement(child);
1289
+ while (parent) {
1290
+ if (parent === origin) {
1291
+ return true;
1292
+ }
1293
+ parent = findParentElement(parent);
1294
+ }
1295
+ return false;
1296
+ };
1297
+ const filterChildElement = (elements) => {
1298
+ let result = [];
1299
+ elements.forEach(element => {
1300
+ const isChild = elements.some(node => {
1301
+ return isChildElement(node, element);
1302
+ });
1303
+ if (!isChild) {
1304
+ result.push(element);
1305
+ }
1306
+ });
1307
+ return result;
1308
+ };
1309
+ const isChildRight = (node, child) => {
1310
+ return node.x < child.x;
1311
+ };
1312
+ const isChildUp = (node, child) => {
1313
+ return node.y > child.y;
1314
+ };
1315
+ const copyNewNode = (node) => {
1316
+ const newNode = { ...node };
1317
+ newNode.id = idCreator();
1318
+ newNode.children = [];
1319
+ for (const childNode of node.children) {
1320
+ newNode.children.push(copyNewNode(childNode));
1321
+ }
1322
+ return newNode;
1323
+ };
1324
+ const transformRootToNode = (board, node) => {
1325
+ const newNode = { ...node };
1326
+ delete newNode.isRoot;
1327
+ delete newNode.rightNodeCount;
1328
+ delete newNode.type;
1329
+ const text = Node.string(node.data.topic.children[0]) || ' ';
1330
+ const { width, height } = getSizeByText(text, PlaitBoard.getViewportContainer(board), TOPIC_DEFAULT_MAX_WORD_COUNT);
1331
+ newNode.width = Math.max(width, NODE_MIN_WIDTH);
1332
+ newNode.height = height;
1333
+ if (newNode.layout === MindmapLayoutType.standard) {
1334
+ delete newNode.layout;
1335
+ }
1336
+ return newNode;
1337
+ };
1338
+ const transformNodeToRoot = (board, node) => {
1339
+ const newElement = { ...node };
1340
+ let text = Node.string(newElement.data.topic);
1341
+ if (!text) {
1342
+ text = '思维导图';
1343
+ newElement.data.topic = { children: [{ text }] };
1344
+ }
1345
+ delete newElement?.strokeColor;
1346
+ delete newElement?.fill;
1347
+ delete newElement?.shape;
1348
+ delete newElement?.strokeWidth;
1349
+ const { width, height } = getSizeByText(text, PlaitBoard.getViewportContainer(board), TOPIC_DEFAULT_MAX_WORD_COUNT, ROOT_TOPIC_FONT_SIZE);
1350
+ newElement.width = Math.max(width, NODE_MIN_WIDTH);
1351
+ newElement.height = height;
1352
+ return {
1353
+ ...newElement,
1354
+ layout: newElement.layout ?? MindmapLayoutType.right,
1355
+ isCollapsed: false,
1356
+ isRoot: true,
1357
+ type: 'mindmap'
1358
+ };
1359
+ };
1360
+ const extractNodesText = (node) => {
1361
+ let str = '';
1362
+ if (node) {
1363
+ str += Node.string(node.data.topic.children[0]) + ' ';
1364
+ for (const childNode of node.children) {
1365
+ str += extractNodesText(childNode);
1366
+ }
1367
+ }
1368
+ return str;
1369
+ };
1370
+ const changeRightNodeCount = (board, parentPath, changeNumber) => {
1371
+ const _rightNodeCount = board.children[parentPath[0]].rightNodeCount;
1372
+ Transforms.setNode(board, {
1373
+ rightNodeCount: changeNumber >= 0
1374
+ ? _rightNodeCount + changeNumber
1375
+ : _rightNodeCount + changeNumber < 0
1376
+ ? 0
1377
+ : _rightNodeCount + changeNumber
1378
+ }, parentPath);
1379
+ };
1380
+ const shouldChangeRightNodeCount = (selectedElement) => {
1381
+ const parentElement = findParentElement(selectedElement);
1382
+ const MindNodeComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(selectedElement);
1383
+ if (parentElement && MindNodeComponent) {
1384
+ const nodeIndex = MindNodeComponent.parent.children.findIndex(item => item.origin.id === selectedElement.id);
1385
+ if (parentElement.isRoot &&
1386
+ getRootLayout(parentElement) === MindmapLayoutType.standard &&
1387
+ parentElement.rightNodeCount &&
1388
+ nodeIndex <= parentElement.rightNodeCount - 1) {
1389
+ return true;
1390
+ }
1391
+ }
1392
+ return false;
1393
+ };
1394
+ const createMindmapData = (rightNodeCount, layout) => {
1395
+ const mindmapData = {
1396
+ type: 'mindmap',
1397
+ id: idCreator(),
1398
+ isRoot: true,
1399
+ rightNodeCount,
1400
+ layout,
1401
+ width: 72,
1402
+ height: ROOT_DEFAULT_HEIGHT,
1403
+ points: [[230, 208]],
1404
+ value: { children: [{ text: '思维导图' }] },
1405
+ shape: MindmapNodeShape.roundRectangle,
1406
+ children: [
1407
+ {
1408
+ id: idCreator(),
1409
+ value: { children: [{ text: '新建节点' }] },
1410
+ children: [],
1411
+ width: 56,
1412
+ height: TEXT_DEFAULT_HEIGHT,
1413
+ shape: MindmapNodeShape.roundRectangle
1414
+ },
1415
+ {
1416
+ id: idCreator(),
1417
+ value: { children: [{ text: '新建节点' }] },
1418
+ children: [],
1419
+ width: 56,
1420
+ height: TEXT_DEFAULT_HEIGHT,
1421
+ shape: MindmapNodeShape.roundRectangle
1422
+ },
1423
+ {
1424
+ id: idCreator(),
1425
+ value: { children: [{ text: '新建节点' }] },
1426
+ children: [],
1427
+ width: 56,
1428
+ height: TEXT_DEFAULT_HEIGHT,
1429
+ shape: MindmapNodeShape.roundRectangle
1430
+ }
1431
+ ]
1432
+ };
1433
+ return [mindmapData];
1434
+ };
1435
+ const createMindElement = (text, width, height, options) => {
1436
+ const newElement = {
1437
+ id: idCreator(),
1438
+ data: {
1439
+ topic: { children: [{ text }] }
1440
+ },
1441
+ children: [],
1442
+ width,
1443
+ height,
1444
+ fill: options.fill,
1445
+ strokeColor: options.strokeColor,
1446
+ strokeWidth: options.strokeWidth,
1447
+ shape: options.shape
1448
+ };
1449
+ if (options.fill) {
1450
+ newElement.fill = options.fill;
1451
+ }
1452
+ if (options.strokeColor) {
1453
+ newElement.strokeColor = options.strokeColor;
1454
+ }
1455
+ if (!isNullOrUndefined(options.strokeWidth)) {
1456
+ newElement.strokeWidth = options.strokeWidth;
1457
+ }
1458
+ if (options.shape) {
1459
+ newElement.shape = options.shape;
1460
+ }
1461
+ if (options.layout) {
1462
+ newElement.layout = options.layout;
1463
+ }
1464
+ if (options.linkLineColor) {
1465
+ newElement.linkLineColor = options.linkLineColor;
1466
+ }
1467
+ return newElement;
1468
+ };
1469
+ // layoutLevel 用来表示插入兄弟节点还是子节点
1470
+ const insertMindElement = (board, inheritNode, path) => {
1471
+ let fill, strokeColor, strokeWidth, shape = MindmapNodeShape.roundRectangle;
1472
+ if (!inheritNode.isRoot) {
1473
+ fill = inheritNode.fill;
1474
+ strokeColor = inheritNode.strokeColor;
1475
+ strokeWidth = inheritNode.strokeWidth;
1476
+ }
1477
+ shape = inheritNode.shape;
1478
+ const newElement = createMindElement('', NODE_MIN_WIDTH, TEXT_DEFAULT_HEIGHT, { fill, strokeColor, strokeWidth, shape });
1479
+ const index = path[path.length - 1];
1480
+ const abstractNode = inheritNode.children.find(child => AbstractNode.isAbstract(child) && index > child.start && index <= child.end + 1);
1481
+ if (abstractNode) {
1482
+ const path = PlaitBoard.findPath(board, abstractNode);
1483
+ Transforms.setNode(board, { end: abstractNode.end + 1 }, path);
1484
+ }
1485
+ Transforms.insertNode(board, newElement, path);
1486
+ addSelectedElement(board, newElement);
1487
+ setTimeout(() => {
1488
+ enterNodeEditing(newElement);
1489
+ });
1490
+ };
1491
+ const findLastChild = (child) => {
1492
+ let result = child;
1493
+ while (result.children.length !== 0) {
1494
+ result = result.children[result.children.length - 1];
1495
+ }
1496
+ return result;
1497
+ };
1498
+ const deleteSelectedELements = (board, selectedElements) => {
1499
+ //翻转,从下到上修改,防止找不到 path
1500
+ const deletableElements = filterChildElement(selectedElements).reverse();
1501
+ const relativeAbstracts = [];
1502
+ const accumulativeProperties = new WeakMap();
1503
+ deletableElements.forEach(node => {
1504
+ const MindNodeComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(node);
1505
+ if (MindNodeComponent && MindNodeComponent.parent) {
1506
+ const index = MindNodeComponent.parent.origin.children.indexOf(node);
1507
+ const abstracts = MindNodeComponent.parent.children.filter(value => AbstractNode.isAbstract(value.origin));
1508
+ abstracts.forEach(abstract => {
1509
+ const abstractNode = abstract.origin;
1510
+ if (index >= abstractNode.start && index <= abstractNode.end) {
1511
+ let newProperties = accumulativeProperties.get(abstractNode);
1512
+ if (!newProperties) {
1513
+ newProperties = { start: abstractNode.start, end: abstractNode.end };
1514
+ accumulativeProperties.set(abstractNode, newProperties);
1515
+ relativeAbstracts.push(abstractNode);
1516
+ }
1517
+ newProperties.end = newProperties.end - 1;
1518
+ }
1519
+ });
1520
+ }
1521
+ });
1522
+ const abstractHandles = relativeAbstracts.map(value => {
1523
+ const newProperties = accumulativeProperties.get(value);
1524
+ if (newProperties) {
1525
+ const path = PlaitBoard.findPath(board, value);
1526
+ return () => {
1527
+ if (newProperties.start > newProperties.end) {
1528
+ Transforms.removeNode(board, path);
1529
+ }
1530
+ else {
1531
+ Transforms.setNode(board, newProperties, path);
1532
+ }
1533
+ };
1534
+ }
1535
+ return () => { };
1536
+ });
1537
+ const deletableHandles = deletableElements.map(node => {
1538
+ const path = PlaitBoard.findPath(board, node);
1539
+ return () => {
1540
+ if (shouldChangeRightNodeCount(node)) {
1541
+ changeRightNodeCount(board, path.slice(0, path.length - 1), -1);
1542
+ }
1543
+ Transforms.removeNode(board, path);
1544
+ };
1545
+ });
1546
+ abstractHandles.forEach(action => action());
1547
+ deletableHandles.forEach(action => action());
1548
+ };
1549
+
1550
+ const getStrokeByMindmapElement = (element) => {
1551
+ let stroke = element.strokeColor;
1552
+ if (stroke) {
1553
+ return stroke;
1554
+ }
1555
+ const { root, branch } = findUpElement(element);
1556
+ if (branch) {
1557
+ const index = root.children.indexOf(branch);
1558
+ const length = COLORS.length;
1559
+ const remainder = index % length;
1560
+ return COLORS[remainder];
1561
+ }
1562
+ else {
1563
+ return ROOT_NODE_STROKE;
1564
+ }
1565
+ };
1566
+ const getLinkLineColorByMindmapElement = (element) => {
1567
+ let color = element.linkLineColor;
1568
+ if (color) {
1569
+ return color;
1570
+ }
1571
+ const { root, branch } = findUpElement(element);
1572
+ if (branch) {
1573
+ const index = root.children.indexOf(branch);
1574
+ const length = COLORS.length;
1575
+ const remainder = index % length;
1576
+ return COLORS[remainder];
1577
+ }
1578
+ else {
1579
+ throw new Error('root element should not have link line');
1580
+ }
1581
+ };
1582
+ const getRootLinkLineColorByMindmapElement = (root) => {
1583
+ const index = root.children.length;
1584
+ const length = COLORS.length;
1585
+ const remainder = index % length;
1586
+ return COLORS[remainder];
1587
+ };
1588
+
1589
+ function drawIndentedLink(roughSVG, node, child, defaultStroke = null, needDrawUnderline = true) {
1590
+ const isUnderlineShap = getNodeShapeByElement(child.origin) === MindmapNodeShape.underline;
1591
+ let beginX, beginY, endX, endY, beginNode = node, endNode = child;
1592
+ const beginRectangle = getRectangleByNode(beginNode);
1593
+ const endRectangle = getRectangleByNode(endNode);
1594
+ beginX = beginNode.x + beginNode.width / 2;
1595
+ beginY = isChildUp(node, child) ? beginRectangle.y : beginRectangle.y + beginRectangle.height;
1596
+ endX = node.left ? endNode.x + endNode.hGap + endRectangle.width : endNode.x + endNode.hGap;
1597
+ endY = isUnderlineShap ? endNode.y + endNode.height - endNode.vGap : endNode.y + endNode.height / 2;
1598
+ //根据位置,设置正负参数
1599
+ let plusMinus = isChildUp(node, child) ? (node.left ? [-1, -1] : [1, -1]) : node.left ? [-1, 1] : [1, 1];
1600
+ const layout = MindmapQueries.getCorrectLayoutByElement(node.origin);
1601
+ const strokeWidth = child.origin.linkLineWidth ? child.origin.linkLineWidth : STROKE_WIDTH;
1602
+ if (beginNode.origin.isRoot) {
1603
+ if (layout === MindmapLayoutType.leftBottomIndented || layout === MindmapLayoutType.rightBottomIndented) {
1604
+ beginY += strokeWidth;
1605
+ }
1606
+ if (layout === MindmapLayoutType.leftTopIndented || layout === MindmapLayoutType.rightTopIndented) {
1607
+ beginY -= strokeWidth;
1608
+ }
1609
+ }
1610
+ let curve = [
1611
+ [beginX, beginY],
1612
+ [beginX, beginY],
1613
+ [beginX, beginY],
1614
+ [beginX, endY - (endNode.hGap * 3 * plusMinus[1]) / 5],
1615
+ [beginX, endY - (endNode.hGap * plusMinus[1]) / 5],
1616
+ [beginX + (endNode.hGap * plusMinus[0]) / 4, endY],
1617
+ [beginX + (endNode.hGap * plusMinus[0] * 3) / 5, endY],
1618
+ isUnderlineShap && needDrawUnderline ? [endX + (endNode.width - endNode.hGap * 2) * plusMinus[0], endY] : [endX, endY],
1619
+ isUnderlineShap && needDrawUnderline ? [endX + (endNode.width - endNode.hGap * 2) * plusMinus[0], endY] : [endX, endY],
1620
+ isUnderlineShap && needDrawUnderline ? [endX + (endNode.width - endNode.hGap * 2) * plusMinus[0], endY] : [endX, endY]
1621
+ ];
1622
+ const stroke = defaultStroke || getLinkLineColorByMindmapElement(child.origin);
1623
+ const points = pointsOnBezierCurves(curve);
1624
+ return roughSVG.curve(points, { stroke, strokeWidth });
1625
+ }
1626
+
1627
+ var HorizontalPlacement;
1628
+ (function (HorizontalPlacement) {
1629
+ HorizontalPlacement["left"] = "left";
1630
+ HorizontalPlacement["center"] = "center";
1631
+ HorizontalPlacement["right"] = "right";
1632
+ })(HorizontalPlacement || (HorizontalPlacement = {}));
1633
+ var VerticalPlacement;
1634
+ (function (VerticalPlacement) {
1635
+ VerticalPlacement["top"] = "top";
1636
+ VerticalPlacement["middle"] = "middle";
1637
+ VerticalPlacement["bottom"] = "bottom";
1638
+ })(VerticalPlacement || (VerticalPlacement = {}));
1639
+
1640
+ const getPointByPlacement = (client, placement) => {
1641
+ let x = client.x;
1642
+ let y = client.y;
1643
+ if (placement[0] === HorizontalPlacement.center) {
1644
+ x = client.x + client.width / 2;
1645
+ }
1646
+ if (placement[0] === HorizontalPlacement.right) {
1647
+ x = client.x + client.width;
1648
+ }
1649
+ if (placement[1] === VerticalPlacement.middle) {
1650
+ y = client.y + client.height / 2;
1651
+ }
1652
+ if (placement[1] === VerticalPlacement.bottom) {
1653
+ y = client.y + client.height;
1654
+ }
1655
+ return [x, y];
1656
+ };
1657
+ const getLayoutDirection = (node, isHorizontal) => {
1658
+ if (isHorizontal) {
1659
+ if (node.left) {
1660
+ return LayoutDirection.left;
1661
+ }
1662
+ else {
1663
+ return LayoutDirection.right;
1664
+ }
1665
+ }
1666
+ else {
1667
+ if (node.up) {
1668
+ return LayoutDirection.top;
1669
+ }
1670
+ else {
1671
+ return LayoutDirection.bottom;
1672
+ }
1673
+ }
1674
+ };
1675
+ // 以右为基准
1676
+ // 右 -> 左:
1677
+ // 1. 终点 -> 起点/起点 -> 终点
1678
+ // 2. 加 -> 减
1679
+ // 水平 -> 垂直:
1680
+ // 1. 起点/终点 -> 纵轴
1681
+ // 2. 加减 -> 纵轴
1682
+ // 下 -> 上:
1683
+ // 1. 终点 -> 起点/终点 -> 起点
1684
+ // 2. 加 -> 减
1685
+ const movePoint = (point, distance, direction = LayoutDirection.right) => {
1686
+ if (direction === LayoutDirection.left) {
1687
+ return [point[0] - distance, point[1]];
1688
+ }
1689
+ if (direction === LayoutDirection.bottom) {
1690
+ return [point[0], point[1] + distance];
1691
+ }
1692
+ if (direction === LayoutDirection.top) {
1693
+ return [point[0], point[1] - distance];
1694
+ }
1695
+ return [point[0] + distance, point[1]];
1696
+ };
1697
+ const transformPlacement = (placement, direction) => {
1698
+ // to left
1699
+ if (direction === LayoutDirection.left) {
1700
+ if (placement[0] === HorizontalPlacement.right) {
1701
+ placement[0] = HorizontalPlacement.left;
1702
+ }
1703
+ else if (placement[0] === HorizontalPlacement.left) {
1704
+ placement[0] = HorizontalPlacement.right;
1705
+ }
1706
+ }
1707
+ // to bottom
1708
+ if (direction === LayoutDirection.bottom || direction === LayoutDirection.top) {
1709
+ let horizontal = HorizontalPlacement.center;
1710
+ let vertical = VerticalPlacement.middle;
1711
+ if (placement[1] === VerticalPlacement.top) {
1712
+ horizontal = HorizontalPlacement.left;
1713
+ }
1714
+ if (placement[1] === VerticalPlacement.bottom) {
1715
+ horizontal = HorizontalPlacement.right;
1716
+ }
1717
+ if (placement[0] === HorizontalPlacement.left) {
1718
+ vertical = VerticalPlacement.top;
1719
+ }
1720
+ if (placement[0] === HorizontalPlacement.right) {
1721
+ vertical = VerticalPlacement.bottom;
1722
+ }
1723
+ placement[0] = horizontal;
1724
+ placement[1] = vertical;
1725
+ }
1726
+ // to up
1727
+ if (direction === LayoutDirection.top) {
1728
+ if (placement[1] === VerticalPlacement.bottom) {
1729
+ placement[1] = VerticalPlacement.top;
1730
+ }
1731
+ else if (placement[1] === VerticalPlacement.top) {
1732
+ placement[1] = VerticalPlacement.bottom;
1733
+ }
1734
+ }
1735
+ };
1736
+
1737
+ function drawLogicLink(roughSVG, node, parent, isHorizontal) {
1738
+ const stroke = getLinkLineColorByMindmapElement(node.origin);
1739
+ const strokeWidth = node.origin.linkLineWidth ? node.origin.linkLineWidth : STROKE_WIDTH;
1740
+ const hasStraightLine = !parent.origin.isRoot;
1741
+ const hasUnderlineShape = node.origin.shape === MindmapNodeShape.underline;
1742
+ const hasUnderlineShapeOfParent = parent.origin.shape === MindmapNodeShape.underline;
1743
+ const nodeClient = getRectangleByNode(node);
1744
+ const parentClient = getRectangleByNode(parent);
1745
+ const linkDirection = getLayoutDirection(node, isHorizontal);
1746
+ // ① ensure begin placement and end placement
1747
+ // begin placement represent parent connector position and end placement represent child connector
1748
+ const beginPlacement = [HorizontalPlacement.right, VerticalPlacement.middle];
1749
+ const endPlacement = [HorizontalPlacement.left, VerticalPlacement.middle];
1750
+ transformPlacement(beginPlacement, linkDirection);
1751
+ transformPlacement(endPlacement, linkDirection);
1752
+ // underline shape and horizontal
1753
+ if (isHorizontal && hasUnderlineShapeOfParent && !parent.origin.isRoot) {
1754
+ beginPlacement[1] = VerticalPlacement.bottom;
1755
+ }
1756
+ if (isHorizontal && hasUnderlineShape) {
1757
+ endPlacement[1] = VerticalPlacement.bottom;
1758
+ }
1759
+ let beginPoint = getPointByPlacement(parentClient, beginPlacement);
1760
+ let endPoint = getPointByPlacement(nodeClient, endPlacement);
1761
+ // ② 确定凸出直线,从起始点开始画一条直线,从直线的结束位置绘制曲线,保证收起图标可以完美覆盖起始连线,根节点不需要这条直线
1762
+ // 绘制贝塞尔曲线要求,需要增加三个点,正常两个点就可以确定这条直线
1763
+ const straightLineDistance = 8;
1764
+ const beginPoint2 = hasStraightLine ? movePoint(beginPoint, straightLineDistance, linkDirection) : beginPoint;
1765
+ let straightLine = hasStraightLine ? [beginPoint, beginPoint2, beginPoint2] : [];
1766
+ // ③ 确定曲线
1767
+ const beginBufferDistance = (parent.hGap + node.hGap) / 3;
1768
+ const endBufferDistance = -(parent.hGap + node.hGap) / 2.4;
1769
+ let curve = [
1770
+ beginPoint2,
1771
+ movePoint(beginPoint2, beginBufferDistance, linkDirection),
1772
+ movePoint(endPoint, endBufferDistance, linkDirection),
1773
+ endPoint
1774
+ ];
1775
+ // ④ 下划线绘制,underline shape and horizontal
1776
+ const underlineEnd = movePoint(endPoint, nodeClient.width, linkDirection);
1777
+ const underline = hasUnderlineShape && isHorizontal ? [underlineEnd, underlineEnd, underlineEnd] : [];
1778
+ const points = pointsOnBezierCurves([...straightLine, ...curve, ...underline]);
1779
+ return roughSVG.curve(points, { stroke, strokeWidth });
1780
+ }
1781
+
1782
+ function getEmojisRectangle(element) {
1783
+ const count = element.data.emojis.length;
1784
+ const fontSize = getEmojiFontSize(element);
1785
+ return { width: fontSize * count, height: fontSize * 1.5 };
1786
+ }
1787
+ function getEmojiFontSize(element) {
1788
+ if (PlaitMind.isMind(element)) {
1789
+ return 18;
1790
+ }
1791
+ else {
1792
+ return 14;
1793
+ }
1794
+ }
1795
+
1796
+ const NodeDefaultSpace = {
1797
+ horizontal: {
1798
+ nodeAndText: BASE * 3,
1799
+ emojiAndText: BASE * 1.5
1800
+ },
1801
+ vertical: {
1802
+ nodeAndText: BASE * 1.5
1803
+ }
1804
+ };
1805
+ const RootDefaultSpace = {
1806
+ horizontal: {
1807
+ nodeAndText: BASE * 4,
1808
+ emojiAndText: BASE * 2
1809
+ },
1810
+ vertical: {
1811
+ nodeAndText: BASE * 2
1812
+ }
1813
+ };
1814
+ const getHorizontalSpaceBetweenNodeAndText = (element) => {
1815
+ const isMind = PlaitMind.isMind(element);
1816
+ const nodeAndText = isMind ? RootDefaultSpace.horizontal.nodeAndText : NodeDefaultSpace.horizontal.nodeAndText;
1817
+ return nodeAndText;
1818
+ };
1819
+ const getHorizontalSpaceEmojiAndText = (element) => {
1820
+ const isMind = PlaitMind.isMind(element);
1821
+ const emojiAndText = isMind ? RootDefaultSpace.horizontal.emojiAndText : NodeDefaultSpace.horizontal.emojiAndText;
1822
+ return emojiAndText;
1823
+ };
1824
+ const getVerticalSpaceBetweenNodeAndText = (element) => {
1825
+ const isMind = PlaitMind.isMind(element);
1826
+ const nodeAndText = isMind ? RootDefaultSpace.vertical.nodeAndText : NodeDefaultSpace.vertical.nodeAndText;
1827
+ return nodeAndText;
1828
+ };
1829
+ const NodeSpace = {
1830
+ getNodeWidth(element) {
1831
+ const nodeAndText = getHorizontalSpaceBetweenNodeAndText(element);
1832
+ if (MindElement.hasEmojis(element)) {
1833
+ return nodeAndText + getEmojisRectangle(element).width + getHorizontalSpaceEmojiAndText(element) + element.width + nodeAndText;
1834
+ }
1835
+ return nodeAndText + element.width + nodeAndText;
1836
+ },
1837
+ getNodeHeight(element) {
1838
+ const nodeAndText = getVerticalSpaceBetweenNodeAndText(element);
1839
+ return nodeAndText + element.height + nodeAndText;
1840
+ },
1841
+ getTextHorizontalSpace(element) {
1842
+ const nodeAndText = getHorizontalSpaceBetweenNodeAndText(element);
1843
+ if (MindElement.hasEmojis(element)) {
1844
+ return nodeAndText + getEmojisRectangle(element).width + getHorizontalSpaceEmojiAndText(element);
1845
+ }
1846
+ else {
1847
+ return nodeAndText;
1848
+ }
1849
+ },
1850
+ getTextVerticalSpace(element) {
1851
+ const nodeAndText = getVerticalSpaceBetweenNodeAndText(element);
1852
+ return nodeAndText;
1853
+ },
1854
+ getEmojiHorizontalSpace(element) {
1855
+ const nodeAndText = getHorizontalSpaceBetweenNodeAndText(element);
1856
+ return nodeAndText;
1857
+ },
1858
+ getEmojiVerticalSpace(element) {
1859
+ const nodeAndText = getVerticalSpaceBetweenNodeAndText(element);
1860
+ return nodeAndText;
1861
+ }
1862
+ };
1863
+
1864
+ function drawMindmapNodeRichtext(node, viewContainerRef) {
1865
+ const { x, y } = getRichtextRectangleByNode(node);
1866
+ const classList = [];
1867
+ if (node.origin.isRoot) {
1868
+ classList.push('root-node');
1869
+ classList.push('font-size-18');
1870
+ }
1871
+ else if (node.parent?.origin?.isRoot) {
1872
+ classList.push('root-child-node');
1873
+ }
1874
+ else {
1875
+ classList.push('child-node');
1876
+ }
1877
+ return drawRichtext(x, y, node.origin.width, node.origin.height, node.origin.data.topic, viewContainerRef, classList);
1878
+ }
1879
+ function updateMindNodeTopicSize(node, g, isEditable) {
1880
+ const { x, y, width, height } = getRichtextRectangleByNode(node);
1881
+ if (isEditable) {
1882
+ // add 999, avoid changing lines when paste more text
1883
+ updateForeignObject(g, width + 999, height + 999, x, y);
1884
+ }
1885
+ else {
1886
+ updateForeignObject(g, node.origin.width, node.origin.height, x, y);
1887
+ }
1888
+ }
1889
+ function getRichtextRectangleByNode(node) {
1890
+ let { x, y, width, height } = getRectangleByNode(node);
1891
+ x = x + NodeSpace.getTextHorizontalSpace(node.origin);
1892
+ y = y + NodeSpace.getTextVerticalSpace(node.origin);
1893
+ return { width, height, x, y };
1894
+ }
1895
+
1896
+ function drawRectangleNode(board, node) {
1897
+ const { x, y, width, height } = getRectangleByNode(node);
1898
+ const fill = node.origin.fill ? node.origin.fill : node.origin.isRoot ? ROOT_NODE_FILL : NODE_FILL;
1899
+ const stroke = getStrokeByMindmapElement(node.origin);
1900
+ const strokeWidth = node.origin.strokeWidth ? node.origin.strokeWidth : STROKE_WIDTH;
1901
+ const nodeG = drawRoundRectangle(PlaitBoard.getRoughSVG(board), x, y, x + width, y + height, {
1902
+ stroke,
1903
+ strokeWidth,
1904
+ fill,
1905
+ fillStyle: 'solid'
1906
+ });
1907
+ return nodeG;
1908
+ }
1909
+
1910
+ function drawAbstractLink(board, node, isHorizontal) {
1911
+ const linkPadding = 15;
1912
+ const parent = node.parent;
1913
+ const abstractRectangle = getRectangleByNode(node);
1914
+ let includedElements = parent.children.slice(node.origin.start, node.origin.end + 1).map(node => {
1915
+ return node.origin;
1916
+ });
1917
+ const includedElementsRectangle = getRectangleByElements(board, includedElements, true);
1918
+ const linkDirection = getLayoutDirection(node, isHorizontal);
1919
+ const bezierBeginPlacement = [HorizontalPlacement.right, VerticalPlacement.top];
1920
+ const bezierEndPlacement = [HorizontalPlacement.right, VerticalPlacement.bottom];
1921
+ const abstractConnectorPlacement = [HorizontalPlacement.left, VerticalPlacement.middle];
1922
+ transformPlacement(bezierBeginPlacement, linkDirection);
1923
+ transformPlacement(bezierEndPlacement, linkDirection);
1924
+ transformPlacement(abstractConnectorPlacement, linkDirection);
1925
+ let bezierBeginPoint = getPointByPlacement(includedElementsRectangle, bezierBeginPlacement);
1926
+ let bezierEndPoint = getPointByPlacement(includedElementsRectangle, bezierEndPlacement);
1927
+ let abstractConnectorPoint = getPointByPlacement(abstractRectangle, abstractConnectorPlacement);
1928
+ let curveDistance = 0;
1929
+ if (isHorizontal) {
1930
+ curveDistance = Math.abs(abstractConnectorPoint[0] - bezierBeginPoint[0]) - linkPadding * 2;
1931
+ }
1932
+ else {
1933
+ curveDistance = Math.abs(abstractConnectorPoint[1] - bezierBeginPoint[1]) - linkPadding * 2;
1934
+ }
1935
+ bezierBeginPoint = movePoint(bezierBeginPoint, linkPadding, linkDirection);
1936
+ let c1 = movePoint(bezierBeginPoint, curveDistance, linkDirection);
1937
+ bezierEndPoint = movePoint(bezierEndPoint, linkPadding, linkDirection);
1938
+ let c2 = movePoint(bezierEndPoint, curveDistance, linkDirection);
1939
+ let bezierConnectorPoint = movePoint(abstractConnectorPoint, -linkPadding, linkDirection);
1940
+ const link = PlaitBoard.getRoughSVG(board).path(`M${bezierBeginPoint[0]},${bezierBeginPoint[1]} Q${c1[0]},${c1[1]} ${bezierConnectorPoint[0]},${bezierConnectorPoint[1]} Q${c2[0]},${c2[1]} ${bezierEndPoint[0]},${bezierEndPoint[1]} M${abstractConnectorPoint[0]},${abstractConnectorPoint[1]} L${bezierConnectorPoint[0]},${bezierConnectorPoint[1]}`, {
1941
+ stroke: GRAY_COLOR,
1942
+ strokeWidth: 2
1943
+ });
1944
+ return link;
1945
+ }
1946
+
1947
+ class EmojiDrawer {
1948
+ constructor(board, viewContainerRef) {
1949
+ this.board = board;
1950
+ this.viewContainerRef = viewContainerRef;
1951
+ this.componentRef = null;
1952
+ }
1953
+ draw(emoji, element) {
1954
+ this.destroy();
1955
+ const componentType = this.board.drawEmoji(emoji, element);
1956
+ this.componentRef = this.viewContainerRef.createComponent(componentType);
1957
+ this.componentRef.instance.emojiItem = emoji;
1958
+ const fontSize = PlaitMind.isMind(element) ? 18 : 14;
1959
+ this.componentRef.instance.fontSize = fontSize;
1960
+ }
1961
+ get nativeElement() {
1962
+ if (this.componentRef) {
1963
+ return this.componentRef.instance.nativeElement;
1964
+ }
1965
+ else {
1966
+ return null;
1967
+ }
1968
+ }
1969
+ destroy() {
1970
+ if (this.componentRef) {
1971
+ this.componentRef.destroy();
1972
+ this.componentRef = null;
1973
+ }
1974
+ }
1975
+ }
1976
+ class EmojisDrawer {
1977
+ constructor(board, viewContainerRef) {
1978
+ this.board = board;
1979
+ this.viewContainerRef = viewContainerRef;
1980
+ this.emojiDrawers = [];
1981
+ }
1982
+ drawEmojis(element) {
1983
+ this.destroy();
1984
+ if (MindElement.hasEmojis(element)) {
1985
+ const node = MindElement.getNode(element);
1986
+ this.g = createG();
1987
+ this.g.classList.add('emojis');
1988
+ let { x, y } = getRectangleByNode(MindElement.getNode(element));
1989
+ x = x + NodeSpace.getEmojiHorizontalSpace(element);
1990
+ y = y + NodeSpace.getEmojiVerticalSpace(element);
1991
+ const { width, height } = getEmojisRectangle(element);
1992
+ const fontSize = getEmojiFontSize(element);
1993
+ const foreignObject = createForeignObject(x, y, width, height);
1994
+ this.g.append(foreignObject);
1995
+ const container = document.createElement('div');
1996
+ container.classList.add('node-emojis-container');
1997
+ container.classList.add(`emoji-font-size-${fontSize}`);
1998
+ foreignObject.append(container);
1999
+ this.emojiDrawers = element.data.emojis.map(emojiItem => {
2000
+ const drawer = new EmojiDrawer(this.board, this.viewContainerRef);
2001
+ drawer.draw(emojiItem, element);
2002
+ return drawer;
2003
+ });
2004
+ this.emojiDrawers.forEach(drawer => {
2005
+ container.append(drawer.nativeElement);
2006
+ });
2007
+ return this.g;
2008
+ }
2009
+ return undefined;
2010
+ }
2011
+ destroy() {
2012
+ if (this.g) {
2013
+ this.g.remove();
2014
+ }
2015
+ this.emojiDrawers.forEach(drawer => drawer.destroy());
2016
+ this.emojiDrawers = [];
2017
+ }
2018
+ }
2019
+
2020
+ const setLayout = (board, layout, path) => {
2021
+ correctLogicLayoutNode(board, layout, path);
2022
+ Transforms.setNode(board, { layout }, path);
2023
+ };
2024
+ const correctLogicLayoutNode = (board, layout, path) => {
2025
+ const node = PlaitNode.get(board, path);
2026
+ if (node && layout) {
2027
+ node.children?.forEach((value, index) => {
2028
+ if (value.layout) {
2029
+ if ((isHorizontalLogicLayout(layout) && isVerticalLogicLayout(value.layout)) ||
2030
+ (isVerticalLogicLayout(layout) && isHorizontalLogicLayout(value.layout))) {
2031
+ Transforms.setNode(board, { layout: null }, [...path, index]);
2032
+ }
2033
+ if (value.children?.length) {
2034
+ correctLogicLayoutNode(board, layout, [...path, index]);
2035
+ }
2036
+ }
2037
+ });
2038
+ }
2039
+ };
2040
+
2041
+ const setTopic = (board, element, topic, width, height) => {
2042
+ const newElement = {
2043
+ data: { topic },
2044
+ width: width < NODE_MIN_WIDTH * board.viewport.zoom ? NODE_MIN_WIDTH : width / board.viewport.zoom,
2045
+ height: height / board.viewport.zoom
2046
+ };
2047
+ if (MindElement.hasEmojis(element)) {
2048
+ newElement.data.emojis = element.data.emojis;
2049
+ }
2050
+ const path = PlaitBoard.findPath(board, element);
2051
+ Transforms.setNode(board, newElement, path);
2052
+ };
2053
+ const setTopicSize = (board, element, width, height) => {
2054
+ const newElement = {
2055
+ width: width < NODE_MIN_WIDTH * board.viewport.zoom ? NODE_MIN_WIDTH : width / board.viewport.zoom,
2056
+ height: height / board.viewport.zoom
2057
+ };
2058
+ const path = PlaitBoard.findPath(board, element);
2059
+ Transforms.setNode(board, newElement, path);
2060
+ };
2061
+ const addEmoji = (board, element, emojiItem) => {
2062
+ const emojis = element.data.emojis || [];
2063
+ emojis.push(emojiItem);
2064
+ const newElement = {
2065
+ data: { topic: element.data.topic, emojis }
2066
+ };
2067
+ const path = PlaitBoard.findPath(board, element);
2068
+ Transforms.setNode(board, newElement, path);
2069
+ };
2070
+ const removeEmoji = (board, element, emojiItem) => {
2071
+ const emojis = element.data.emojis.filter(value => value !== emojiItem);
2072
+ const newElement = {
2073
+ data: { topic: element.data.topic }
2074
+ };
2075
+ if (emojis.length > 0) {
2076
+ newElement.data.emojis = emojis;
2077
+ }
2078
+ const path = PlaitBoard.findPath(board, element);
2079
+ Transforms.setNode(board, newElement, path);
2080
+ };
2081
+
2082
+ const MindTransforms = {
2083
+ setLayout,
2084
+ setTopic,
2085
+ setTopicSize,
2086
+ addEmoji,
2087
+ removeEmoji
2088
+ };
2089
+
2090
+ function drawAbstractIncludedOutline(board, roughSVG, element, handlePosition = AbstractHandlePosition.start, resizingLocation) {
2091
+ const abstractIncludedG = createG();
2092
+ const parentElement = MindElement.getParent(element);
2093
+ const nodeLayout = MindmapQueries.getCorrectLayoutByElement(element);
2094
+ const isHorizontal = isHorizontalLayout(nodeLayout);
2095
+ const includedElements = parentElement.children.slice(element.start, element.end + 1);
2096
+ let abstractRectangle = getRectangleByElements(board, includedElements, true);
2097
+ abstractRectangle = RectangleClient.getOutlineRectangle(abstractRectangle, -ABSTRACT_INCLUDED_OUTLINE_OFFSET);
2098
+ if (resizingLocation) {
2099
+ abstractRectangle = getRectangleByResizingLocation(abstractRectangle, resizingLocation, handlePosition, isHorizontal);
2100
+ }
2101
+ const rectangle = drawAbstractRoundRectangle(roughSVG, abstractRectangle.x, abstractRectangle.y, abstractRectangle.x + abstractRectangle.width, abstractRectangle.y + abstractRectangle.height, isHorizontal, {
2102
+ stroke: PRIMARY_COLOR,
2103
+ strokeWidth: 1,
2104
+ fillStyle: 'solid'
2105
+ });
2106
+ const handleOptions = {
2107
+ stroke: ABSTRACT_HANDLE_COLOR,
2108
+ strokeWidth: 3,
2109
+ fillStyle: 'solid'
2110
+ };
2111
+ const startPlacement = [HorizontalPlacement.center, VerticalPlacement.top];
2112
+ const endPlacement = [HorizontalPlacement.center, VerticalPlacement.bottom];
2113
+ const linkDirection = getLayoutDirection(MindElement.getNode(element), isHorizontal);
2114
+ transformPlacement(startPlacement, linkDirection);
2115
+ transformPlacement(endPlacement, linkDirection);
2116
+ let startCenterPoint = getPointByPlacement(abstractRectangle, startPlacement);
2117
+ let endCenterPoint = getPointByPlacement(abstractRectangle, endPlacement);
2118
+ const startPoint1 = movePoint(startCenterPoint, -ABSTRACT_HANDLE_LENGTH / 2, linkDirection);
2119
+ const startPoint2 = movePoint(startCenterPoint, ABSTRACT_HANDLE_LENGTH / 2, linkDirection);
2120
+ const endPoint1 = movePoint(endCenterPoint, -ABSTRACT_HANDLE_LENGTH / 2, linkDirection);
2121
+ const endPoint2 = movePoint(endCenterPoint, ABSTRACT_HANDLE_LENGTH / 2, linkDirection);
2122
+ const startHandle = roughSVG.line(startPoint1[0], startPoint1[1], startPoint2[0], startPoint2[1], handleOptions);
2123
+ const endHandle = roughSVG.line(endPoint1[0], endPoint1[1], endPoint2[0], endPoint2[1], handleOptions);
2124
+ abstractIncludedG.append(startHandle);
2125
+ abstractIncludedG.append(endHandle);
2126
+ abstractIncludedG.append(rectangle);
2127
+ return abstractIncludedG;
2128
+ }
2129
+
2130
+ class MindNodeComponent extends PlaitPluginElementComponent {
2131
+ get handActive() {
2132
+ return this.board.pointer === PlaitPointerType.hand;
2133
+ }
2134
+ constructor(viewContainerRef, cdr, render2) {
2135
+ super(cdr);
2136
+ this.viewContainerRef = viewContainerRef;
2137
+ this.cdr = cdr;
2138
+ this.render2 = render2;
2139
+ this.isEditable = false;
2140
+ this.activeG = [];
2141
+ this.shapeG = null;
2142
+ this.destroy$ = new Subject();
2143
+ this.trackBy = (index, node) => {
2144
+ return node.origin.id;
2145
+ };
2146
+ }
2147
+ ngOnInit() {
2148
+ this.emojisDrawer = new EmojisDrawer(this.board, this.viewContainerRef);
2149
+ MINDMAP_ELEMENT_TO_COMPONENT.set(this.element, this);
2150
+ super.ngOnInit();
2151
+ this.node = ELEMENT_TO_NODE.get(this.element);
2152
+ if (!PlaitMind.isMind(this.element)) {
2153
+ this.parent = MindElement.getNode(MindElement.getParent(this.element));
2154
+ }
2155
+ this.index = NODE_TO_INDEX.get(this.element) || 0;
2156
+ this.roughSVG = PlaitBoard.getRoughSVG(this.board);
2157
+ this.parentG = PlaitElement.getComponent(MindElement.getRoot(this.board, this.element)).rootG;
2158
+ this.drawShape();
2159
+ this.drawLink();
2160
+ this.drawRichtext();
2161
+ this.drawEmojis();
2162
+ this.drawActiveG();
2163
+ this.updateActiveClass();
2164
+ this.drawMaskG();
2165
+ this.drawExtend();
2166
+ }
2167
+ onContextChanged(value, previous) {
2168
+ const newNode = ELEMENT_TO_NODE.get(this.element);
2169
+ if (!PlaitMind.isMind(this.element)) {
2170
+ this.parent = MindElement.getNode(MindElement.getParent(this.element));
2171
+ }
2172
+ MINDMAP_ELEMENT_TO_COMPONENT.set(this.element, this);
2173
+ // resolve move node richtext lose issue
2174
+ if (this.node !== newNode) {
2175
+ if (this.foreignObject && this.foreignObject.children.length <= 0) {
2176
+ this.foreignObject?.appendChild(this.richtextComponentRef?.instance.editable);
2177
+ }
2178
+ }
2179
+ const isEquals = MindmapNode.isEquals(this.node, newNode);
2180
+ this.node = newNode;
2181
+ this.drawActiveG();
2182
+ this.updateActiveClass();
2183
+ if (!isEquals) {
2184
+ this.drawShape();
2185
+ this.drawLink();
2186
+ this.updateRichtext();
2187
+ this.drawMaskG();
2188
+ this.drawExtend();
2189
+ this.drawEmojis();
2190
+ }
2191
+ }
2192
+ drawShape() {
2193
+ this.destroyShape();
2194
+ const shape = getNodeShapeByElement(this.node.origin);
2195
+ switch (shape) {
2196
+ case MindmapNodeShape.roundRectangle:
2197
+ this.shapeG = drawRectangleNode(this.board, this.node);
2198
+ this.g.prepend(this.shapeG);
2199
+ break;
2200
+ default:
2201
+ break;
2202
+ }
2203
+ }
2204
+ drawEmojis() {
2205
+ const g = this.emojisDrawer.drawEmojis(this.element);
2206
+ if (g) {
2207
+ this.g.append(g);
2208
+ }
2209
+ }
2210
+ destroyShape() {
2211
+ if (this.shapeG) {
2212
+ this.shapeG.remove();
2213
+ this.shapeG = null;
2214
+ }
2215
+ }
2216
+ drawLink() {
2217
+ if (!this.parent) {
2218
+ return;
2219
+ }
2220
+ if (this.linkG) {
2221
+ this.linkG.remove();
2222
+ }
2223
+ const layout = MindmapQueries.getLayoutByElement(this.parent.origin);
2224
+ if (AbstractNode.isAbstract(this.node.origin)) {
2225
+ this.linkG = drawAbstractLink(this.board, this.node, isHorizontalLayout(layout));
2226
+ }
2227
+ else if (MindElement.isIndentedLayout(this.parent.origin)) {
2228
+ this.linkG = drawIndentedLink(this.roughSVG, this.parent, this.node);
2229
+ }
2230
+ else {
2231
+ this.linkG = drawLogicLink(this.roughSVG, this.node, this.parent, isHorizontalLayout(layout));
2232
+ }
2233
+ this.g.append(this.linkG);
2234
+ }
2235
+ destroyLine() {
2236
+ if (this.parent) {
2237
+ if (this.linkG) {
2238
+ this.linkG.remove();
2239
+ }
2240
+ }
2241
+ }
2242
+ drawMaskG() {
2243
+ this.destroyMaskG();
2244
+ const lineWidthOffset = 2;
2245
+ const extendOffset = 15;
2246
+ const nodeLayout = MindmapQueries.getLayoutByElement(this.node.origin);
2247
+ const isTop = isTopLayout(nodeLayout);
2248
+ const isRight = isRightLayout(nodeLayout);
2249
+ const isBottom = isBottomLayout(nodeLayout);
2250
+ const isLeft = isLeftLayout(nodeLayout);
2251
+ const { x, y, width, height } = getRectangleByNode(this.node);
2252
+ let drawX = x;
2253
+ let drawY = y;
2254
+ let drawWidth = x + width;
2255
+ let drawHeight = y + height;
2256
+ switch (true) {
2257
+ case isTop:
2258
+ drawX = x - lineWidthOffset;
2259
+ drawY = y - extendOffset;
2260
+ drawWidth = x + width + lineWidthOffset;
2261
+ drawHeight = y + height + lineWidthOffset;
2262
+ break;
2263
+ case isBottom:
2264
+ drawX = x - lineWidthOffset;
2265
+ drawY = y - lineWidthOffset;
2266
+ drawWidth = x + width + lineWidthOffset;
2267
+ drawHeight = y + height + extendOffset;
2268
+ break;
2269
+ case isLeft:
2270
+ drawX = x - extendOffset;
2271
+ drawY = y - lineWidthOffset;
2272
+ drawWidth = x + width + lineWidthOffset;
2273
+ drawHeight = y + height + lineWidthOffset;
2274
+ break;
2275
+ case isRight:
2276
+ drawX = x - lineWidthOffset;
2277
+ drawY = y - lineWidthOffset;
2278
+ drawWidth = x + width + extendOffset;
2279
+ drawHeight = y + height + lineWidthOffset;
2280
+ break;
2281
+ }
2282
+ this.maskG = drawRoundRectangle(this.roughSVG, drawX, drawY, drawWidth, drawHeight, { stroke: 'none', fill: 'rgba(255,255,255,0)', fillStyle: 'solid' }, true);
2283
+ this.maskG.classList.add('mask');
2284
+ this.maskG.setAttribute('visibility', 'visible');
2285
+ this.g.append(this.maskG);
2286
+ if (this.isEditable) {
2287
+ this.disabledMaskG();
2288
+ }
2289
+ fromEvent(this.maskG, 'mouseenter')
2290
+ .pipe(takeUntil(this.destroy$), filter(() => {
2291
+ return PlaitBoard.isFocus(this.board) && !this.element.isCollapsed && !this.handActive;
2292
+ }))
2293
+ .subscribe(() => {
2294
+ this.g.classList.add('hovered');
2295
+ });
2296
+ fromEvent(this.maskG, 'mouseleave')
2297
+ .pipe(takeUntil(this.destroy$), filter(() => {
2298
+ return PlaitBoard.isFocus(this.board) && !this.element.isCollapsed;
2299
+ }))
2300
+ .subscribe(() => {
2301
+ this.g.classList.remove('hovered');
2302
+ });
2303
+ }
2304
+ destroyMaskG() {
2305
+ if (this.maskG) {
2306
+ this.maskG.remove();
2307
+ this.g.classList.remove('hovered');
2308
+ }
2309
+ }
2310
+ enableMaskG() {
2311
+ if (this.maskG) {
2312
+ this.maskG.setAttribute('visibility', 'visible');
2313
+ }
2314
+ }
2315
+ disabledMaskG() {
2316
+ if (this.maskG) {
2317
+ this.maskG.setAttribute('visibility', 'hidden');
2318
+ }
2319
+ }
2320
+ drawActiveG() {
2321
+ this.destroyActiveG();
2322
+ this.abstractIncludedOutlineG?.remove();
2323
+ if (this.selected) {
2324
+ if (AbstractNode.isAbstract(this.element)) {
2325
+ this.updateAbstractIncludedOutline();
2326
+ }
2327
+ let { x, y, width, height } = getRectangleByNode(this.node);
2328
+ const selectedStrokeG = drawRoundRectangle(this.roughSVG, x - 2, y - 2, x + width + 2, y + height + 2, { stroke: PRIMARY_COLOR, strokeWidth: 2, fill: '' }, true);
2329
+ // 影响 mask 移入移出事件
2330
+ selectedStrokeG.style.pointerEvents = 'none';
2331
+ this.g.appendChild(selectedStrokeG);
2332
+ this.activeG.push(selectedStrokeG);
2333
+ if (this.richtextComponentRef?.instance.plaitReadonly === true) {
2334
+ const selectedBackgroundG = drawRoundRectangle(this.roughSVG, x - 2, y - 2, x + width + 2, y + height + 2, { stroke: PRIMARY_COLOR, fill: PRIMARY_COLOR, fillStyle: 'solid' }, true);
2335
+ selectedBackgroundG.style.opacity = '0.15';
2336
+ // 影响双击事件
2337
+ selectedBackgroundG.style.pointerEvents = 'none';
2338
+ this.g.appendChild(selectedBackgroundG);
2339
+ this.activeG.push(selectedBackgroundG, selectedStrokeG);
2340
+ }
2341
+ }
2342
+ }
2343
+ destroyActiveG() {
2344
+ this.activeG.forEach(g => g.remove());
2345
+ this.activeG = [];
2346
+ }
2347
+ updateActiveClass() {
2348
+ if (!this.g) {
2349
+ return;
2350
+ }
2351
+ if (this.selected) {
2352
+ this.render2.addClass(this.g, 'active');
2353
+ }
2354
+ else {
2355
+ this.render2.removeClass(this.g, 'active');
2356
+ }
2357
+ }
2358
+ drawRichtext() {
2359
+ const { richtextG, richtextComponentRef, foreignObject } = drawMindmapNodeRichtext(this.node, this.viewContainerRef);
2360
+ this.richtextComponentRef = richtextComponentRef;
2361
+ this.richtextG = richtextG;
2362
+ this.foreignObject = foreignObject;
2363
+ this.render2.addClass(richtextG, 'richtext');
2364
+ this.g.append(richtextG);
2365
+ }
2366
+ drawQuickInsert(offset = 0) {
2367
+ if (this.board.options.readonly) {
2368
+ return;
2369
+ }
2370
+ const quickInsertG = createG();
2371
+ quickInsertG.classList.add('quick-insert');
2372
+ this.extendG?.append(quickInsertG);
2373
+ const { x, y, width, height } = getRectangleByNode(this.node);
2374
+ /**
2375
+ * 方位:
2376
+ * 1. 左、左上、左下
2377
+ * 2. 右、右上、右下
2378
+ * 3. 上、上左、上右
2379
+ * 4. 下、下左、下右
2380
+ */
2381
+ const shape = getNodeShapeByElement(this.node.origin);
2382
+ // 形状是矩形要偏移边框的线宽
2383
+ const strokeWidth = this.node.origin.linkLineWidth ? this.node.origin.linkLineWidth : STROKE_WIDTH;
2384
+ let offsetBorderLineWidth = 0;
2385
+ if (shape === MindmapNodeShape.roundRectangle && offset === 0) {
2386
+ offsetBorderLineWidth = strokeWidth;
2387
+ }
2388
+ let offsetRootBorderLineWidth = 0;
2389
+ if (this.node.origin.isRoot) {
2390
+ offsetRootBorderLineWidth = strokeWidth;
2391
+ }
2392
+ // 当没有子节点时,需要缩小的偏移量
2393
+ const extraOffset = 3;
2394
+ const underlineCoordinates = {
2395
+ // 画线方向:右向左 <--
2396
+ [MindmapLayoutType.left]: {
2397
+ // EXTEND_RADIUS * 0.5 是 左方向,折叠/收起的偏移量
2398
+ startX: x - (offset > 0 ? offset + EXTEND_RADIUS * 0.5 : 0) - offsetRootBorderLineWidth,
2399
+ startY: y + height,
2400
+ endX: x -
2401
+ offsetBorderLineWidth -
2402
+ offsetRootBorderLineWidth -
2403
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET - extraOffset : 0) -
2404
+ EXTEND_RADIUS,
2405
+ endY: y + height
2406
+ },
2407
+ // 画线方向:左向右 -->
2408
+ [MindmapLayoutType.right]: {
2409
+ startX: x + width + (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) + offsetRootBorderLineWidth,
2410
+ startY: y + height,
2411
+ endX: x +
2412
+ width +
2413
+ offsetBorderLineWidth +
2414
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET - extraOffset : 0) +
2415
+ EXTEND_RADIUS +
2416
+ offsetRootBorderLineWidth,
2417
+ endY: y + height
2418
+ },
2419
+ // 画线方向:下向上 -->
2420
+ [MindmapLayoutType.upward]: {
2421
+ startX: x + width * 0.5,
2422
+ startY: y - offsetBorderLineWidth - (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) - offsetRootBorderLineWidth,
2423
+ endX: x + width * 0.5,
2424
+ endY: y -
2425
+ offsetBorderLineWidth -
2426
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET - extraOffset : 0) -
2427
+ EXTEND_RADIUS -
2428
+ offsetRootBorderLineWidth
2429
+ },
2430
+ // 画线方向:上向下 -->
2431
+ [MindmapLayoutType.downward]: {
2432
+ startX: x + width * 0.5,
2433
+ startY: y + height + offsetBorderLineWidth + (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) + offsetRootBorderLineWidth,
2434
+ endX: x + width * 0.5,
2435
+ endY: y +
2436
+ height +
2437
+ offsetBorderLineWidth +
2438
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET - extraOffset : 0) +
2439
+ EXTEND_RADIUS +
2440
+ offsetRootBorderLineWidth
2441
+ },
2442
+ [MindmapLayoutType.leftBottomIndented]: {
2443
+ startX: x + width * 0.5,
2444
+ startY: y + height + offsetBorderLineWidth + (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) + offsetRootBorderLineWidth,
2445
+ endX: x + width * 0.5,
2446
+ endY: y +
2447
+ height +
2448
+ offsetBorderLineWidth +
2449
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET - extraOffset : 0) +
2450
+ EXTEND_RADIUS +
2451
+ offsetRootBorderLineWidth
2452
+ },
2453
+ [MindmapLayoutType.leftTopIndented]: {
2454
+ startX: x + width * 0.5,
2455
+ startY: y - offsetBorderLineWidth - (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) - offsetRootBorderLineWidth,
2456
+ endX: x + width * 0.5,
2457
+ endY: y -
2458
+ offsetBorderLineWidth -
2459
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) -
2460
+ EXTEND_RADIUS -
2461
+ offsetRootBorderLineWidth
2462
+ },
2463
+ [MindmapLayoutType.rightBottomIndented]: {
2464
+ startX: x + width * 0.5,
2465
+ startY: y + height + offsetBorderLineWidth + (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) + offsetRootBorderLineWidth,
2466
+ endX: x + width * 0.5,
2467
+ endY: y +
2468
+ height +
2469
+ offsetBorderLineWidth +
2470
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET - extraOffset : 0) +
2471
+ EXTEND_RADIUS +
2472
+ offsetRootBorderLineWidth
2473
+ },
2474
+ [MindmapLayoutType.rightTopIndented]: {
2475
+ startX: x + width * 0.5,
2476
+ startY: y - offsetBorderLineWidth - (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) - offsetRootBorderLineWidth,
2477
+ endX: x + width * 0.5,
2478
+ endY: y -
2479
+ offsetBorderLineWidth -
2480
+ (offset > 0 ? offset + QUICK_INSERT_CIRCLE_OFFSET : 0) -
2481
+ EXTEND_RADIUS -
2482
+ offsetRootBorderLineWidth
2483
+ }
2484
+ };
2485
+ if (shape === MindmapNodeShape.roundRectangle || this.node.origin.isRoot) {
2486
+ underlineCoordinates[MindmapLayoutType.left].startY -= height * 0.5;
2487
+ underlineCoordinates[MindmapLayoutType.left].endY -= height * 0.5;
2488
+ underlineCoordinates[MindmapLayoutType.right].startY -= height * 0.5;
2489
+ underlineCoordinates[MindmapLayoutType.right].endY -= height * 0.5;
2490
+ }
2491
+ const stroke = this.node.origin.isRoot
2492
+ ? getRootLinkLineColorByMindmapElement(this.element)
2493
+ : getLinkLineColorByMindmapElement(this.element);
2494
+ let nodeLayout = MindmapQueries.getCorrectLayoutByElement(this.node.origin);
2495
+ if (this.node.origin.isRoot && isStandardLayout(nodeLayout)) {
2496
+ const root = this.node.origin;
2497
+ nodeLayout = root.children.length >= root.rightNodeCount ? MindmapLayoutType.left : MindmapLayoutType.right;
2498
+ }
2499
+ const underlineCoordinate = underlineCoordinates[nodeLayout];
2500
+ if (underlineCoordinate) {
2501
+ const underline = this.roughSVG.line(underlineCoordinate.startX, underlineCoordinate.startY, underlineCoordinate.endX, underlineCoordinate.endY, { stroke, strokeWidth });
2502
+ const circleCoordinates = {
2503
+ startX: underlineCoordinate.endX,
2504
+ startY: underlineCoordinate.endY
2505
+ };
2506
+ const circle = this.roughSVG.circle(circleCoordinates.startX, circleCoordinates.startY, EXTEND_RADIUS, {
2507
+ fill: QUICK_INSERT_CIRCLE_COLOR,
2508
+ stroke: QUICK_INSERT_CIRCLE_COLOR,
2509
+ fillStyle: 'solid'
2510
+ });
2511
+ const innerCrossCoordinates = {
2512
+ horizontal: {
2513
+ startX: circleCoordinates.startX - EXTEND_RADIUS * 0.5 + 3,
2514
+ startY: circleCoordinates.startY,
2515
+ endX: circleCoordinates.startX + EXTEND_RADIUS * 0.5 - 3,
2516
+ endY: circleCoordinates.startY
2517
+ },
2518
+ vertical: {
2519
+ startX: circleCoordinates.startX,
2520
+ startY: circleCoordinates.startY - EXTEND_RADIUS * 0.5 + 3,
2521
+ endX: circleCoordinates.startX,
2522
+ endY: circleCoordinates.startY + EXTEND_RADIUS * 0.5 - 3
2523
+ }
2524
+ };
2525
+ const innerCrossHLine = this.roughSVG.line(innerCrossCoordinates.horizontal.startX, innerCrossCoordinates.horizontal.startY, innerCrossCoordinates.horizontal.endX, innerCrossCoordinates.horizontal.endY, {
2526
+ stroke: QUICK_INSERT_INNER_CROSS_COLOR,
2527
+ strokeWidth: 2
2528
+ });
2529
+ const innerRingVLine = this.roughSVG.line(innerCrossCoordinates.vertical.startX, innerCrossCoordinates.vertical.startY, innerCrossCoordinates.vertical.endX, innerCrossCoordinates.vertical.endY, {
2530
+ stroke: QUICK_INSERT_INNER_CROSS_COLOR,
2531
+ strokeWidth: 2
2532
+ });
2533
+ quickInsertG.appendChild(underline);
2534
+ quickInsertG.appendChild(circle);
2535
+ quickInsertG.appendChild(innerCrossHLine);
2536
+ quickInsertG.appendChild(innerRingVLine);
2537
+ }
2538
+ fromEvent(quickInsertG, 'mouseup')
2539
+ .pipe(take(1))
2540
+ .subscribe(() => {
2541
+ const path = PlaitBoard.findPath(this.board, this.element).concat(this.element.children.filter(child => !AbstractNode.isAbstract(child)).length);
2542
+ insertMindElement(this.board, this.node.origin, path);
2543
+ });
2544
+ }
2545
+ drawExtend() {
2546
+ // destroy
2547
+ this.destroyExtend();
2548
+ // create extend
2549
+ this.extendG = createG();
2550
+ const collapseG = createG();
2551
+ this.extendG.classList.add('extend');
2552
+ collapseG.classList.add('collapse-container');
2553
+ this.g.append(this.extendG);
2554
+ this.extendG.append(collapseG);
2555
+ if (this.node.origin.isRoot) {
2556
+ this.drawQuickInsert();
2557
+ return;
2558
+ }
2559
+ // interactive
2560
+ fromEvent(collapseG, 'mouseup')
2561
+ .pipe(filter(() => !this.handActive || this.board.options.readonly), take(1))
2562
+ .subscribe(() => {
2563
+ const isCollapsed = !this.node.origin.isCollapsed;
2564
+ const newElement = { isCollapsed };
2565
+ const path = PlaitBoard.findPath(this.board, this.element);
2566
+ Transforms.setNode(this.board, newElement, path);
2567
+ });
2568
+ const { x, y, width, height } = getRectangleByNode(this.node);
2569
+ const stroke = getLinkLineColorByMindmapElement(this.element);
2570
+ const strokeWidth = this.node.origin.linkLineWidth ? this.node.origin.linkLineWidth : STROKE_WIDTH;
2571
+ const extendY = y + height / 2;
2572
+ const nodeLayout = MindmapQueries.getCorrectLayoutByElement(this.element);
2573
+ let extendLineXY = [
2574
+ [x + width, extendY],
2575
+ [x + width + EXTEND_OFFSET, extendY]
2576
+ ];
2577
+ let arrowYOffset = [-4, 1, -0.6, 4];
2578
+ let arrowXOffset = [10, 5.5, 5.5, 10];
2579
+ let extendLineXOffset = [0, 0];
2580
+ let extendLineYOffset = [0, 0];
2581
+ let circleOffset = [EXTEND_RADIUS / 2, 0];
2582
+ if (isHorizontalLayout(nodeLayout) && !isIndentedLayout(nodeLayout)) {
2583
+ extendLineYOffset =
2584
+ getNodeShapeByElement(this.node.origin) === MindmapNodeShape.roundRectangle
2585
+ ? [0, 0]
2586
+ : [height / 2, height / 2];
2587
+ if (isLeftLayout(nodeLayout)) {
2588
+ //左
2589
+ extendLineXOffset = [-width, -width - EXTEND_OFFSET * 2];
2590
+ circleOffset = [-EXTEND_RADIUS / 2, 0];
2591
+ arrowXOffset = [-10, -5.5, -5.5, -10];
2592
+ }
2593
+ }
2594
+ else {
2595
+ arrowXOffset = [-4, 0.6, -1, 4];
2596
+ if (isTopLayout(nodeLayout)) {
2597
+ //上
2598
+ extendLineXOffset = [-width / 2, -width / 2 - EXTEND_OFFSET];
2599
+ extendLineYOffset = [-height / 2, -height / 2 - EXTEND_OFFSET];
2600
+ arrowYOffset = [-10, -5.5, -5.5, -10];
2601
+ circleOffset = [0, -EXTEND_RADIUS / 2];
2602
+ }
2603
+ else {
2604
+ //下
2605
+ extendLineXOffset = [-width / 2, -width / 2 - EXTEND_OFFSET];
2606
+ extendLineYOffset = [height / 2, height / 2 + EXTEND_OFFSET];
2607
+ arrowYOffset = [10, 5.5, 5.5, 10];
2608
+ circleOffset = [0, EXTEND_RADIUS / 2];
2609
+ }
2610
+ }
2611
+ extendLineXY = [
2612
+ [extendLineXY[0][0] + extendLineXOffset[0], extendLineXY[0][1] + extendLineYOffset[0]],
2613
+ [extendLineXY[1][0] + extendLineXOffset[1], extendLineXY[1][1] + extendLineYOffset[1]]
2614
+ ];
2615
+ const extendLine = this.roughSVG.line(extendLineXY[0][0], extendLineXY[0][1], extendLineXY[1][0], extendLineXY[1][1], {
2616
+ strokeWidth,
2617
+ stroke
2618
+ });
2619
+ //绘制箭头
2620
+ const hideArrowTopLine = this.roughSVG.line(extendLineXY[1][0] + arrowXOffset[0], extendLineXY[1][1] + arrowYOffset[0], extendLineXY[1][0] + arrowXOffset[1], extendLineXY[1][1] + arrowYOffset[1], {
2621
+ stroke,
2622
+ strokeWidth: 2
2623
+ });
2624
+ const hideArrowBottomLine = this.roughSVG.line(extendLineXY[1][0] + arrowXOffset[2], extendLineXY[1][1] + arrowYOffset[2], extendLineXY[1][0] + arrowXOffset[3], extendLineXY[1][1] + arrowYOffset[3], {
2625
+ stroke,
2626
+ strokeWidth: 2
2627
+ });
2628
+ if (this.node.origin.isCollapsed) {
2629
+ const badge = this.roughSVG.circle(extendLineXY[1][0] + circleOffset[0], extendLineXY[1][1] + circleOffset[1], EXTEND_RADIUS, {
2630
+ fill: stroke,
2631
+ stroke,
2632
+ fillStyle: 'solid'
2633
+ });
2634
+ let numberOffset = 0;
2635
+ if (getChildrenCount(this.node.origin) >= 10)
2636
+ numberOffset = -2;
2637
+ if (getChildrenCount(this.node.origin) === 1)
2638
+ numberOffset = 1;
2639
+ const badgeText = createText(extendLineXY[1][0] + circleOffset[0] - 4 + numberOffset, extendLineXY[1][1] + circleOffset[1] + 4, stroke, `${getChildrenCount(this.node.origin)}`);
2640
+ this.g.classList.add('collapsed');
2641
+ badge.setAttribute('style', 'opacity: 0.15');
2642
+ badgeText.setAttribute('style', 'font-size: 12px');
2643
+ collapseG.appendChild(badge);
2644
+ collapseG.appendChild(badgeText);
2645
+ collapseG.appendChild(extendLine);
2646
+ }
2647
+ else {
2648
+ this.g.classList.remove('collapsed');
2649
+ if (this.node.origin.children.length > 0) {
2650
+ const hideCircleG = this.roughSVG.circle(extendLineXY[1][0] + circleOffset[0], extendLineXY[1][1] + circleOffset[1], EXTEND_RADIUS - 1, {
2651
+ fill: '#fff',
2652
+ stroke,
2653
+ strokeWidth,
2654
+ fillStyle: 'solid'
2655
+ });
2656
+ collapseG.appendChild(hideCircleG);
2657
+ collapseG.appendChild(hideArrowTopLine);
2658
+ collapseG.appendChild(hideArrowBottomLine);
2659
+ this.drawQuickInsert(EXTEND_RADIUS);
2660
+ }
2661
+ else {
2662
+ this.drawQuickInsert();
2663
+ }
2664
+ }
2665
+ }
2666
+ destroyExtend() {
2667
+ if (this.extendG) {
2668
+ this.extendG.remove();
2669
+ }
2670
+ }
2671
+ destroyRichtext() {
2672
+ if (this.richtextG) {
2673
+ this.richtextG.remove();
2674
+ }
2675
+ if (this.richtextComponentRef) {
2676
+ this.richtextComponentRef.destroy();
2677
+ }
2678
+ }
2679
+ updateAbstractIncludedOutline(resizingLocation, handlePosition) {
2680
+ this.abstractIncludedOutlineG?.remove();
2681
+ this.abstractIncludedOutlineG = drawAbstractIncludedOutline(this.board, this.roughSVG, this.element, handlePosition, resizingLocation);
2682
+ PlaitBoard.getHost(this.board).append(this.abstractIncludedOutlineG);
2683
+ }
2684
+ updateRichtext() {
2685
+ updateRichText(this.node.origin.data.topic, this.richtextComponentRef);
2686
+ updateMindNodeTopicSize(this.node, this.richtextG, this.isEditable);
2687
+ }
2688
+ startEditText(isEnd, isClear) {
2689
+ if (!this.richtextComponentRef) {
2690
+ throw new Error('undefined richtextComponentRef');
2691
+ }
2692
+ const richtextInstance = this.richtextComponentRef.instance;
2693
+ this.isEditable = true;
2694
+ IS_TEXT_EDITABLE.set(this.board, true);
2695
+ this.disabledMaskG();
2696
+ updateMindNodeTopicSize(this.node, this.richtextG, this.isEditable);
2697
+ if (richtextInstance.plaitReadonly) {
2698
+ richtextInstance.plaitReadonly = false;
2699
+ this.richtextComponentRef.changeDetectorRef.detectChanges();
2700
+ this.drawActiveG();
2701
+ const location = isEnd ? Editor.end(richtextInstance.editor, [0]) : [0];
2702
+ setFullSelectionAndFocus(richtextInstance.editor, location);
2703
+ if (isClear) {
2704
+ Editor.deleteBackward(richtextInstance.editor);
2705
+ }
2706
+ // handle invalid width and height (old data)
2707
+ let { width, height } = getRichtextContentSize(richtextInstance.editable);
2708
+ if (width !== this.element.width || height !== this.element.height) {
2709
+ MindTransforms.setTopicSize(this.board, this.element, width, height);
2710
+ }
2711
+ }
2712
+ let richtext = richtextInstance.plaitValue;
2713
+ // use debounceTime to wait DOM render complete
2714
+ const valueChange$ = richtextInstance.plaitChange
2715
+ .pipe(debounceTime(0), filter(event => {
2716
+ // 过滤掉 operations 中全是 set_selection 的操作
2717
+ return !event.operations.every(op => Operation.isSelectionOperation(op));
2718
+ }))
2719
+ .subscribe(event => {
2720
+ if (richtext === event.value) {
2721
+ return;
2722
+ }
2723
+ this.updateRichtext();
2724
+ // 更新富文本、更新宽高
2725
+ let { width, height } = getRichtextContentSize(richtextInstance.editable);
2726
+ MindTransforms.setTopic(this.board, this.element, event.value, width, height);
2727
+ MERGING.set(this.board, true);
2728
+ });
2729
+ const composition$ = richtextInstance.plaitComposition.pipe(debounceTime(0)).subscribe(event => {
2730
+ let { width, height } = getRichtextContentSize(richtextInstance.editable);
2731
+ if (width < NODE_MIN_WIDTH) {
2732
+ width = NODE_MIN_WIDTH;
2733
+ }
2734
+ if (event.isComposing && (width !== this.node.origin.width || height !== this.node.origin.height)) {
2735
+ const newElement = {
2736
+ width: width / this.board.viewport.zoom,
2737
+ height: height / this.board.viewport.zoom
2738
+ };
2739
+ const path = PlaitBoard.findPath(this.board, this.element);
2740
+ Transforms.setNode(this.board, newElement, path);
2741
+ MERGING.set(this.board, true);
2742
+ }
2743
+ });
2744
+ const mousedown$ = fromEvent(document, 'mousedown').subscribe((event) => {
2745
+ const point = transformPoint(this.board, toPoint(event.x, event.y, PlaitBoard.getHost(this.board)));
2746
+ const clickInNode = hitMindmapElement(this.board, point, this.element);
2747
+ if (clickInNode && !hasEditableTarget(richtextInstance.editor, event.target)) {
2748
+ event.preventDefault();
2749
+ }
2750
+ else if (!clickInNode) {
2751
+ // handle composition input state, like: Chinese IME Composition Input
2752
+ timer(0).subscribe(() => {
2753
+ exitHandle();
2754
+ this.enableMaskG();
2755
+ });
2756
+ }
2757
+ });
2758
+ const editor = richtextInstance.editor;
2759
+ const { keydown } = editor;
2760
+ editor.keydown = (event) => {
2761
+ if (event.isComposing) {
2762
+ return;
2763
+ }
2764
+ if (event.key === 'Escape') {
2765
+ event.preventDefault();
2766
+ event.stopPropagation();
2767
+ exitHandle();
2768
+ this.drawActiveG();
2769
+ this.enableMaskG();
2770
+ return;
2771
+ }
2772
+ if (event.key === 'Enter' && !event.shiftKey) {
2773
+ event.preventDefault();
2774
+ event.stopPropagation();
2775
+ exitHandle();
2776
+ this.drawActiveG();
2777
+ this.enableMaskG();
2778
+ return;
2779
+ }
2780
+ if (event.key === 'Tab') {
2781
+ event.preventDefault();
2782
+ event.stopPropagation();
2783
+ exitHandle();
2784
+ this.drawActiveG();
2785
+ this.drawMaskG();
2786
+ }
2787
+ };
2788
+ const exitHandle = () => {
2789
+ // unsubscribe
2790
+ valueChange$.unsubscribe();
2791
+ composition$.unsubscribe();
2792
+ mousedown$.unsubscribe();
2793
+ editor.keydown = keydown; // reset keydown
2794
+ // editable status
2795
+ MERGING.set(this.board, false);
2796
+ richtextInstance.plaitReadonly = true;
2797
+ this.richtextComponentRef?.changeDetectorRef.markForCheck();
2798
+ this.isEditable = false;
2799
+ updateMindNodeTopicSize(this.node, this.richtextG, this.isEditable);
2800
+ IS_TEXT_EDITABLE.set(this.board, false);
2801
+ };
2802
+ }
2803
+ ngOnDestroy() {
2804
+ super.ngOnDestroy();
2805
+ this.abstractIncludedOutlineG?.remove();
2806
+ this.destroyRichtext();
2807
+ this.destroy$.next();
2808
+ this.destroy$.complete();
2809
+ if (ELEMENT_TO_NODE.get(this.element) === this.node) {
2810
+ ELEMENT_TO_NODE.delete(this.element);
2811
+ }
2812
+ if (MINDMAP_ELEMENT_TO_COMPONENT.get(this.element) === this) {
2813
+ MINDMAP_ELEMENT_TO_COMPONENT.delete(this.element);
2814
+ }
2815
+ }
2816
+ }
2817
+ MindNodeComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindNodeComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
2818
+ MindNodeComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.5", type: MindNodeComponent, selector: "plait-mindmap-node", usesInheritance: true, ngImport: i0, template: `
2819
+ <plait-children
2820
+ *ngIf="!element.isCollapsed"
2821
+ [board]="board"
2822
+ [parent]="element"
2823
+ [effect]="effect"
2824
+ [parentG]="parentG"
2825
+ ></plait-children>
2826
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.PlaitChildrenElement, selector: "plait-children", inputs: ["board", "parent", "effect", "parentG"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2827
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindNodeComponent, decorators: [{
2828
+ type: Component,
2829
+ args: [{
2830
+ selector: 'plait-mindmap-node',
2831
+ template: `
2832
+ <plait-children
2833
+ *ngIf="!element.isCollapsed"
2834
+ [board]="board"
2835
+ [parent]="element"
2836
+ [effect]="effect"
2837
+ [parentG]="parentG"
2838
+ ></plait-children>
2839
+ `,
2840
+ changeDetection: ChangeDetectionStrategy.OnPush
2841
+ }]
2842
+ }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }]; } });
2843
+
2844
+ const getLayoutOptions = () => {
2845
+ function getMainAxle(element, parent) {
2846
+ const strokeWidth = element.strokeWidth || STROKE_WIDTH;
2847
+ if (element.isRoot) {
2848
+ return BASE * 12;
2849
+ }
2850
+ if (parent && parent.isRoot()) {
2851
+ return BASE * 3 + strokeWidth / 2;
2852
+ }
2853
+ return BASE * 3 + strokeWidth / 2;
2854
+ }
2855
+ function getSecondAxle(element, parent) {
2856
+ const strokeWidth = element.strokeWidth || STROKE_WIDTH;
2857
+ if (element.isRoot) {
2858
+ return BASE * 10 + strokeWidth / 2;
2859
+ }
2860
+ return BASE * 6 + strokeWidth / 2;
2861
+ }
2862
+ return {
2863
+ getHeight(element) {
2864
+ return NodeSpace.getNodeHeight(element);
2865
+ },
2866
+ getWidth(element) {
2867
+ return NodeSpace.getNodeWidth(element);
2868
+ },
2869
+ getHorizontalGap(element, parent) {
2870
+ const _layout = (parent && parent.layout) || getRootLayout(element);
2871
+ const isHorizontal = isHorizontalLayout(_layout);
2872
+ const strokeWidth = element.strokeWidth || STROKE_WIDTH;
2873
+ if (isIndentedLayout(_layout)) {
2874
+ return BASE * 4 + strokeWidth;
2875
+ }
2876
+ if (!isHorizontal) {
2877
+ return getMainAxle(element, parent);
2878
+ }
2879
+ else {
2880
+ return getSecondAxle(element, parent);
2881
+ }
2882
+ },
2883
+ getVerticalGap(element, parent) {
2884
+ const _layout = (parent && parent.layout) || getRootLayout(element);
2885
+ if (isIndentedLayout(_layout)) {
2886
+ return BASE;
2887
+ }
2888
+ const isHorizontal = isHorizontalLayout(_layout);
2889
+ if (isHorizontal) {
2890
+ return getMainAxle(element, parent);
2891
+ }
2892
+ else {
2893
+ return getSecondAxle(element, parent);
2894
+ }
2895
+ },
2896
+ getVerticalConnectingPosition(element, parent) {
2897
+ if (element.shape === MindmapNodeShape.underline && parent && isHorizontalLogicLayout(parent.layout)) {
2898
+ return ConnectingPosition.bottom;
2899
+ }
2900
+ return undefined;
2901
+ },
2902
+ getExtendHeight(node) {
2903
+ return BASE * 6;
2904
+ },
2905
+ getIndentedCrossLevelGap() {
2906
+ return BASE * 2;
2907
+ }
2908
+ };
2909
+ };
2910
+
2911
+ class PlaitMindComponent extends MindNodeComponent {
2912
+ ngOnInit() {
2913
+ this.updateMindmap();
2914
+ super.ngOnInit();
2915
+ }
2916
+ beforeContextChange(value) {
2917
+ if (value.element !== this.element && this.initialized) {
2918
+ this.updateMindmap(value.element);
2919
+ }
2920
+ }
2921
+ updateMindmap(element = this.element) {
2922
+ const mindLayoutType = element.layout || getDefaultMindmapLayout();
2923
+ this.root = GlobalLayout.layout(element, getLayoutOptions(), mindLayoutType);
2924
+ this.updateMindmapLocation(element);
2925
+ }
2926
+ updateMindmapLocation(element) {
2927
+ const { x, y, hGap, vGap } = this.root;
2928
+ const offsetX = x + hGap;
2929
+ const offsetY = y + vGap;
2930
+ depthFirstRecursion(this.root, node => {
2931
+ node.x = node.x - offsetX + element.points[0][0];
2932
+ node.y = node.y - offsetY + element.points[0][1];
2933
+ ELEMENT_TO_NODE.set(node.origin, node);
2934
+ });
2935
+ }
2936
+ }
2937
+ PlaitMindComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitMindComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2938
+ PlaitMindComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.5", type: PlaitMindComponent, selector: "plait-mind", usesInheritance: true, ngImport: i0, template: `
2939
+ <plait-children [board]="board" [parent]="element" [effect]="effect" [parentG]="rootG"></plait-children>
2940
+ `, isInline: true, dependencies: [{ kind: "component", type: i2.PlaitChildrenElement, selector: "plait-children", inputs: ["board", "parent", "effect", "parentG"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2941
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitMindComponent, decorators: [{
2942
+ type: Component,
2943
+ args: [{
2944
+ selector: 'plait-mind',
2945
+ template: `
2946
+ <plait-children [board]="board" [parent]="element" [effect]="effect" [parentG]="rootG"></plait-children>
2947
+ `,
2948
+ changeDetection: ChangeDetectionStrategy.OnPush
2949
+ }]
2950
+ }] });
2951
+
2952
+ class MindModule {
2953
+ }
2954
+ MindModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2955
+ MindModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.5", ngImport: i0, type: MindModule, declarations: [PlaitMindComponent, MindNodeComponent], imports: [BrowserModule, RichtextModule, PlaitModule], exports: [PlaitMindComponent, MindNodeComponent] });
2956
+ MindModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindModule, imports: [BrowserModule, RichtextModule, PlaitModule] });
2957
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindModule, decorators: [{
2958
+ type: NgModule,
2959
+ args: [{
2960
+ declarations: [PlaitMindComponent, MindNodeComponent],
2961
+ imports: [BrowserModule, RichtextModule, PlaitModule],
2962
+ exports: [PlaitMindComponent, MindNodeComponent]
2963
+ }]
2964
+ }] });
2965
+
2966
+ const DRAG_MOVE_BUFFER = 5;
2967
+ const withDnd = (board) => {
2968
+ const { mousedown, mousemove, globalMouseup, keydown } = board;
2969
+ let activeElement;
2970
+ let startPoint;
2971
+ let fakeDragNodeG;
2972
+ let fakeDropNodeG;
2973
+ let dropTarget = null;
2974
+ board.mousedown = (event) => {
2975
+ if (board.options.readonly || IS_TEXT_EDITABLE.get(board) || event.button === 2) {
2976
+ mousedown(event);
2977
+ return;
2978
+ }
2979
+ // 确认是否 hit 节点
2980
+ const point = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
2981
+ const selectedElements = getSelectedElements(board);
2982
+ board.children.forEach((value) => {
2983
+ if (activeElement) {
2984
+ return;
2985
+ }
2986
+ if (PlaitMind.isMind(value)) {
2987
+ const mindmapComponent = ELEMENT_TO_COMPONENT.get(value);
2988
+ const root = mindmapComponent?.root;
2989
+ root.eachNode((node) => {
2990
+ if (activeElement) {
2991
+ return;
2992
+ }
2993
+ if (hitMindmapElement(board, point, node.origin) && !node.origin.isRoot && selectedElements.length <= 1) {
2994
+ activeElement = node.origin;
2995
+ startPoint = point;
2996
+ }
2997
+ });
2998
+ }
2999
+ });
3000
+ if (activeElement) {
3001
+ event.preventDefault();
3002
+ }
3003
+ mousedown(event);
3004
+ };
3005
+ board.mousemove = (event) => {
3006
+ if (!board.options.readonly && activeElement && startPoint) {
3007
+ const endPoint = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
3008
+ const distance = distanceBetweenPointAndPoint(startPoint[0], startPoint[1], endPoint[0], endPoint[1]);
3009
+ if (distance < DRAG_MOVE_BUFFER) {
3010
+ return;
3011
+ }
3012
+ if (!isDragging(board)) {
3013
+ setIsDragging(board, true);
3014
+ fakeDragNodeG = createG();
3015
+ fakeDragNodeG.classList.add('dragging', 'fake-node', 'plait-board-attached');
3016
+ fakeDropNodeG = createG();
3017
+ addActiveOnDragOrigin(activeElement);
3018
+ PlaitBoard.getHost(board).appendChild(fakeDropNodeG);
3019
+ PlaitBoard.getHost(board).appendChild(fakeDragNodeG);
3020
+ }
3021
+ else {
3022
+ fakeDragNodeG?.childNodes.forEach(node => {
3023
+ node.remove();
3024
+ });
3025
+ fakeDropNodeG?.childNodes.forEach(node => {
3026
+ node.remove();
3027
+ });
3028
+ }
3029
+ // fake dragging origin node
3030
+ const offsetX = endPoint[0] - startPoint[0];
3031
+ const offsetY = endPoint[1] - startPoint[1];
3032
+ const activeComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(activeElement);
3033
+ const roughSVG = PlaitBoard.getRoughSVG(board);
3034
+ const fakeDraggingNode = {
3035
+ ...activeComponent.node,
3036
+ children: [],
3037
+ x: activeComponent.node.x + offsetX,
3038
+ y: activeComponent.node.y + offsetY
3039
+ };
3040
+ const textRectangle = getRichtextRectangleByNode(activeComponent.node);
3041
+ const fakeNodeG = drawRectangleNode(board, fakeDraggingNode);
3042
+ const richtextG = activeComponent.richtextG?.cloneNode(true);
3043
+ updateForeignObject(richtextG, textRectangle.width + BASE * 10, textRectangle.height, textRectangle.x + offsetX, textRectangle.y + offsetY);
3044
+ fakeDragNodeG?.append(fakeNodeG);
3045
+ fakeDragNodeG?.append(richtextG);
3046
+ // drop position detect
3047
+ const { x, y } = getRectangleByNode(fakeDraggingNode);
3048
+ const detectCenterPoint = [x + textRectangle.width / 2, y + textRectangle.height / 2];
3049
+ let detectResult = null;
3050
+ board.children.forEach((value) => {
3051
+ if (detectResult) {
3052
+ return;
3053
+ }
3054
+ if (PlaitMind.isMind(value)) {
3055
+ const mindmapComponent = ELEMENT_TO_COMPONENT.get(value);
3056
+ const root = mindmapComponent?.root;
3057
+ root.eachNode((node) => {
3058
+ if (detectResult) {
3059
+ return;
3060
+ }
3061
+ const directions = directionDetector(node, detectCenterPoint);
3062
+ if (directions) {
3063
+ detectResult = directionCorrector(node, directions);
3064
+ }
3065
+ dropTarget = null;
3066
+ if (detectResult && isValidTarget(activeComponent.node.origin, node.origin)) {
3067
+ dropTarget = { target: node.origin, detectResult: detectResult[0] };
3068
+ }
3069
+ });
3070
+ }
3071
+ });
3072
+ if (dropTarget?.target) {
3073
+ dropTarget = readjustmentDropTarget(dropTarget);
3074
+ drawPlaceholderDropNodeG(dropTarget, roughSVG, fakeDropNodeG);
3075
+ }
3076
+ }
3077
+ mousemove(event);
3078
+ };
3079
+ board.globalMouseup = (event) => {
3080
+ if (!board.options.readonly && activeElement) {
3081
+ if (dropTarget?.target) {
3082
+ const activeComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(activeElement);
3083
+ const targetComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(dropTarget.target);
3084
+ let targetPath = PlaitBoard.findPath(board, targetComponent.element);
3085
+ const mindmapElement = findUpElement(dropTarget.target).root;
3086
+ const mindmapComponent = ELEMENT_TO_COMPONENT.get(mindmapElement);
3087
+ const layout = MindmapQueries.getCorrectLayoutByElement(mindmapComponent?.root.origin);
3088
+ targetPath = updatePathByLayoutAndDropTarget(targetPath, layout, dropTarget);
3089
+ const originPath = PlaitBoard.findPath(board, activeComponent.element);
3090
+ let newElement = { isCollapsed: false }, rightTargetPath = PlaitBoard.findPath(board, targetComponent.element);
3091
+ if (isStandardLayout(layout)) {
3092
+ updateRightNodeCount(board, activeComponent, targetComponent, dropTarget.detectResult);
3093
+ }
3094
+ if (dropTarget.detectResult === 'right') {
3095
+ if (targetComponent.node.origin.isRoot) {
3096
+ targetPath = PlaitBoard.findPath(board, targetComponent.element);
3097
+ targetPath.push(0);
3098
+ const rightNodeCount = targetComponent.node.origin.rightNodeCount + 1;
3099
+ newElement = { isCollapsed: false, rightNodeCount };
3100
+ }
3101
+ Transforms.setNode(board, newElement, rightTargetPath);
3102
+ }
3103
+ Transforms.moveNode(board, originPath, targetPath);
3104
+ }
3105
+ if (isDragging(board)) {
3106
+ removeActiveOnDragOrigin(activeElement);
3107
+ }
3108
+ setIsDragging(board, false);
3109
+ activeElement = null;
3110
+ fakeDragNodeG?.remove();
3111
+ fakeDragNodeG = undefined;
3112
+ fakeDropNodeG?.remove();
3113
+ fakeDropNodeG = undefined;
3114
+ dropTarget = null;
3115
+ }
3116
+ globalMouseup(event);
3117
+ };
3118
+ board.keydown = (event) => {
3119
+ keydown(event);
3120
+ };
3121
+ return board;
3122
+ };
3123
+ const isValidTarget = (origin, target) => {
3124
+ return origin !== target && !isChildElement(origin, target);
3125
+ };
3126
+ const addActiveOnDragOrigin = (activeElement, isOrigin = true) => {
3127
+ const activeComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(activeElement);
3128
+ if (isOrigin) {
3129
+ activeComponent.g.classList.add('dragging-origin');
3130
+ }
3131
+ else {
3132
+ activeComponent.g.classList.add('dragging-child');
3133
+ }
3134
+ !activeElement.isCollapsed &&
3135
+ activeElement.children.forEach(child => {
3136
+ addActiveOnDragOrigin(child, false);
3137
+ });
3138
+ };
3139
+ const removeActiveOnDragOrigin = (activeElement, isOrigin = true) => {
3140
+ const activeComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(activeElement);
3141
+ if (isOrigin) {
3142
+ activeComponent.g.classList.remove('dragging-origin');
3143
+ }
3144
+ else {
3145
+ activeComponent.g.classList.remove('dragging-child');
3146
+ }
3147
+ !activeElement.isCollapsed &&
3148
+ activeElement.children.forEach(child => {
3149
+ removeActiveOnDragOrigin(child, false);
3150
+ });
3151
+ };
3152
+ const updatePathByLayoutAndDropTarget = (targetPath, layout, dropTarget) => {
3153
+ // 上下布局:左右是兄弟节点,上下是子节点
3154
+ if (isVerticalLogicLayout(layout)) {
3155
+ if (isTopLayout(layout) && dropTarget.detectResult === 'top') {
3156
+ targetPath.push(dropTarget.target.children.length);
3157
+ }
3158
+ if (isBottomLayout(layout) && dropTarget.detectResult === 'bottom') {
3159
+ targetPath.push(dropTarget.target.children.length);
3160
+ }
3161
+ // 如果是左,位置不变,右则插入到下一个兄弟节点
3162
+ if (dropTarget.detectResult === 'right') {
3163
+ targetPath = Path.next(targetPath);
3164
+ }
3165
+ }
3166
+ // 水平布局/标准布局:上下是兄弟节点,左右是子节点
3167
+ if (isHorizontalLogicLayout(layout)) {
3168
+ if (dropTarget.detectResult === 'right') {
3169
+ targetPath.push(dropTarget.target.children.length);
3170
+ }
3171
+ if (dropTarget.detectResult === 'left') {
3172
+ targetPath.push(dropTarget.target.children.length);
3173
+ }
3174
+ // 如果是上,位置不变,下插入到下一个兄弟节点
3175
+ if (dropTarget.detectResult === 'bottom') {
3176
+ targetPath = Path.next(targetPath);
3177
+ }
3178
+ }
3179
+ // 缩进布局:上下是兄弟节点,左右是子节点,但上(左上/右上),探测到上是子节点,下则位置不变,反之同理。
3180
+ if (isIndentedLayout(layout)) {
3181
+ if (isTopLayout(layout) && dropTarget.detectResult === 'top') {
3182
+ targetPath = Path.next(targetPath);
3183
+ }
3184
+ if (isBottomLayout(layout) && dropTarget.detectResult === 'bottom') {
3185
+ targetPath = Path.next(targetPath);
3186
+ }
3187
+ if (isLeftLayout(layout) && dropTarget.detectResult === 'left') {
3188
+ targetPath.push(dropTarget.target.children.length);
3189
+ }
3190
+ if (isRightLayout(layout) && dropTarget.detectResult === 'right') {
3191
+ targetPath.push(dropTarget.target.children.length);
3192
+ }
3193
+ }
3194
+ return targetPath;
3195
+ };
3196
+ const updateRightNodeCount = (board, activeComponent, targetComponent, detectResult) => {
3197
+ let rightNodeCount;
3198
+ const mindmapElement = findUpElement(targetComponent.node.origin).root;
3199
+ const mindmapComponent = ELEMENT_TO_COMPONENT.get(mindmapElement);
3200
+ const activeIndex = mindmapComponent?.root.children.indexOf(activeComponent.node);
3201
+ const targetIndex = mindmapComponent?.root.children.indexOf(targetComponent.node);
3202
+ const isActiveOnRight = activeIndex !== -1 && activeIndex <= activeComponent.parent.origin.rightNodeCount - 1;
3203
+ const isTargetOnRight = targetComponent.parent && targetIndex !== -1 && targetIndex <= targetComponent.parent.origin.rightNodeCount - 1;
3204
+ const isBothOnRight = isActiveOnRight && isTargetOnRight;
3205
+ const rootChildCount = mindmapComponent.root.children?.length;
3206
+ const rootRightNodeCount = mindmapComponent?.root.origin.rightNodeCount;
3207
+ if (!isBothOnRight) {
3208
+ if (isActiveOnRight) {
3209
+ rightNodeCount = rootChildCount < rootRightNodeCount ? rootChildCount - 1 : rootRightNodeCount - 1;
3210
+ Transforms.setNode(board, { rightNodeCount }, PlaitBoard.findPath(board, activeComponent.parent.origin));
3211
+ }
3212
+ if (isTargetOnRight && detectResult !== 'right') {
3213
+ rightNodeCount = rootChildCount < rootRightNodeCount ? rootRightNodeCount : rootRightNodeCount + 1;
3214
+ Transforms.setNode(board, { rightNodeCount }, PlaitBoard.findPath(board, targetComponent.parent.origin));
3215
+ }
3216
+ //二级子节点拖动到根节点左侧
3217
+ if (targetComponent.node.origin.isRoot && detectResult === 'left' && activeIndex === -1) {
3218
+ rightNodeCount = rootChildCount;
3219
+ Transforms.setNode(board, { rightNodeCount }, PlaitBoard.findPath(board, targetComponent.element));
3220
+ }
3221
+ }
3222
+ };
3223
+ const IS_DRAGGING = new WeakMap();
3224
+ const isDragging = (board) => {
3225
+ return !!IS_DRAGGING.get(board);
3226
+ };
3227
+ const setIsDragging = (board, state) => {
3228
+ IS_DRAGGING.set(board, state);
3229
+ };
3230
+
3231
+ const buildClipboardData = (board, selectedElements) => {
3232
+ let result = [];
3233
+ const selectedMindmapNodes = Array.from(selectedElements, node => {
3234
+ return MINDMAP_ELEMENT_TO_COMPONENT.get(node)?.node;
3235
+ });
3236
+ const nodesRectangle = getRectangleByElements(board, selectedElements, true);
3237
+ selectedElements.forEach((node, index) => {
3238
+ const nodeRectangle = getRectangleByNode(selectedMindmapNodes[index]);
3239
+ result.push({
3240
+ ...node,
3241
+ points: [[nodeRectangle.x - nodesRectangle.x, nodeRectangle.y - nodesRectangle.y]]
3242
+ });
3243
+ });
3244
+ return result;
3245
+ };
3246
+ const setClipboardData = (data, elements) => {
3247
+ const stringObj = JSON.stringify(elements);
3248
+ const encoded = window.btoa(encodeURIComponent(stringObj));
3249
+ const text = elements.reduce((string, currentNode) => {
3250
+ return string + extractNodesText(currentNode);
3251
+ }, '');
3252
+ data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
3253
+ data?.setData(`text/plain`, text);
3254
+ };
3255
+ const getDataFromClipboard = (data) => {
3256
+ const encoded = data?.getData(`application/${CLIP_BOARD_FORMAT_KEY}`);
3257
+ let nodesData = [];
3258
+ if (encoded) {
3259
+ const decoded = decodeURIComponent(window.atob(encoded));
3260
+ nodesData = JSON.parse(decoded);
3261
+ }
3262
+ return nodesData;
3263
+ };
3264
+ const insertClipboardData = (board, elements, targetPoint) => {
3265
+ let newElement, path;
3266
+ const selectedElements = getSelectedElements(board);
3267
+ let newELements = [];
3268
+ elements.forEach((item, index) => {
3269
+ newElement = copyNewNode(item);
3270
+ if (selectedElements.length === 1) {
3271
+ if (item.isRoot) {
3272
+ newElement = transformRootToNode(board, newElement);
3273
+ }
3274
+ const selectedElementPath = PlaitBoard.findPath(board, selectedElements[0]);
3275
+ path = selectedElementPath.concat((selectedElements[0].children || []).length + index);
3276
+ }
3277
+ else {
3278
+ const point = [targetPoint[0] + item.points[0][0], targetPoint[1] + item.points[0][1]];
3279
+ newElement.points = [point];
3280
+ if (!item.isRoot) {
3281
+ newElement = transformNodeToRoot(board, newElement);
3282
+ }
3283
+ path = [board.children.length];
3284
+ }
3285
+ newELements.push(newElement);
3286
+ Transforms.insertNode(board, newElement, path);
3287
+ return;
3288
+ });
3289
+ Transforms.setSelectionWithTemporaryElements(board, newELements);
3290
+ };
3291
+ const insertClipboardText = (board, parentElement, text, width, height) => {
3292
+ const newElement = createMindElement(text, width, height, {});
3293
+ const path = PlaitBoard.findPath(board, parentElement).concat((parentElement.children || []).length);
3294
+ Transforms.insertNode(board, newElement, path);
3295
+ return;
3296
+ };
3297
+
3298
+ function findNewChildNodePath(board, element) {
3299
+ const path = PlaitBoard.findPath(board, element);
3300
+ return path.concat((element.children || []).length);
3301
+ }
3302
+ function findNewSiblingNodePath(board, element) {
3303
+ const path = PlaitBoard.findPath(board, element);
3304
+ return Path$1.next(path);
3305
+ }
3306
+
3307
+ const withEmoji = (board) => {
3308
+ const newBoard = board;
3309
+ newBoard.drawEmoji = (emoji, element) => {
3310
+ throw new Error('Not implement drawEmoji method error.');
3311
+ };
3312
+ return newBoard;
3313
+ };
3314
+
3315
+ const withAbstract = (board) => {
3316
+ const { mousedown, mousemove, mouseup } = board;
3317
+ let activeAbstractElement;
3318
+ let abstractHandlePosition;
3319
+ let startPoint;
3320
+ let newProperty;
3321
+ board.mousedown = (event) => {
3322
+ const activeAbstractElements = getSelectedElements(board).filter(element => AbstractNode.isAbstract(element));
3323
+ const host = BOARD_TO_HOST.get(board);
3324
+ const point = transformPoint(board, toPoint(event.x, event.y, host));
3325
+ activeAbstractElement = activeAbstractElements.find(element => {
3326
+ abstractHandlePosition = getHitAbstractHandle(board, element, point);
3327
+ return abstractHandlePosition;
3328
+ });
3329
+ if (activeAbstractElement) {
3330
+ startPoint = point;
3331
+ return;
3332
+ }
3333
+ mousedown(event);
3334
+ };
3335
+ board.mousemove = (event) => {
3336
+ getSelectedElements(board);
3337
+ const host = BOARD_TO_HOST.get(board);
3338
+ const endPoint = transformPoint(board, toPoint(event.x, event.y, host));
3339
+ if (abstractHandlePosition && activeAbstractElement) {
3340
+ const abstractComponent = PlaitElement.getComponent(activeAbstractElement);
3341
+ const element = abstractComponent.element;
3342
+ const nodeLayout = MindmapQueries.getCorrectLayoutByElement(activeAbstractElement);
3343
+ const isHorizontal = isHorizontalLayout(nodeLayout);
3344
+ const parentElement = MindElement.getParent(element);
3345
+ let children = parentElement.children;
3346
+ const parentLayout = MindmapQueries.getLayoutByElement(parentElement);
3347
+ if (isStandardLayout(parentLayout)) {
3348
+ const rightNodeCount = parentElement.rightNodeCount;
3349
+ const { leftChildren, rightChildren } = separateChildren(parentElement);
3350
+ if (activeAbstractElement.end < rightNodeCount) {
3351
+ children = rightChildren;
3352
+ }
3353
+ if (activeAbstractElement.start >= rightNodeCount) {
3354
+ children = leftChildren;
3355
+ }
3356
+ }
3357
+ const resizingLocation = isHorizontal ? endPoint[1] : endPoint[0];
3358
+ const parent = MindElement.getNode(parentElement);
3359
+ const scope = getLocationScope(board, abstractHandlePosition, children, element, parent, isHorizontal);
3360
+ const location = Math.min(scope.max, Math.max(scope.min, resizingLocation));
3361
+ let locationIndex = findLocationLeftIndex(board, children, location, isHorizontal);
3362
+ const isPropertyUnchanged = (abstractHandlePosition === AbstractHandlePosition.start &&
3363
+ locationIndex + 1 === activeAbstractElement.start) ||
3364
+ (abstractHandlePosition === AbstractHandlePosition.end && locationIndex === activeAbstractElement.end);
3365
+ if (isPropertyUnchanged) {
3366
+ newProperty = undefined;
3367
+ }
3368
+ else {
3369
+ if (isStandardLayout(parent.layout)) {
3370
+ const rightNodeCount = parent.origin.rightNodeCount;
3371
+ let start = element.start;
3372
+ if (start >= rightNodeCount) {
3373
+ locationIndex += rightNodeCount;
3374
+ }
3375
+ }
3376
+ newProperty =
3377
+ abstractHandlePosition === AbstractHandlePosition.start ? { start: locationIndex + 1 } : { end: locationIndex };
3378
+ }
3379
+ abstractComponent.updateAbstractIncludedOutline(location, abstractHandlePosition);
3380
+ }
3381
+ mousemove(event);
3382
+ };
3383
+ board.mouseup = (event) => {
3384
+ startPoint = undefined;
3385
+ abstractHandlePosition = null;
3386
+ if (activeAbstractElement) {
3387
+ if (newProperty) {
3388
+ const path = PlaitBoard.findPath(board, activeAbstractElement);
3389
+ Transforms.setNode(board, newProperty, path);
3390
+ }
3391
+ else {
3392
+ const abstractComponent = PlaitElement.getComponent(activeAbstractElement);
3393
+ abstractComponent.updateAbstractIncludedOutline();
3394
+ }
3395
+ }
3396
+ mouseup(event);
3397
+ };
3398
+ return board;
3399
+ };
3400
+
3401
+ const withMind = (board) => {
3402
+ const { drawElement, dblclick, keydown, insertFragment, setFragment, deleteFragment, isHitSelection, getRectangle, isMovable, isRecursion } = board;
3403
+ board.drawElement = (context) => {
3404
+ if (PlaitMind.isMind(context.element)) {
3405
+ return PlaitMindComponent;
3406
+ }
3407
+ else if (MindElement.isMindElement(board, context.element)) {
3408
+ return MindNodeComponent;
3409
+ }
3410
+ return drawElement(context);
3411
+ };
3412
+ board.getRectangle = element => {
3413
+ if (MindElement.isMindElement(board, element)) {
3414
+ return getRectangleByNode(MindElement.getNode(element));
3415
+ }
3416
+ return getRectangle(element);
3417
+ };
3418
+ board.isRecursion = element => {
3419
+ if (MindElement.isMindElement(board, element) && element.isCollapsed) {
3420
+ return false;
3421
+ }
3422
+ return isRecursion(element);
3423
+ };
3424
+ board.isHitSelection = (element, range) => {
3425
+ if (MindElement.isMindElement(board, element) && board.selection) {
3426
+ const client = getRectangleByNode(MindElement.getNode(element));
3427
+ return RectangleClient.isIntersect(RectangleClient.toRectangleClient([range.anchor, range.focus]), client);
3428
+ }
3429
+ return isHitSelection(element, range);
3430
+ };
3431
+ board.isMovable = element => {
3432
+ if (PlaitMind.isMind(element) && element.isRoot) {
3433
+ return true;
3434
+ }
3435
+ return isMovable(element);
3436
+ };
3437
+ board.keydown = (event) => {
3438
+ if (board.options.readonly || IS_TEXT_EDITABLE.get(board)) {
3439
+ keydown(event);
3440
+ return;
3441
+ }
3442
+ const selectedElements = getSelectedElements(board);
3443
+ if (selectedElements.length) {
3444
+ if (selectedElements.length === 1 &&
3445
+ (event.key === 'Tab' ||
3446
+ (event.key === 'Enter' && !selectedElements[0].isRoot && !AbstractNode.isAbstract(selectedElements[0])))) {
3447
+ event.preventDefault();
3448
+ const selectedElement = selectedElements[0];
3449
+ removeSelectedElement(board, selectedElement);
3450
+ const selectedElementPath = PlaitBoard.findPath(board, selectedElement);
3451
+ if (event.key === 'Tab') {
3452
+ if (selectedElement.isCollapsed) {
3453
+ const newElement = { isCollapsed: false };
3454
+ PlaitHistoryBoard.withoutSaving(board, () => {
3455
+ Transforms.setNode(board, newElement, selectedElementPath);
3456
+ });
3457
+ }
3458
+ insertMindElement(board, selectedElement, findNewChildNodePath(board, selectedElement));
3459
+ }
3460
+ else {
3461
+ if (shouldChangeRightNodeCount(selectedElement)) {
3462
+ changeRightNodeCount(board, selectedElementPath.slice(0, 1), 1);
3463
+ }
3464
+ insertMindElement(board, selectedElement, findNewSiblingNodePath(board, selectedElement));
3465
+ }
3466
+ return;
3467
+ }
3468
+ if (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event)) {
3469
+ event.preventDefault();
3470
+ deleteSelectedELements(board, selectedElements);
3471
+ let lastNode = null;
3472
+ const elementGroup = filterChildElement(selectedElements);
3473
+ const selectNode = elementGroup[0];
3474
+ const MindNodeComponent = MINDMAP_ELEMENT_TO_COMPONENT.get(selectNode);
3475
+ const nodeIndex = MindNodeComponent?.parent?.children.findIndex(item => item.origin.id === selectNode.id);
3476
+ const isSameParent = elementGroup.every(element => {
3477
+ return findParentElement(element) && findParentElement(elementGroup[0]) === findParentElement(element);
3478
+ });
3479
+ if (isSameParent) {
3480
+ const childCount = MindNodeComponent.parent?.children.length - elementGroup.length;
3481
+ if (childCount === 0) {
3482
+ lastNode = MindNodeComponent?.parent;
3483
+ }
3484
+ else if (nodeIndex === 0) {
3485
+ lastNode = MindNodeComponent?.parent.children[elementGroup.length];
3486
+ }
3487
+ else if (nodeIndex > 0) {
3488
+ lastNode = MindNodeComponent?.parent.children[nodeIndex - 1];
3489
+ }
3490
+ }
3491
+ if (lastNode) {
3492
+ addSelectedElement(board, lastNode.origin);
3493
+ }
3494
+ return;
3495
+ }
3496
+ // auto enter edit status
3497
+ if (!isVirtualKey(event)) {
3498
+ event.preventDefault();
3499
+ const selectedElement = selectedElements[0];
3500
+ enterNodeEditing(selectedElement);
3501
+ return;
3502
+ }
3503
+ }
3504
+ if (board.selection && event.code === 'Space') {
3505
+ if (selectedElements?.length) {
3506
+ return;
3507
+ }
3508
+ }
3509
+ keydown(event);
3510
+ };
3511
+ board.dblclick = (event) => {
3512
+ if (board.options.readonly || IS_TEXT_EDITABLE.get(board)) {
3513
+ dblclick(event);
3514
+ return;
3515
+ }
3516
+ const point = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
3517
+ board.children
3518
+ .filter(value => PlaitMind.isMind(value))
3519
+ .forEach(mindmap => {
3520
+ depthFirstRecursion(mindmap, node => {
3521
+ if (!PlaitBoard.hasBeenTextEditing(board) && hitMindmapElement(board, point, node)) {
3522
+ enterNodeEditing(node);
3523
+ }
3524
+ });
3525
+ });
3526
+ if (PlaitBoard.hasBeenTextEditing(board)) {
3527
+ return;
3528
+ }
3529
+ dblclick(event);
3530
+ };
3531
+ board.setFragment = (data) => {
3532
+ const selectedElements = filterChildElement(getSelectedElements(board));
3533
+ if (selectedElements.length) {
3534
+ const elements = buildClipboardData(board, selectedElements);
3535
+ setClipboardData(data, elements);
3536
+ return;
3537
+ }
3538
+ setFragment(data);
3539
+ };
3540
+ board.insertFragment = (data, targetPoint) => {
3541
+ if (board.options.readonly) {
3542
+ insertFragment(data, targetPoint);
3543
+ return;
3544
+ }
3545
+ const elements = getDataFromClipboard(data);
3546
+ if (elements.length) {
3547
+ insertClipboardData(board, elements, targetPoint || [0, 0]);
3548
+ }
3549
+ else {
3550
+ const text = data?.getData(`text/plain`);
3551
+ const { width, height } = getSizeByText(text, PlaitBoard.getHost(board).parentElement, TOPIC_DEFAULT_MAX_WORD_COUNT);
3552
+ const selectedElements = getSelectedElements(board);
3553
+ if (text && selectedElements.length === 1) {
3554
+ insertClipboardText(board, selectedElements[0], text, width, height);
3555
+ }
3556
+ }
3557
+ insertFragment(data, targetPoint);
3558
+ };
3559
+ board.deleteFragment = (data) => {
3560
+ const selectedElements = getSelectedElements(board);
3561
+ deleteSelectedELements(board, selectedElements);
3562
+ deleteFragment(data);
3563
+ };
3564
+ return withEmoji(withAbstract(withDnd(board)));
3565
+ };
3566
+
3567
+ class MindEmojiBaseComponent {
3568
+ get nativeElement() {
3569
+ return this.elementRef.nativeElement;
3570
+ }
3571
+ constructor(elementRef) {
3572
+ this.elementRef = elementRef;
3573
+ this.fontSize = 14;
3574
+ }
3575
+ }
3576
+ MindEmojiBaseComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindEmojiBaseComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
3577
+ MindEmojiBaseComponent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.5", type: MindEmojiBaseComponent, inputs: { fontSize: "fontSize", emojiItem: "emojiItem" }, ngImport: i0 });
3578
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindEmojiBaseComponent, decorators: [{
3579
+ type: Directive
3580
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { fontSize: [{
3581
+ type: Input
3582
+ }], emojiItem: [{
3583
+ type: Input
3584
+ }] } });
3585
+
3586
+ /*
3587
+ * Public API Surface of mind
3588
+ */
3589
+
3590
+ /**
3591
+ * Generated bundle index. Do not edit.
3592
+ */
3593
+
3594
+ export { ABSTRACT_HANDLE_COLOR, ABSTRACT_HANDLE_LENGTH, ABSTRACT_HANDLE_MASK_WIDTH, ABSTRACT_INCLUDED_OUTLINE_OFFSET, AbstractHandlePosition, BASE, COLORS, ELEMENT_TO_NODE, EXTEND_OFFSET, EXTEND_RADIUS, GRAY_COLOR, LayoutDirection, LayoutDirectionsMap, MAX_RADIUS, MINDMAP_ELEMENT_TO_COMPONENT, MINDMAP_KEY, MindElement, MindEmojiBaseComponent, MindModule, MindNodeComponent, MindTransforms, MindmapNode, MindmapNodeShape, MindmapQueries, NODE_FILL, NODE_MIN_WIDTH, PRIMARY_COLOR, PlaitMind, PlaitMindComponent, QUICK_INSERT_CIRCLE_COLOR, QUICK_INSERT_CIRCLE_OFFSET, QUICK_INSERT_INNER_CROSS_COLOR, ROOT_NODE_FILL, ROOT_NODE_STROKE, ROOT_TOPIC_FONT_SIZE, STROKE_WIDTH, TOPIC_COLOR, TOPIC_DEFAULT_MAX_WORD_COUNT, TOPIC_FONT_SIZE, TRANSPARENT, changeRightNodeCount, copyNewNode, correctLayoutByDirection, createMindElement, createMindmapData, deleteSelectedELements, directionCorrector, directionDetector, drawCurvePlaceholderDropNodeG, drawIndentNodeG, drawPlaceholderDropNodeG, drawStraightDropNodeG, extractNodesText, filterChildElement, findLastChild, findLocationLeftIndex, findParentElement, findUpElement, getAbstractHandleRectangle, getAllowedDirection, getAvailableSubLayoutsByLayoutDirections, getBranchDirectionsByLayouts, getChildrenCount, getDefaultMindmapLayout, getEmojiFontSize, getEmojisRectangle, getHitAbstractHandle, getHorizontalFakeY, getInCorrectLayoutDirection, getIndentedFakePoint, getLayoutReverseDirection, getLinkLineColorByMindmapElement, getLocationScope, getMindmapDirection, getNodeShapeByElement, getRectangleByNode, getRectangleByResizingLocation, getRootLayout, getRootLinkLineColorByMindmapElement, getStrokeByMindmapElement, hitMindmapElement, insertMindElement, isChildElement, isChildRight, isChildUp, isCorrectLayout, isMixedLayout, isVirtualKey, readjustmentDropTarget, separateChildren, shouldChangeRightNodeCount, transformNodeToRoot, transformRootToNode, withEmoji, withMind };
3595
+ //# sourceMappingURL=plait-mind.mjs.map