@plait/mind 0.2.0-next.3 → 0.2.0-next.5

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 (43) hide show
  1. package/draw/indented-link.d.ts +2 -2
  2. package/draw/link/logic-link.d.ts +2 -2
  3. package/draw/link.d.ts +2 -2
  4. package/esm2020/draw/indented-link.mjs +7 -6
  5. package/esm2020/draw/link/logic-link.mjs +7 -6
  6. package/esm2020/draw/link.mjs +8 -7
  7. package/esm2020/draw/shape.mjs +3 -3
  8. package/esm2020/drawer/quick-insert.drawer.mjs +9 -8
  9. package/esm2020/interfaces/element.mjs +13 -2
  10. package/esm2020/node.component.mjs +6 -6
  11. package/esm2020/plugins/emoji/emoji-base.component.mjs +4 -2
  12. package/esm2020/plugins/emoji/emoji.drawer.mjs +2 -1
  13. package/esm2020/plugins/with-abstract.mjs +2 -2
  14. package/esm2020/plugins/with-dnd.mjs +2 -2
  15. package/esm2020/plugins/with-mind.mjs +3 -2
  16. package/esm2020/transforms/layout.mjs +8 -2
  17. package/esm2020/utils/abstract/common.mjs +49 -15
  18. package/esm2020/utils/abstract/resize.mjs +2 -2
  19. package/esm2020/utils/clipboard.mjs +10 -3
  20. package/esm2020/utils/draw-placeholder.mjs +20 -20
  21. package/esm2020/utils/index.mjs +2 -2
  22. package/esm2020/utils/mind.mjs +30 -21
  23. package/esm2020/utils/node-style/branch.mjs +28 -0
  24. package/esm2020/utils/node-style/index.mjs +3 -0
  25. package/esm2020/utils/node-style/node.mjs +22 -0
  26. package/esm2020/utils/path.mjs +3 -3
  27. package/fesm2015/plait-mind.mjs +1449 -1377
  28. package/fesm2015/plait-mind.mjs.map +1 -1
  29. package/fesm2020/plait-mind.mjs +1451 -1379
  30. package/fesm2020/plait-mind.mjs.map +1 -1
  31. package/interfaces/element.d.ts +3 -2
  32. package/package.json +1 -1
  33. package/plugins/emoji/emoji-base.component.d.ts +3 -1
  34. package/utils/abstract/common.d.ts +6 -1
  35. package/utils/abstract/resize.d.ts +1 -1
  36. package/utils/draw-placeholder.d.ts +8 -8
  37. package/utils/index.d.ts +1 -1
  38. package/utils/mind.d.ts +2 -1
  39. package/utils/node-style/branch.d.ts +7 -0
  40. package/utils/node-style/index.d.ts +2 -0
  41. package/utils/node-style/node.d.ts +3 -0
  42. package/esm2020/utils/colors.mjs +0 -41
  43. package/utils/colors.d.ts +0 -4
@@ -1,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Component, ChangeDetectionStrategy, NgModule, Directive, Input } from '@angular/core';
3
3
  import * as i2 from '@plait/core';
4
- import { distanceBetweenPointAndRectangle, PlaitElement, drawRoundRectangle, PlaitBoard, Path, Transforms, getRectangleByElements, RectangleClient, getSelectedElements, PlaitNode, NODE_TO_PARENT, ELEMENT_TO_COMPONENT, idCreator, isNullOrUndefined, clearSelectedElement, addSelectedElement, createG, drawAbstractRoundRectangle, PlaitPluginElementComponent, PlaitPointerType, NODE_TO_INDEX, createText, IS_TEXT_EDITABLE, MERGING, transformPoint, toPoint, depthFirstRecursion, PlaitModule, distanceBetweenPointAndPoint, CLIP_BOARD_FORMAT_KEY, BOARD_TO_HOST, removeSelectedElement, PlaitHistoryBoard, hotkeys } from '@plait/core';
5
- import { isIndentedLayout, MindLayoutType, isStandardLayout, isTopLayout, isLeftLayout, isHorizontalLogicLayout, isVerticalLogicLayout, isBottomLayout, isRightLayout, isHorizontalLayout, AbstractNode, getCorrectStartEnd, isChildOfAbstract, getAbstractLayout, ConnectingPosition, GlobalLayout } from '@plait/layouts';
4
+ import { distanceBetweenPointAndRectangle, PlaitElement, PlaitBoard, PlaitNode, NODE_TO_PARENT, Path, ELEMENT_TO_COMPONENT, Transforms, idCreator, isNullOrUndefined, clearSelectedElement, addSelectedElement, drawRoundRectangle, getRectangleByElements, RectangleClient, getSelectedElements, createG, drawAbstractRoundRectangle, PlaitPluginElementComponent, PlaitPointerType, NODE_TO_INDEX, createText, IS_TEXT_EDITABLE, MERGING, transformPoint, toPoint, depthFirstRecursion, PlaitModule, distanceBetweenPointAndPoint, CLIP_BOARD_FORMAT_KEY, BOARD_TO_HOST, removeSelectedElement, PlaitHistoryBoard, hotkeys } from '@plait/core';
5
+ import { AbstractNode, isChildOfAbstract, isIndentedLayout, getAbstractLayout, MindLayoutType, isStandardLayout, isTopLayout, isLeftLayout, isHorizontalLogicLayout, isVerticalLogicLayout, isBottomLayout, isRightLayout, isHorizontalLayout, getCorrectStartEnd, ConnectingPosition, GlobalLayout } from '@plait/layouts';
6
6
  import { getSizeByText, ROOT_DEFAULT_HEIGHT, TEXT_DEFAULT_HEIGHT, drawRichtext, updateForeignObject, createForeignObject, updateRichText, setFullSelectionAndFocus, getRichtextContentSize, hasEditableTarget, RichtextModule } from '@plait/richtext';
7
7
  import { fromEvent, Subject, timer } from 'rxjs';
8
8
  import { take, takeUntil, filter, debounceTime } from 'rxjs/operators';
9
- import { Node, Editor, Operation, Path as Path$1 } from 'slate';
9
+ import { Node, Path as Path$1, Editor, Operation } from 'slate';
10
10
  import { pointsOnBezierCurves } from 'points-on-curve';
11
11
  import { isKeyHotkey } from 'is-hotkey';
12
12
  import * as i1 from '@angular/common';
@@ -68,6 +68,249 @@ function hitMindElement(board, point, element) {
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ * get correctly layout:
73
+ * 1. root is standard -> left or right
74
+ * 2. correct layout by incorrect layout direction
75
+ * @param element
76
+ */
77
+ const getCorrectLayoutByElement = (element) => {
78
+ const { root } = findUpElement(element);
79
+ const rootLayout = root.layout || getDefaultLayout();
80
+ let correctRootLayout = rootLayout;
81
+ if (element.isRoot) {
82
+ return correctRootLayout;
83
+ }
84
+ const component = PlaitElement.getComponent(element);
85
+ let layout = element.layout;
86
+ let parentComponent = null;
87
+ let parent = component.parent.origin;
88
+ while (!layout && parent) {
89
+ parentComponent = PlaitElement.getComponent(parent);
90
+ layout = parentComponent.node.origin.layout;
91
+ parent = parentComponent.parent?.origin;
92
+ }
93
+ if ((AbstractNode.isAbstract(element) || isChildOfAbstract(MindElement.getNode(element))) &&
94
+ isIndentedLayout(layout)) {
95
+ return getAbstractLayout(layout);
96
+ }
97
+ // handle root standard
98
+ if (rootLayout === MindLayoutType.standard) {
99
+ correctRootLayout = component?.node.left ? MindLayoutType.left : MindLayoutType.right;
100
+ }
101
+ if (parentComponent && parentComponent.node.origin.isRoot) {
102
+ return correctRootLayout;
103
+ }
104
+ if (layout) {
105
+ const incorrectDirection = getInCorrectLayoutDirection(correctRootLayout, layout);
106
+ if (incorrectDirection) {
107
+ return correctLayoutByDirection(layout, incorrectDirection);
108
+ }
109
+ else {
110
+ return layout;
111
+ }
112
+ }
113
+ else {
114
+ return correctRootLayout;
115
+ }
116
+ };
117
+
118
+ const getBranchLayouts = (element) => {
119
+ const layouts = [];
120
+ if (element.layout) {
121
+ //getCorrectLayoutByElement含有递归操作,getBranchMindmapLayouts本身也有递归操作,有待优化
122
+ layouts.unshift(getCorrectLayoutByElement(element));
123
+ }
124
+ let parent = findParentElement(element);
125
+ while (parent) {
126
+ if (parent.layout) {
127
+ layouts.unshift(parent.layout);
128
+ }
129
+ parent = findParentElement(parent);
130
+ }
131
+ return layouts;
132
+ };
133
+
134
+ /**
135
+ * get available sub layouts by element
136
+ * @param element
137
+ * @returns MindLayoutType[]
138
+ */
139
+ const getAvailableSubLayoutsByElement = (element) => {
140
+ const parentElement = findParentElement(element);
141
+ if (parentElement) {
142
+ const branchLayouts = getBranchLayouts(parentElement);
143
+ if (branchLayouts[0] === MindLayoutType.standard) {
144
+ const node = MindElement.getNode(element);
145
+ branchLayouts[0] = node.left ? MindLayoutType.left : MindLayoutType.right;
146
+ }
147
+ const currentLayoutDirections = getBranchDirectionsByLayouts(branchLayouts);
148
+ let availableSubLayouts = getAvailableSubLayoutsByLayoutDirections(currentLayoutDirections);
149
+ const parentLayout = [branchLayouts[branchLayouts.length - 1]];
150
+ const parentDirections = getBranchDirectionsByLayouts(parentLayout);
151
+ const parentAvailableSubLayouts = getAvailableSubLayoutsByLayoutDirections(parentDirections);
152
+ availableSubLayouts = availableSubLayouts.filter(layout => parentAvailableSubLayouts.some(parentAvailableSubLayout => parentAvailableSubLayout === layout));
153
+ return availableSubLayouts;
154
+ }
155
+ return undefined;
156
+ };
157
+
158
+ /**
159
+ * 获取父节点布局类型
160
+ * @param element
161
+ * @returns MindLayoutType
162
+ */
163
+ const getLayoutParentByElement = (element) => {
164
+ let parent = findParentElement(element);
165
+ while (parent) {
166
+ if (parent.layout) {
167
+ return parent.layout;
168
+ }
169
+ parent = findParentElement(parent);
170
+ }
171
+ return getDefaultLayout();
172
+ };
173
+
174
+ const getLayoutByElement = (element) => {
175
+ const layout = element.layout;
176
+ if (layout) {
177
+ return layout;
178
+ }
179
+ if (AbstractNode.isAbstract(element) ||
180
+ (isChildOfAbstract(MindElement.getNode(element)) && isIndentedLayout(layout))) {
181
+ const parentLayout = getLayoutParentByElement(element);
182
+ return getAbstractLayout(parentLayout);
183
+ }
184
+ return getLayoutParentByElement(element);
185
+ };
186
+
187
+ const MindQueries = {
188
+ getAvailableSubLayoutsByElement,
189
+ getLayoutParentByElement,
190
+ getBranchLayouts,
191
+ getLayoutByElement,
192
+ getCorrectLayoutByElement
193
+ };
194
+
195
+ const PlaitMind = {
196
+ isMind: (value) => {
197
+ return value.type === 'mindmap';
198
+ }
199
+ };
200
+ const MindElement = {
201
+ hasLayout(value, layout) {
202
+ const _layout = MindQueries.getLayoutByElement(value);
203
+ return _layout === layout;
204
+ },
205
+ isIndentedLayout(value) {
206
+ const _layout = MindQueries.getLayoutByElement(value);
207
+ return isIndentedLayout(_layout);
208
+ },
209
+ isMindElement(board, element) {
210
+ const path = PlaitBoard.findPath(board, element);
211
+ const rootElement = PlaitNode.get(board, path.slice(0, 1));
212
+ if (PlaitMind.isMind(rootElement)) {
213
+ return true;
214
+ }
215
+ else {
216
+ return false;
217
+ }
218
+ },
219
+ getParent(node) {
220
+ if (PlaitMind.isMind(node)) {
221
+ throw new Error('mind root node can not get parent');
222
+ }
223
+ const parent = NODE_TO_PARENT.get(node);
224
+ return parent;
225
+ },
226
+ getRoot(board, element) {
227
+ const path = PlaitBoard.findPath(board, element);
228
+ return PlaitNode.get(board, path.slice(0, 1));
229
+ },
230
+ getAncestors(board, element) {
231
+ const path = PlaitBoard.findPath(board, element);
232
+ const parents = [];
233
+ for (const p of Path.ancestors(path, { reverse: true })) {
234
+ const n = PlaitNode.get(board, p);
235
+ if (n && !PlaitBoard.isBoard(n)) {
236
+ parents.push(n);
237
+ }
238
+ }
239
+ return parents;
240
+ },
241
+ getNode(element) {
242
+ const node = ELEMENT_TO_NODE.get(element);
243
+ if (!node) {
244
+ throw new Error(`can not get node from ${JSON.stringify(element)}`);
245
+ }
246
+ return node;
247
+ },
248
+ hasEmojis(element) {
249
+ if (element.data.emojis) {
250
+ return true;
251
+ }
252
+ else {
253
+ return false;
254
+ }
255
+ },
256
+ getEmojis(element) {
257
+ return element.data.emojis;
258
+ }
259
+ };
260
+
261
+ const MindNode = {
262
+ get(root, path) {
263
+ let node = root;
264
+ for (let i = 0; i < path.length; i++) {
265
+ const p = path[i];
266
+ if (!node || !node.children || !node.children[p]) {
267
+ throw new Error(`Cannot find a descendant at path [${path}]`);
268
+ }
269
+ node = node.children[p];
270
+ }
271
+ return node;
272
+ },
273
+ isEquals(node, otherNode) {
274
+ const hasSameSize = node.x === otherNode.x && node.y === otherNode.y && node.width === otherNode.width && node.height === otherNode.height;
275
+ const hasSameOrigin = node.origin === otherNode.origin;
276
+ let hasSameParentOriginChildren = false;
277
+ if (node.parent && otherNode.parent) {
278
+ hasSameParentOriginChildren = node.parent.origin.children == otherNode.parent.origin.children;
279
+ }
280
+ return hasSameSize && hasSameOrigin && hasSameParentOriginChildren;
281
+ }
282
+ };
283
+
284
+ var LayoutDirection;
285
+ (function (LayoutDirection) {
286
+ LayoutDirection["top"] = "top";
287
+ LayoutDirection["right"] = "right";
288
+ LayoutDirection["bottom"] = "bottom";
289
+ LayoutDirection["left"] = "left";
290
+ })(LayoutDirection || (LayoutDirection = {}));
291
+ const LayoutDirectionsMap = {
292
+ [MindLayoutType.right]: [LayoutDirection.right],
293
+ [MindLayoutType.left]: [LayoutDirection.left],
294
+ [MindLayoutType.upward]: [LayoutDirection.top],
295
+ [MindLayoutType.downward]: [LayoutDirection.bottom],
296
+ [MindLayoutType.rightBottomIndented]: [LayoutDirection.right, LayoutDirection.bottom],
297
+ [MindLayoutType.rightTopIndented]: [LayoutDirection.right, LayoutDirection.top],
298
+ [MindLayoutType.leftBottomIndented]: [LayoutDirection.left, LayoutDirection.bottom],
299
+ [MindLayoutType.leftTopIndented]: [LayoutDirection.left, LayoutDirection.top]
300
+ };
301
+
302
+ var AbstractHandlePosition;
303
+ (function (AbstractHandlePosition) {
304
+ AbstractHandlePosition["start"] = "start";
305
+ AbstractHandlePosition["end"] = "end";
306
+ })(AbstractHandlePosition || (AbstractHandlePosition = {}));
307
+ var AbstractResizeState;
308
+ (function (AbstractResizeState) {
309
+ AbstractResizeState["start"] = "start";
310
+ AbstractResizeState["resizing"] = "resizing";
311
+ AbstractResizeState["end"] = "end";
312
+ })(AbstractResizeState || (AbstractResizeState = {}));
313
+
71
314
  const getBranchDirectionsByLayouts = (branchLayouts) => {
72
315
  const branchDirections = [];
73
316
  branchLayouts.forEach(l => {
@@ -171,1485 +414,1301 @@ const getRootLayout = (root) => {
171
414
  return root.layout || getDefaultLayout();
172
415
  };
173
416
 
174
- const getNodeShapeByElement = (element) => {
175
- let nodeShape = element.shape;
176
- if (nodeShape) {
177
- return nodeShape;
178
- }
179
- let parent = findParentElement(element);
180
- while (parent) {
181
- if (parent.shape) {
182
- return parent.shape;
183
- }
184
- parent = findParentElement(parent);
185
- }
186
- return MindNodeShape.roundRectangle;
187
- };
417
+ function enterNodeEditing(element) {
418
+ const component = ELEMENT_TO_COMPONENT.get(element);
419
+ component.startEditText(false, false);
420
+ }
188
421
 
189
- function isVirtualKey(e) {
190
- const isMod = e.ctrlKey || e.metaKey;
191
- const isAlt = isKeyHotkey('alt', e);
192
- const isShift = isKeyHotkey('shift', e);
193
- const isCapsLock = e.key.includes('CapsLock');
194
- const isTab = e.key.includes('Tab');
195
- const isEsc = e.key.includes('Escape');
196
- const isF = e.key.startsWith('F');
197
- const isArrow = e.key.includes('Arrow') ? true : false;
198
- return isCapsLock || isMod || isAlt || isArrow || isShift || isTab || isEsc || isF;
199
- }
200
-
201
- function drawLink(roughSVG, node, child, defaultStroke = null, isHorizontal = true, needDrawUnderline = true) {
202
- let beginX, beginY, endX, endY, beginNode = node, endNode = child;
203
- const layout = MindQueries.getCorrectLayoutByElement(node.origin);
204
- if (isHorizontal) {
205
- if (!isChildRight(node, child)) {
206
- beginNode = child;
207
- endNode = node;
208
- }
209
- beginX = beginNode.x + beginNode.width - beginNode.hGap;
210
- beginY = beginNode.y + beginNode.height / 2;
211
- endX = endNode.x + endNode.hGap;
212
- endY = endNode.y + endNode.height / 2;
213
- if (node.parent &&
214
- isIndentedLayout(MindQueries.getLayoutByElement(node.parent?.origin)) &&
215
- getNodeShapeByElement(node.origin) === MindNodeShape.underline) {
216
- if (isChildRight(node, child)) {
217
- beginY = node.y + node.height - node.vGap;
218
- }
219
- else {
220
- endY = node.y + node.height - node.vGap;
221
- }
222
- }
223
- }
224
- else {
225
- if (node.y > child.y) {
226
- beginNode = child;
227
- endNode = node;
228
- }
229
- beginX = beginNode.x + beginNode.width / 2;
230
- beginY = beginNode.y + beginNode.height - beginNode.vGap;
231
- endX = endNode.x + endNode.width / 2;
232
- endY = endNode.y + endNode.vGap;
233
- }
234
- const stroke = defaultStroke || getLinkLineColorByMindElement(child.origin);
235
- const strokeWidth = child.origin.linkLineWidth ? child.origin.linkLineWidth : STROKE_WIDTH;
236
- if (endNode.origin.isRoot) {
237
- if (layout === MindLayoutType.left || isStandardLayout(layout)) {
238
- endX -= strokeWidth;
422
+ const separateChildren = (parentElement) => {
423
+ const rightNodeCount = parentElement.rightNodeCount;
424
+ const children = parentElement.children;
425
+ let rightChildren = [], leftChildren = [];
426
+ for (let i = 0; i < children.length; i++) {
427
+ const child = children[i];
428
+ if (AbstractNode.isAbstract(child) && child.end < rightNodeCount) {
429
+ rightChildren.push(child);
430
+ continue;
239
431
  }
240
- if (layout === MindLayoutType.upward) {
241
- endY -= strokeWidth;
432
+ if (AbstractNode.isAbstract(child) && child.start >= rightNodeCount) {
433
+ leftChildren.push(child);
434
+ continue;
242
435
  }
243
- }
244
- if (beginNode.origin.isRoot) {
245
- if (layout === MindLayoutType.right || isStandardLayout(layout)) {
246
- beginX += strokeWidth;
436
+ if (i < rightNodeCount) {
437
+ rightChildren.push(child);
247
438
  }
248
- if (layout === MindLayoutType.downward) {
249
- beginY += strokeWidth;
439
+ else {
440
+ leftChildren.push(child);
250
441
  }
251
442
  }
252
- if (isHorizontal) {
253
- let curve = [
254
- [beginX, beginY],
255
- [beginX + (beginNode.hGap + endNode.hGap) / 3, beginY],
256
- [endX - (beginNode.hGap + endNode.hGap) / 2, endY],
257
- [endX, endY]
258
- ];
259
- const shape = getNodeShapeByElement(child.origin);
260
- if (!node.origin.isRoot) {
261
- if (node.x > child.x) {
262
- curve = [
263
- [beginX, beginY],
264
- [beginX + (beginNode.hGap + endNode.hGap) / 3, beginY],
265
- [endX - (beginNode.hGap + endNode.hGap) / 2, endY],
266
- [endX - 12, endY]
267
- ];
268
- const line = [
269
- [endX - 12, endY],
270
- [endX - 12, endY],
271
- [endX, endY]
272
- ];
273
- curve = [...curve, ...line];
274
- }
275
- else {
276
- curve = [
277
- [beginX + 12, beginY],
278
- [beginX + (beginNode.hGap + endNode.hGap) / 2, beginY],
279
- [endX - (beginNode.hGap + endNode.hGap) / 3, endY],
280
- [endX, endY]
281
- ];
282
- const line = [
283
- [beginX, beginY],
284
- [beginX + 12, beginY],
285
- [beginX + 12, beginY]
286
- ];
287
- curve = [...line, ...curve];
288
- }
289
- }
290
- if (needDrawUnderline && shape === MindNodeShape.underline) {
291
- if (child.left) {
292
- const underline = [
293
- [beginX - (beginNode.width - beginNode.hGap * 2), beginY],
294
- [beginX - (beginNode.width - beginNode.hGap * 2), beginY],
295
- [beginX - (beginNode.width - beginNode.hGap * 2), beginY]
296
- ];
297
- curve = [...underline, ...curve];
298
- }
299
- else {
300
- const underline = [
301
- [endX + (endNode.width - endNode.hGap * 2), endY],
302
- [endX + (endNode.width - endNode.hGap * 2), endY],
303
- [endX + (endNode.width - endNode.hGap * 2), endY]
304
- ];
305
- curve = [...curve, ...underline];
306
- }
307
- }
308
- const points = pointsOnBezierCurves(curve);
309
- return roughSVG.curve(points, { stroke, strokeWidth });
443
+ return { leftChildren, rightChildren };
444
+ };
445
+ const isSetAbstract = (element) => {
446
+ return !!getCorrespondingAbstract(element);
447
+ };
448
+ const canSetAbstract = (element) => {
449
+ return !PlaitElement.isRootElement(element) && !AbstractNode.isAbstract(element) && !isSetAbstract(element);
450
+ };
451
+ const setAbstract = (board, elements) => {
452
+ let elementGroup = filterChildElement(elements);
453
+ const { parentElements, abstractIncludedGroups } = divideElementByParent(elementGroup);
454
+ abstractIncludedGroups.forEach((group, index) => {
455
+ const groupParent = parentElements[index];
456
+ setAbstractByElements(board, groupParent, group);
457
+ });
458
+ };
459
+ const setAbstractByElements = (board, groupParent, group) => {
460
+ const indexArray = group.map(child => groupParent.children.indexOf(child)).sort((a, b) => a - b);
461
+ const rightNodeCount = groupParent?.rightNodeCount;
462
+ const start = indexArray[0], end = indexArray[indexArray.length - 1];
463
+ if (isStandardLayout(MindQueries.getLayoutByElement(groupParent)) &&
464
+ rightNodeCount &&
465
+ start < rightNodeCount &&
466
+ end >= rightNodeCount) {
467
+ const childrenLength = groupParent.children.length;
468
+ const path = [...PlaitBoard.findPath(board, groupParent), childrenLength];
469
+ const leftChildren = indexArray.filter(index => index >= rightNodeCount);
470
+ const rightChildren = indexArray.filter(index => index < rightNodeCount);
471
+ insertAbstractNode(board, path, rightChildren[0], rightChildren[rightChildren.length - 1]);
472
+ insertAbstractNode(board, Path.next(path), leftChildren[0], leftChildren[leftChildren.length - 1]);
310
473
  }
311
474
  else {
312
- let curve = [
313
- [beginX, beginY],
314
- [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
315
- [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
316
- [endX, endY]
317
- ];
318
- if (!node.origin.isRoot) {
319
- if (isTopLayout(layout)) {
320
- curve = [
321
- [beginX, beginY],
322
- [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
323
- [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
324
- [endX, endY - 12]
325
- ];
326
- const line = [
327
- [endX, endY - 12],
328
- [endX, endY - 12],
329
- [endX, endY]
330
- ];
331
- curve = [...curve, ...line];
332
- }
333
- else {
334
- curve = [
335
- [beginX, beginY + 12],
336
- [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
337
- [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
338
- [endX, endY]
339
- ];
340
- const line = [
341
- [beginX, beginY],
342
- [beginX, beginY + 12],
343
- [beginX, beginY + 12]
344
- ];
345
- curve = [...line, ...curve];
346
- }
347
- }
348
- const points = pointsOnBezierCurves(curve);
349
- return roughSVG.curve(points, { stroke, strokeWidth });
475
+ const path = [...PlaitBoard.findPath(board, groupParent), groupParent.children.length];
476
+ insertAbstractNode(board, path, start, end);
350
477
  }
351
- }
478
+ };
479
+ const insertAbstractNode = (board, path, start, end) => {
480
+ const mindElement = createMindElement('概要', 28, 20, {
481
+ strokeColor: GRAY_COLOR,
482
+ branchColor: GRAY_COLOR
483
+ });
484
+ mindElement.start = start;
485
+ mindElement.end = end;
486
+ Transforms.insertNode(board, mindElement, path);
487
+ };
488
+ const handleAbstractIncluded = (board, element) => {
489
+ const rightNodeCount = element.rightNodeCount;
490
+ const abstract = element.children.find(child => {
491
+ return AbstractNode.isAbstract(child) && child.end >= rightNodeCount && child.start < rightNodeCount;
492
+ });
493
+ if (abstract) {
494
+ const path = PlaitBoard.findPath(board, abstract);
495
+ Transforms.setNode(board, { end: rightNodeCount - 1 }, path);
496
+ }
497
+ };
498
+ const getCorrespondingAbstract = (element) => {
499
+ const parent = MindElement.getParent(element);
500
+ if (!parent)
501
+ return undefined;
502
+ const elementIndex = parent.children.indexOf(element);
503
+ return parent.children.find(child => {
504
+ return AbstractNode.isAbstract(child) && elementIndex >= child.start && elementIndex <= child.end;
505
+ });
506
+ };
507
+ const getBehindAbstracts = (element) => {
508
+ const parent = MindElement.getParent(element);
509
+ const index = parent.children.indexOf(element);
510
+ return parent.children.filter(child => AbstractNode.isAbstract(child) && child.start > index);
511
+ };
512
+ const insertSiblingElementHandleAbstract = (board, selectedElement) => {
513
+ const abstract = getCorrespondingAbstract(selectedElement);
514
+ if (abstract) {
515
+ PlaitBoard.findPath(board, abstract);
516
+ Transforms.setNode(board, { end: abstract.end + 1 }, PlaitBoard.findPath(board, abstract));
517
+ }
518
+ const abstracts = getBehindAbstracts(selectedElement);
519
+ if (abstracts.length) {
520
+ moveAbstractPosition(board, abstracts, 1);
521
+ }
522
+ };
523
+ const moveAbstractPosition = (board, abstracts, step) => {
524
+ abstracts.forEach(abstract => {
525
+ Transforms.setNode(board, { start: abstract.start + step, end: abstract.end + step }, PlaitBoard.findPath(board, abstract));
526
+ });
527
+ };
352
528
 
353
- const drawPlaceholderDropNodeG = (dropTarget, roughSVG, fakeDropNodeG) => {
354
- const targetComponent = PlaitElement.getComponent(dropTarget.target);
355
- const targetRect = getRectangleByNode(targetComponent.node);
356
- if (dropTarget.detectResult && ['right', 'left'].includes(dropTarget.detectResult)) {
357
- drawStraightDropNodeG(targetRect, dropTarget.detectResult, targetComponent, roughSVG, fakeDropNodeG);
529
+ function findParentElement(element) {
530
+ const component = PlaitElement.getComponent(element);
531
+ if (component && component.parent) {
532
+ return component.parent.origin;
358
533
  }
359
- if (targetComponent.parent && dropTarget.detectResult && ['top', 'bottom'].includes(dropTarget.detectResult)) {
360
- const parentComponent = PlaitElement.getComponent(targetComponent.parent.origin);
361
- const targetIndex = parentComponent.node.origin.children.indexOf(targetComponent.node.origin);
362
- drawCurvePlaceholderDropNodeG(targetRect, dropTarget.detectResult, targetIndex, targetComponent, roughSVG, parentComponent, fakeDropNodeG);
534
+ return undefined;
535
+ }
536
+ function findUpElement(element) {
537
+ let branch;
538
+ let root = element;
539
+ let parent = findParentElement(element);
540
+ while (parent) {
541
+ branch = root;
542
+ root = parent;
543
+ parent = findParentElement(parent);
363
544
  }
545
+ return { root, branch };
546
+ }
547
+ const getChildrenCount = (element) => {
548
+ const count = element.children.reduce((p, c) => {
549
+ return p + getChildrenCount(c);
550
+ }, 0);
551
+ return count + element.children.length;
364
552
  };
365
- const drawCurvePlaceholderDropNodeG = (targetRect, detectResult, targetIndex, targetComponent, roughSVG, parentComponent, fakeDropNodeG) => {
366
- const parentNodeLayout = MindQueries.getCorrectLayoutByElement(parentComponent.node.origin);
367
- const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.parent.origin);
368
- const strokeWidth = targetComponent.node.origin.linkLineWidth ? targetComponent.node.origin.linkLineWidth : STROKE_WIDTH;
369
- let fakeX = targetComponent.node.x, fakeY = targetRect.y - 30, fakeRectangleStartX = targetRect.x, fakeRectangleEndX = targetRect.x + 30, fakeRectangleStartY = fakeY, fakeRectangleEndY = fakeRectangleStartY + 12, width = 30;
370
- if (isLeftLayout(layout)) {
371
- fakeX = targetComponent.node.x + targetComponent.node.width - 30;
372
- fakeRectangleStartX = targetRect.x + targetRect.width - 30;
373
- fakeRectangleEndX = targetRect.x + targetRect.width;
553
+ const isChildElement = (origin, child) => {
554
+ let parent = findParentElement(child);
555
+ while (parent) {
556
+ if (parent === origin) {
557
+ return true;
558
+ }
559
+ parent = findParentElement(parent);
374
560
  }
375
- if (isHorizontalLogicLayout(parentNodeLayout)) {
376
- fakeY = getHorizontalFakeY(detectResult, targetIndex, parentComponent.node, targetRect, layout, fakeY);
377
- if (isStandardLayout(parentNodeLayout)) {
378
- const rightNodeCount = parentComponent.node.origin.rightNodeCount || 0;
379
- const idx = parentComponent.node.children.findIndex(x => x === targetComponent.node);
380
- const isLeft = idx >= rightNodeCount;
381
- // 标准布局的左,需要调整 x
382
- if (isLeft) {
383
- fakeX = targetComponent.node.x + targetComponent.node.width - 30;
384
- fakeRectangleStartX = targetRect.x + targetRect.width - 30;
385
- fakeRectangleEndX = targetRect.x + targetRect.width;
386
- }
387
- const isLeftFirst = idx === rightNodeCount;
388
- const isRightLast = idx === rightNodeCount - 1;
389
- // 拖拽至左第一个节点的情况
390
- if (detectResult === 'top' && isLeftFirst) {
391
- fakeY = targetRect.y - targetRect.height;
392
- }
393
- if (detectResult === 'bottom' && isRightLast) {
394
- fakeY = targetRect.y + targetRect.height + 30;
395
- }
561
+ return false;
562
+ };
563
+ const filterChildElement = (elements) => {
564
+ let result = [];
565
+ elements.forEach(element => {
566
+ const isChild = elements.some(node => {
567
+ return isChildElement(node, element);
568
+ });
569
+ if (!isChild) {
570
+ result.push(element);
396
571
  }
397
- fakeRectangleStartY = fakeY;
398
- fakeRectangleEndY = fakeRectangleStartY + 12;
572
+ });
573
+ return result;
574
+ };
575
+ const isChildRight = (node, child) => {
576
+ return node.x < child.x;
577
+ };
578
+ const isChildUp = (node, child) => {
579
+ return node.y > child.y;
580
+ };
581
+ const copyNewNode = (node) => {
582
+ const newNode = { ...node };
583
+ newNode.id = idCreator();
584
+ newNode.children = [];
585
+ for (const childNode of node.children) {
586
+ newNode.children.push(copyNewNode(childNode));
399
587
  }
400
- if (isVerticalLogicLayout(layout)) {
401
- parentComponent = targetComponent;
402
- targetComponent = PlaitElement.getComponent(targetComponent.parent.origin);
403
- fakeX = parentComponent.node.x;
404
- width = parentComponent.node.width;
405
- const vGap = BASE * 6 + strokeWidth;
406
- if (isTopLayout(layout) && detectResult === 'top') {
407
- fakeY = targetRect.y - vGap;
408
- fakeRectangleStartY = fakeY - vGap + strokeWidth;
409
- }
410
- if (isBottomLayout(layout) && detectResult === 'bottom') {
411
- fakeY = targetRect.y + targetRect.height + vGap;
412
- fakeRectangleStartY = fakeY + vGap - strokeWidth;
413
- }
414
- fakeRectangleStartX = fakeX + Math.ceil(parentComponent.node.width / 2) - parentComponent.node.hGap - Math.ceil(strokeWidth / 2);
415
- fakeRectangleEndX = fakeRectangleStartX + 30;
416
- fakeRectangleEndY = fakeRectangleStartY + 12;
588
+ return newNode;
589
+ };
590
+ const transformRootToNode = (board, node) => {
591
+ const newNode = { ...node };
592
+ delete newNode.isRoot;
593
+ delete newNode.rightNodeCount;
594
+ delete newNode.type;
595
+ const text = Node.string(node.data.topic.children[0]) || ' ';
596
+ const { width, height } = getSizeByText(text, PlaitBoard.getViewportContainer(board), TOPIC_DEFAULT_MAX_WORD_COUNT);
597
+ newNode.width = Math.max(width, NODE_MIN_WIDTH);
598
+ newNode.height = height;
599
+ if (newNode.layout === MindLayoutType.standard) {
600
+ delete newNode.layout;
417
601
  }
418
- if (isIndentedLayout(layout)) {
419
- // 偏移一个 Gap
420
- if (isLeftLayout(layout)) {
421
- fakeX -= BASE * 4;
422
- }
423
- if (isRightLayout(layout)) {
424
- fakeX += BASE * 4;
602
+ return newNode;
603
+ };
604
+ const transformAbstractToNode = (node) => {
605
+ const newNode = { ...node };
606
+ delete newNode.start;
607
+ delete newNode.end;
608
+ return newNode;
609
+ };
610
+ const transformNodeToRoot = (board, node) => {
611
+ const newElement = { ...node };
612
+ let text = Node.string(newElement.data.topic);
613
+ if (!text) {
614
+ text = '思维导图';
615
+ newElement.data.topic = { children: [{ text }] };
616
+ }
617
+ delete newElement?.strokeColor;
618
+ delete newElement?.fill;
619
+ delete newElement?.shape;
620
+ delete newElement?.strokeWidth;
621
+ const { width, height } = getSizeByText(text, PlaitBoard.getViewportContainer(board), TOPIC_DEFAULT_MAX_WORD_COUNT, ROOT_TOPIC_FONT_SIZE);
622
+ newElement.width = Math.max(width, NODE_MIN_WIDTH);
623
+ newElement.height = height;
624
+ return {
625
+ ...newElement,
626
+ layout: newElement.layout ?? MindLayoutType.right,
627
+ isCollapsed: false,
628
+ isRoot: true,
629
+ type: 'mindmap'
630
+ };
631
+ };
632
+ const extractNodesText = (node) => {
633
+ let str = '';
634
+ if (node) {
635
+ str += Node.string(node.data.topic.children[0]) + ' ';
636
+ for (const childNode of node.children) {
637
+ str += extractNodesText(childNode);
425
638
  }
426
- if (isTopLayout(layout)) {
427
- if (detectResult === 'top') {
428
- const isLastNode = targetIndex === parentComponent.node.origin.children.length - 1;
429
- if (isLastNode) {
430
- fakeY = targetRect.y - targetRect.height - BASE;
431
- }
432
- else {
433
- const nextComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex + 1]);
434
- const nextRect = getRectangleByNode(nextComponent.node);
435
- fakeY = targetRect.y - Math.abs((nextRect.y + nextRect.height - targetRect.y) / 2);
436
- }
437
- }
438
- if (detectResult === 'bottom') {
439
- const isFirstNode = targetIndex === 0;
440
- if (isFirstNode) {
441
- const parentRect = getRectangleByNode(parentComponent.node);
442
- fakeY = parentRect.y - parentRect.height / 2 - BASE;
443
- }
444
- else {
445
- const previousComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex + 1]);
446
- const previousRect = getRectangleByNode(previousComponent.node);
447
- fakeY = previousRect.y - Math.abs((targetRect.y + targetRect.height - previousRect.y) / 2);
448
- }
449
- }
639
+ }
640
+ return str;
641
+ };
642
+ const changeRightNodeCount = (board, parentPath, changeNumber) => {
643
+ const _rightNodeCount = board.children[parentPath[0]].rightNodeCount;
644
+ Transforms.setNode(board, {
645
+ rightNodeCount: changeNumber >= 0
646
+ ? _rightNodeCount + changeNumber
647
+ : _rightNodeCount + changeNumber < 0
648
+ ? 0
649
+ : _rightNodeCount + changeNumber
650
+ }, parentPath);
651
+ };
652
+ const shouldChangeRightNodeCount = (selectedElement) => {
653
+ const parentElement = findParentElement(selectedElement);
654
+ if (parentElement) {
655
+ const nodeIndex = parentElement.children.findIndex(item => item.id === selectedElement.id);
656
+ if (parentElement.isRoot &&
657
+ getRootLayout(parentElement) === MindLayoutType.standard &&
658
+ parentElement.rightNodeCount &&
659
+ nodeIndex <= parentElement.rightNodeCount - 1) {
660
+ return true;
450
661
  }
451
- fakeRectangleStartX = fakeX;
452
- fakeRectangleEndX = fakeRectangleStartX + 30;
453
- fakeRectangleStartY = fakeY;
454
- fakeRectangleEndY = fakeRectangleStartY + 12;
455
662
  }
456
- // 构造一条曲线
457
- const fakeNode = { ...targetComponent.node, x: fakeX, y: fakeY, width, height: 12 };
458
- const linkSVGG = isIndentedLayout(layout)
459
- ? drawIndentedLink(roughSVG, parentComponent.node, fakeNode, PRIMARY_COLOR, false)
460
- : drawLink(roughSVG, parentComponent.node, fakeNode, PRIMARY_COLOR, isHorizontalLayout(layout), false);
461
- // 构造一个矩形框坐标
462
- const fakeRectangleG = drawRoundRectangle(roughSVG, fakeRectangleStartX, fakeRectangleStartY, fakeRectangleEndX, fakeRectangleEndY, {
463
- stroke: PRIMARY_COLOR,
464
- strokeWidth: 2,
465
- fill: PRIMARY_COLOR,
466
- fillStyle: 'solid'
663
+ return false;
664
+ };
665
+ const createDefaultMindMapElement = (point, rightNodeCount, layout) => {
666
+ const root = createMindElement('思维导图', 72, ROOT_DEFAULT_HEIGHT, { shape: MindNodeShape.roundRectangle, layout });
667
+ root.rightNodeCount = rightNodeCount;
668
+ root.isRoot = true;
669
+ root.type = 'mindmap';
670
+ root.points = [point];
671
+ const children = [1, 1, 1].map(() => {
672
+ return createMindElement('新建节点', 56, TEXT_DEFAULT_HEIGHT, { shape: MindNodeShape.roundRectangle });
467
673
  });
468
- fakeDropNodeG?.appendChild(linkSVGG);
469
- fakeDropNodeG?.appendChild(fakeRectangleG);
674
+ root.children = children;
675
+ return root;
470
676
  };
471
- const drawStraightDropNodeG = (targetRect, detectResult, targetComponent, roughSVG, fakeDropNodeG) => {
472
- const { x, y, width, height } = targetRect;
473
- const lineLength = 40;
474
- let startLinePoint = x + width;
475
- let endLinePoint = x + width + lineLength;
476
- let startRectanglePointX = x + width + lineLength;
477
- let endRectanglePointX = x + lineLength + width + 30;
478
- let startRectanglePointY = y + height / 2 - 6;
479
- let endRectanglePointY = y + height / 2 - 6 + 12;
480
- if (detectResult === 'left') {
481
- startLinePoint = x - lineLength;
482
- endLinePoint = x;
483
- startRectanglePointX = x - lineLength - 30;
484
- endRectanglePointX = x - lineLength;
485
- }
486
- let fakeY = targetComponent.node.y;
487
- let fakeX = targetRect.x;
488
- const strokeWidth = targetComponent.node.origin.linkLineWidth ? targetComponent.node.origin.linkLineWidth : STROKE_WIDTH;
489
- const pointOptions = {
490
- fakeX,
491
- fakeY,
492
- x,
493
- y,
677
+ const createMindElement = (text, width, height, options) => {
678
+ const newElement = {
679
+ id: idCreator(),
680
+ data: {
681
+ topic: { children: [{ text }] }
682
+ },
683
+ children: [],
494
684
  width,
495
685
  height,
496
- strokeWidth
686
+ fill: options.fill,
687
+ strokeColor: options.strokeColor,
688
+ strokeWidth: options.strokeWidth,
689
+ shape: options.shape
497
690
  };
498
- const parentLayout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
499
- const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
500
- if (!isMixedLayout(parentLayout, layout)) {
501
- // 构造一条直线
502
- let linePoints = [
503
- [startLinePoint, y + height / 2],
504
- [endLinePoint, y + height / 2]
505
- ];
506
- if (isIndentedLayout(parentLayout)) {
507
- const fakePoint = getIndentedFakePoint(parentLayout, pointOptions);
508
- drawIndentNodeG(fakeDropNodeG, roughSVG, fakePoint, targetComponent.node);
509
- return;
510
- }
511
- else if (isVerticalLogicLayout(parentLayout)) {
512
- if (!targetComponent.node.origin.isRoot) {
513
- /**
514
- * 计算逻辑:
515
- * 1. 移动到左侧:当前节点 startX - 偏移值,偏移值计算如下:
516
- * a. 第一个节点: 固定值(来源于 getMainAxle,第二级节点:BASE * 8,其他 BASE * 3 + strokeWidth / 2);
517
- * b. 第二个节点到最后一个节点之间:上一个节点到当前节点间距的一半((当前节点 startX - 上一个节点的 endX) / 2),endX = 当前节点的 startX + width;
518
- * 2. 移动到右侧:当前节点 x + width + 偏移值,偏移值计算如下:
519
- * a. 第二个节点到最后一个节点之间的右侧:当前节点到下一个节点间距的一半((下一个节点 startX - 当前节点的 endX) / 2),endX = 当前节点的 startX + width;
520
- * b. 最后一个节点的右侧:固定值(来源于 getMainAxle,第二级节点:BASE * 8,其他 BASE * 3 + strokeWidth / 2);
521
- */
522
- fakeY = targetComponent.node.y;
523
- const parentComponent = PlaitElement.getComponent(targetComponent.parent.origin);
524
- const targetIndex = parentComponent.node.origin.children.indexOf(targetComponent.node.origin);
525
- if (detectResult === 'left') {
526
- let offsetX = 0;
527
- const isFirstNode = targetIndex === 0;
528
- if (isFirstNode) {
529
- offsetX = parentComponent.node.origin.isRoot ? BASE * 8 : BASE * 3 + strokeWidth / 2;
530
- }
531
- else {
532
- const previousComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex - 1]);
533
- const previousRect = getRectangleByNode(previousComponent.node);
534
- const space = targetRect.x - (previousRect.x + previousRect.width);
535
- offsetX = space / 2;
536
- }
537
- fakeX = targetRect.x - offsetX - width / 2 - Math.ceil(strokeWidth / 2);
538
- }
539
- if (detectResult === 'right') {
540
- let offsetX = 0;
541
- const isLastNode = targetIndex === parentComponent.node.origin.children.length - 1;
542
- if (isLastNode) {
543
- offsetX = parentComponent.node.origin.isRoot ? BASE * 8 : BASE * 3 + strokeWidth / 2;
544
- }
545
- else {
546
- const nextComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex + 1]);
547
- const nextRect = getRectangleByNode(nextComponent.node);
548
- const space = nextRect.x - (targetRect.x + targetRect.width);
549
- offsetX = space / 2;
691
+ if (options.fill) {
692
+ newElement.fill = options.fill;
693
+ }
694
+ if (options.strokeColor) {
695
+ newElement.strokeColor = options.strokeColor;
696
+ }
697
+ if (!isNullOrUndefined(options.strokeWidth)) {
698
+ newElement.strokeWidth = options.strokeWidth;
699
+ }
700
+ if (options.shape) {
701
+ newElement.shape = options.shape;
702
+ }
703
+ if (options.layout) {
704
+ newElement.layout = options.layout;
705
+ }
706
+ if (options.branchColor) {
707
+ newElement.branchColor = options.branchColor;
708
+ }
709
+ return newElement;
710
+ };
711
+ // layoutLevel 用来表示插入兄弟节点还是子节点
712
+ const insertMindElement = (board, inheritNode, path) => {
713
+ let fill, strokeColor, strokeWidth, shape = MindNodeShape.roundRectangle;
714
+ if (!inheritNode.isRoot) {
715
+ fill = inheritNode.fill;
716
+ strokeColor = inheritNode.strokeColor;
717
+ strokeWidth = inheritNode.strokeWidth;
718
+ }
719
+ shape = inheritNode.shape;
720
+ const newElement = createMindElement('', NODE_MIN_WIDTH, TEXT_DEFAULT_HEIGHT, { fill, strokeColor, strokeWidth, shape });
721
+ Transforms.insertNode(board, newElement, path);
722
+ clearSelectedElement(board);
723
+ addSelectedElement(board, newElement);
724
+ setTimeout(() => {
725
+ enterNodeEditing(newElement);
726
+ });
727
+ };
728
+ const findLastChild = (child) => {
729
+ let result = child;
730
+ while (result.children.length !== 0) {
731
+ result = result.children[result.children.length - 1];
732
+ }
733
+ return result;
734
+ };
735
+ const deleteSelectedELements = (board, selectedElements) => {
736
+ //翻转,从下到上修改,防止找不到 path
737
+ const deletableElements = filterChildElement(selectedElements).reverse();
738
+ const relativeAbstracts = [];
739
+ const accumulativeProperties = new WeakMap();
740
+ deletableElements.forEach(node => {
741
+ if (!PlaitMind.isMind(node)) {
742
+ const behindAbstracts = getBehindAbstracts(node).filter(abstract => !deletableElements.includes(abstract));
743
+ if (behindAbstracts.length) {
744
+ behindAbstracts.forEach(abstract => {
745
+ let newProperties = accumulativeProperties.get(abstract);
746
+ if (!newProperties) {
747
+ newProperties = { start: abstract.start, end: abstract.end };
748
+ accumulativeProperties.set(abstract, newProperties);
749
+ relativeAbstracts.push(abstract);
550
750
  }
551
- fakeX = targetRect.x + width + offsetX - width / 2 - Math.ceil(strokeWidth / 2);
751
+ newProperties.start = newProperties.start - 1;
752
+ newProperties.end = newProperties.end - 1;
753
+ });
754
+ }
755
+ const correspondingAbstract = getCorrespondingAbstract(node);
756
+ if (correspondingAbstract && !deletableElements.includes(correspondingAbstract)) {
757
+ let newProperties = accumulativeProperties.get(correspondingAbstract);
758
+ if (!newProperties) {
759
+ newProperties = { start: correspondingAbstract.start, end: correspondingAbstract.end };
760
+ accumulativeProperties.set(correspondingAbstract, newProperties);
761
+ relativeAbstracts.push(correspondingAbstract);
552
762
  }
553
- startRectanglePointX = fakeX;
554
- if (isTopLayout(parentLayout)) {
555
- // 因为矩形是从左上角为起点向下画的,所以需要向上偏移一个矩形的高度(-12)
556
- startRectanglePointY = fakeY + height + targetComponent.node.vGap - 12;
763
+ newProperties.end = newProperties.end - 1;
764
+ }
765
+ }
766
+ });
767
+ const abstractHandles = relativeAbstracts.map(value => {
768
+ const newProperties = accumulativeProperties.get(value);
769
+ if (newProperties) {
770
+ const path = PlaitBoard.findPath(board, value);
771
+ return () => {
772
+ if (newProperties.start > newProperties.end) {
773
+ Transforms.removeNode(board, path);
557
774
  }
558
- if (isBottomLayout(parentLayout)) {
559
- startRectanglePointY = fakeY + targetComponent.node.vGap;
775
+ else {
776
+ Transforms.setNode(board, newProperties, path);
560
777
  }
561
- endRectanglePointX = startRectanglePointX + 30;
562
- endRectanglePointY = startRectanglePointY + 12;
563
- const fakeNode = { ...targetComponent.node, x: fakeX, y: fakeY, width: 30 };
564
- const linkSVGG = drawLink(roughSVG, parentComponent.node, fakeNode, PRIMARY_COLOR, false, false);
565
- fakeDropNodeG?.appendChild(linkSVGG);
778
+ };
779
+ }
780
+ return () => { };
781
+ });
782
+ const deletableHandles = deletableElements.map(node => {
783
+ const path = PlaitBoard.findPath(board, node);
784
+ return () => {
785
+ if (shouldChangeRightNodeCount(node)) {
786
+ changeRightNodeCount(board, path.slice(0, path.length - 1), -1);
566
787
  }
788
+ Transforms.removeNode(board, path);
789
+ };
790
+ });
791
+ abstractHandles.forEach(action => action());
792
+ deletableHandles.forEach(action => action());
793
+ };
794
+ const divideElementByParent = (elements) => {
795
+ const abstractIncludedGroups = [];
796
+ const parentElements = [];
797
+ for (let i = 0; i < elements.length; i++) {
798
+ const parent = MindElement.getParent(elements[i]);
799
+ const parentIndex = parentElements.indexOf(parent);
800
+ if (parentIndex === -1) {
801
+ parentElements.push(parent);
802
+ abstractIncludedGroups.push([elements[i]]);
567
803
  }
568
804
  else {
569
- let linkSVGG = roughSVG.linearPath(linePoints, { stroke: PRIMARY_COLOR, strokeWidth });
570
- fakeDropNodeG?.appendChild(linkSVGG);
571
- }
572
- // 构造一个矩形框坐标
573
- let fakeRectangleG = drawRoundRectangle(roughSVG, startRectanglePointX, startRectanglePointY, endRectanglePointX, endRectanglePointY, {
574
- stroke: PRIMARY_COLOR,
575
- strokeWidth: 2,
576
- fill: PRIMARY_COLOR,
577
- fillStyle: 'solid'
578
- });
579
- fakeDropNodeG?.appendChild(fakeRectangleG);
580
- }
581
- else {
582
- // 混合布局画线逻辑
583
- if (isHorizontalLogicLayout(parentLayout)) {
584
- if (isIndentedLayout(layout)) {
585
- const fakePoint = getIndentedFakePoint(layout, pointOptions);
586
- drawIndentNodeG(fakeDropNodeG, roughSVG, fakePoint, targetComponent.node);
587
- return;
588
- }
805
+ abstractIncludedGroups[parentIndex].push(elements[i]);
589
806
  }
590
807
  }
808
+ return { parentElements, abstractIncludedGroups };
591
809
  };
592
- const getHorizontalFakeY = (detectResult, targetIndex, parentNode, targetRect, layout, fakeY) => {
593
- if (detectResult === 'top') {
594
- if (targetIndex === 0 && isTopLayout(layout)) {
595
- fakeY = targetRect.y + targetRect.height;
596
- }
597
- if (targetIndex > 0) {
598
- const previousComponent = PlaitElement.getComponent(parentNode.origin.children[targetIndex - 1]);
599
- const previousRect = getRectangleByNode(previousComponent.node);
600
- const topY = previousRect.y + previousRect.height;
601
- fakeY = topY + (targetRect.y - topY) / 5;
602
- }
810
+
811
+ const getNodeShapeByElement = (element) => {
812
+ let nodeShape = element.shape;
813
+ if (nodeShape) {
814
+ return nodeShape;
603
815
  }
604
- if (detectResult === 'bottom') {
605
- fakeY = targetRect.y + targetRect.height + 30;
606
- if (targetIndex < parentNode.origin.children.length - 1) {
607
- const nextComponent = PlaitElement.getComponent(parentNode.origin.children[targetIndex + 1]);
608
- const nextRect = getRectangleByNode(nextComponent.node);
609
- const topY = targetRect.y + targetRect.height;
610
- fakeY = topY + (nextRect.y - topY) / 5;
611
- }
612
- if (targetIndex === parentNode.origin.children.length - 1) {
613
- fakeY = targetRect.y + targetRect.height + 30;
816
+ let parent = findParentElement(element);
817
+ while (parent) {
818
+ if (parent.shape) {
819
+ return parent.shape;
614
820
  }
821
+ parent = findParentElement(parent);
615
822
  }
616
- return fakeY;
823
+ return MindNodeShape.roundRectangle;
617
824
  };
618
- const getIndentedFakePoint = (layout, pointOptions) => {
619
- let { fakeX, fakeY, x, y, width, height, strokeWidth } = pointOptions;
620
- const hGap = BASE * 4;
621
- const vGap = BASE * 6;
622
- const offsetX = hGap + width / 2 + strokeWidth;
623
- const offsetY = vGap + height / 2 + strokeWidth;
624
- if (isLeftLayout(layout)) {
625
- fakeX = x - offsetX;
825
+
826
+ const getBranchColorByMindElement = (board, element) => {
827
+ const ancestors = MindElement.getAncestors(board, element);
828
+ ancestors.unshift(element);
829
+ const ancestor = ancestors.find(value => value.branchColor);
830
+ if (ancestor && ancestor.branchColor) {
831
+ return ancestor.branchColor;
832
+ }
833
+ const root = ancestors[ancestors.length - 1];
834
+ const branch = ancestors[ancestors.length - 2];
835
+ if (branch) {
836
+ const index = root.children.indexOf(branch);
837
+ const length = COLORS.length;
838
+ const remainder = index % length;
839
+ return COLORS[remainder];
626
840
  }
627
- if (isRightLayout(layout)) {
628
- fakeX = x + offsetX;
841
+ else {
842
+ throw new Error('root element should not have branch color');
629
843
  }
630
- if (isTopLayout(layout)) {
631
- fakeY = y - offsetY;
844
+ };
845
+ const getNextBranchColor = (root) => {
846
+ const index = root.children.length;
847
+ const length = COLORS.length;
848
+ const remainder = index % length;
849
+ return COLORS[remainder];
850
+ };
851
+
852
+ const getStrokeByMindElement = (board, element) => {
853
+ const ancestors = MindElement.getAncestors(board, element);
854
+ ancestors.unshift(element);
855
+ const ancestor = ancestors.find(value => value.strokeColor);
856
+ if (ancestor && ancestor.strokeColor) {
857
+ return ancestor.strokeColor;
858
+ }
859
+ const root = ancestors[ancestors.length - 1];
860
+ const branch = ancestors[ancestors.length - 2];
861
+ if (branch) {
862
+ const index = root.children.indexOf(branch);
863
+ const length = COLORS.length;
864
+ const remainder = index % length;
865
+ return COLORS[remainder];
632
866
  }
633
- if (isBottomLayout(layout)) {
634
- fakeY = y + height + offsetY;
867
+ else {
868
+ return ROOT_NODE_STROKE;
635
869
  }
636
- return { fakeX, fakeY };
637
- };
638
- const drawIndentNodeG = (fakeDropNodeG, roughSVG, fakePoint, node) => {
639
- const { fakeX, fakeY } = fakePoint;
640
- const fakeNode = { ...node, x: fakeX, y: fakeY, width: 30, height: 12 };
641
- const linkSVGG = drawIndentedLink(roughSVG, node, fakeNode, PRIMARY_COLOR, false);
642
- const startRectanglePointX = fakeX, startRectanglePointY = fakeY, endRectanglePointX = fakeX + 30, endRectanglePointY = fakeY + 12;
643
- const fakeRectangleG = drawRoundRectangle(roughSVG, startRectanglePointX, startRectanglePointY, endRectanglePointX, endRectanglePointY, {
644
- stroke: PRIMARY_COLOR,
645
- strokeWidth: 2,
646
- fill: PRIMARY_COLOR,
647
- fillStyle: 'solid'
648
- });
649
- fakeDropNodeG?.appendChild(linkSVGG);
650
- fakeDropNodeG?.appendChild(fakeRectangleG);
651
870
  };
652
871
 
653
- /**
654
- *
655
- * @param targetNode
656
- * @param centerPoint
657
- * @returns DetectResult[] | null
658
- */
659
- const directionDetector = (targetNode, centerPoint) => {
660
- const { x, y, width, height } = getRectangleByNode(targetNode);
661
- const yCenter = y + height / 2;
662
- const xCenter = x + width / 2;
663
- const top = targetNode.y;
664
- const bottom = targetNode.y + targetNode.height;
665
- const left = targetNode.x;
666
- const right = targetNode.x + targetNode.width;
667
- const direction = [];
668
- // x
669
- if (centerPoint[1] > y && centerPoint[1] < y + height) {
670
- if (centerPoint[0] > left && centerPoint[0] < xCenter) {
671
- direction.push('left');
672
- }
673
- if (centerPoint[0] > xCenter && centerPoint[0] < right) {
674
- direction.push('right');
872
+ function isVirtualKey(e) {
873
+ const isMod = e.ctrlKey || e.metaKey;
874
+ const isAlt = isKeyHotkey('alt', e);
875
+ const isShift = isKeyHotkey('shift', e);
876
+ const isCapsLock = e.key.includes('CapsLock');
877
+ const isTab = e.key.includes('Tab');
878
+ const isEsc = e.key.includes('Escape');
879
+ const isF = e.key.startsWith('F');
880
+ const isArrow = e.key.includes('Arrow') ? true : false;
881
+ return isCapsLock || isMod || isAlt || isArrow || isShift || isTab || isEsc || isF;
882
+ }
883
+
884
+ function drawLink(board, node, child, defaultStroke = null, isHorizontal = true, needDrawUnderline = true) {
885
+ let beginX, beginY, endX, endY, beginNode = node, endNode = child;
886
+ const layout = MindQueries.getCorrectLayoutByElement(node.origin);
887
+ if (isHorizontal) {
888
+ if (!isChildRight(node, child)) {
889
+ beginNode = child;
890
+ endNode = node;
675
891
  }
676
- // 重合区域,返回两个方向
677
- if ((centerPoint[0] > x && centerPoint[0] < xCenter) || (centerPoint[0] > xCenter && centerPoint[0] < x + width)) {
678
- if (centerPoint[1] < yCenter) {
679
- direction.push('top');
892
+ beginX = beginNode.x + beginNode.width - beginNode.hGap;
893
+ beginY = beginNode.y + beginNode.height / 2;
894
+ endX = endNode.x + endNode.hGap;
895
+ endY = endNode.y + endNode.height / 2;
896
+ if (node.parent &&
897
+ isIndentedLayout(MindQueries.getLayoutByElement(node.parent?.origin)) &&
898
+ getNodeShapeByElement(node.origin) === MindNodeShape.underline) {
899
+ if (isChildRight(node, child)) {
900
+ beginY = node.y + node.height - node.vGap;
680
901
  }
681
902
  else {
682
- direction.push('bottom');
903
+ endY = node.y + node.height - node.vGap;
683
904
  }
684
905
  }
685
- return direction.length ? direction : null;
686
906
  }
687
- // y 轴
688
- if (centerPoint[0] > x && centerPoint[0] < x + width) {
689
- if (centerPoint[1] > top && centerPoint[1] < yCenter) {
690
- direction.push('top');
691
- }
692
- if (centerPoint[1] > yCenter && centerPoint[1] < bottom) {
693
- direction.push('bottom');
694
- }
695
- if ((centerPoint[1] > y && centerPoint[1] < y + height) || (centerPoint[1] > yCenter && centerPoint[1] < y + height)) {
696
- if (centerPoint[0] < xCenter) {
697
- direction.push('left');
698
- }
699
- else {
700
- direction.push('right');
701
- }
907
+ else {
908
+ if (node.y > child.y) {
909
+ beginNode = child;
910
+ endNode = node;
702
911
  }
703
- return direction.length ? direction : null;
912
+ beginX = beginNode.x + beginNode.width / 2;
913
+ beginY = beginNode.y + beginNode.height - beginNode.vGap;
914
+ endX = endNode.x + endNode.width / 2;
915
+ endY = endNode.y + endNode.vGap;
704
916
  }
705
- return null;
706
- };
707
-
708
- const directionCorrector = (node, detectResults) => {
709
- if (!node.origin.isRoot) {
710
- const parentlayout = MindQueries.getCorrectLayoutByElement(node?.parent.origin);
711
- if (isStandardLayout(parentlayout)) {
712
- const idx = node.parent.children.findIndex(x => x === node);
713
- const isLeft = idx >= (node.parent.origin.rightNodeCount || 0);
714
- return getAllowedDirection(detectResults, [isLeft ? 'right' : 'left']);
715
- }
716
- if (isLeftLayout(parentlayout)) {
717
- return getAllowedDirection(detectResults, ['right']);
718
- }
719
- if (isRightLayout(parentlayout)) {
720
- return getAllowedDirection(detectResults, ['left']);
721
- }
722
- if (parentlayout === MindLayoutType.upward) {
723
- return getAllowedDirection(detectResults, ['bottom']);
917
+ const stroke = defaultStroke || getBranchColorByMindElement(board, child.origin);
918
+ const strokeWidth = child.origin.branchWidth ? child.origin.branchWidth : STROKE_WIDTH;
919
+ if (endNode.origin.isRoot) {
920
+ if (layout === MindLayoutType.left || isStandardLayout(layout)) {
921
+ endX -= strokeWidth;
724
922
  }
725
- if (parentlayout === MindLayoutType.downward) {
726
- return getAllowedDirection(detectResults, ['top']);
923
+ if (layout === MindLayoutType.upward) {
924
+ endY -= strokeWidth;
727
925
  }
728
926
  }
729
- else {
730
- const layout = MindQueries.getCorrectLayoutByElement(node?.origin);
731
- if (isStandardLayout(layout)) {
732
- return getAllowedDirection(detectResults, ['top', 'bottom']);
733
- }
734
- if (isTopLayout(layout)) {
735
- return getAllowedDirection(detectResults, ['left', 'right', 'bottom']);
736
- }
737
- if (isBottomLayout(layout)) {
738
- return getAllowedDirection(detectResults, ['left', 'right', 'top']);
739
- }
740
- if (layout === MindLayoutType.left) {
741
- return getAllowedDirection(detectResults, ['right', 'top', 'bottom']);
927
+ if (beginNode.origin.isRoot) {
928
+ if (layout === MindLayoutType.right || isStandardLayout(layout)) {
929
+ beginX += strokeWidth;
742
930
  }
743
- if (layout === MindLayoutType.right) {
744
- return getAllowedDirection(detectResults, ['left', 'top', 'bottom']);
931
+ if (layout === MindLayoutType.downward) {
932
+ beginY += strokeWidth;
745
933
  }
746
934
  }
747
- return null;
748
- };
749
- const getAllowedDirection = (detectResults, illegalDirections) => {
750
- const directions = detectResults;
751
- illegalDirections.forEach(item => {
752
- const bottomDirectionIndex = directions.findIndex(direction => direction === item);
753
- if (bottomDirectionIndex !== -1) {
754
- directions.splice(bottomDirectionIndex, 1);
755
- }
756
- });
757
- return directions.length ? directions : null;
758
- };
759
-
760
- /* 根据布局调整 target 以及 direction */
761
- const readjustmentDropTarget = (dropTarget) => {
762
- const { target, detectResult } = dropTarget;
763
- const newDropTarget = { target, detectResult };
764
- const targetComponent = PlaitElement.getComponent(target);
765
- if (targetComponent.node.children.length > 0 && dropTarget.detectResult) {
766
- const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
767
- const parentLayout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
768
- if (['right', 'left'].includes(dropTarget.detectResult)) {
769
- if (!isMixedLayout(parentLayout, layout)) {
770
- if (targetComponent.node.origin.isRoot) {
771
- const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
772
- // 标准布局,根节点
773
- if (isStandardLayout(layout)) {
774
- const rightNodeCount = targetComponent.node.origin.rightNodeCount;
775
- if (detectResult === 'left') {
776
- // 作为左的第一个节点
777
- if (targetComponent.node.children.length === rightNodeCount) {
778
- return newDropTarget;
779
- }
780
- }
781
- else {
782
- // 作为右的第一个节点或最后一个节点
783
- if (rightNodeCount === 0) {
784
- newDropTarget.target = target;
785
- }
786
- else {
787
- newDropTarget.target = targetComponent.node.children[rightNodeCount - 1].origin;
788
- newDropTarget.detectResult = 'bottom';
789
- }
790
- return newDropTarget;
791
- }
792
- }
793
- }
794
- // 缩进布局探测到第一个子节点
795
- if (isIndentedLayout(parentLayout)) {
796
- newDropTarget.target = targetComponent.node.children[0].origin;
797
- newDropTarget.detectResult = isTopLayout(parentLayout) ? 'bottom' : 'top';
798
- return newDropTarget;
799
- }
800
- // 上下布局的根节点只可以探测到上或者下,子节点的左右探测不处理,跳过。
801
- if (isVerticalLogicLayout(parentLayout)) {
802
- return newDropTarget;
803
- }
804
- // 剩下是水平布局的默认情况:插入最后一个子节点的下方
805
- const lastChildNodeIndex = targetComponent.node.children.length - 1;
806
- newDropTarget.target = targetComponent.node.children[lastChildNodeIndex].origin;
807
- newDropTarget.detectResult = 'bottom';
935
+ if (isHorizontal) {
936
+ let curve = [
937
+ [beginX, beginY],
938
+ [beginX + (beginNode.hGap + endNode.hGap) / 3, beginY],
939
+ [endX - (beginNode.hGap + endNode.hGap) / 2, endY],
940
+ [endX, endY]
941
+ ];
942
+ const shape = getNodeShapeByElement(child.origin);
943
+ if (!node.origin.isRoot) {
944
+ if (node.x > child.x) {
945
+ curve = [
946
+ [beginX, beginY],
947
+ [beginX + (beginNode.hGap + endNode.hGap) / 3, beginY],
948
+ [endX - (beginNode.hGap + endNode.hGap) / 2, endY],
949
+ [endX - 12, endY]
950
+ ];
951
+ const line = [
952
+ [endX - 12, endY],
953
+ [endX - 12, endY],
954
+ [endX, endY]
955
+ ];
956
+ curve = [...curve, ...line];
808
957
  }
809
958
  else {
810
- // 处理左右布局下的混合布局
811
- if ([MindLayoutType.left, MindLayoutType.right].includes(parentLayout)) {
812
- const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
813
- if (isIndentedLayout(layout)) {
814
- newDropTarget.target = targetComponent.node.children[0].origin;
815
- newDropTarget.detectResult = isTopLayout(layout) ? 'bottom' : 'top';
816
- return newDropTarget;
817
- }
818
- }
959
+ curve = [
960
+ [beginX + 12, beginY],
961
+ [beginX + (beginNode.hGap + endNode.hGap) / 2, beginY],
962
+ [endX - (beginNode.hGap + endNode.hGap) / 3, endY],
963
+ [endX, endY]
964
+ ];
965
+ const line = [
966
+ [beginX, beginY],
967
+ [beginX + 12, beginY],
968
+ [beginX + 12, beginY]
969
+ ];
970
+ curve = [...line, ...curve];
819
971
  }
820
972
  }
821
- if (['top', 'bottom'].includes(dropTarget.detectResult)) {
822
- // 缩进布局移动至第一个节点
823
- if (targetComponent.node.origin.isRoot && isIndentedLayout(layout)) {
824
- newDropTarget.target = targetComponent.node.children[0].origin;
825
- newDropTarget.detectResult = isTopLayout(layout) ? 'bottom' : 'top';
826
- return newDropTarget;
973
+ if (needDrawUnderline && shape === MindNodeShape.underline) {
974
+ if (child.left) {
975
+ const underline = [
976
+ [beginX - (beginNode.width - beginNode.hGap * 2), beginY],
977
+ [beginX - (beginNode.width - beginNode.hGap * 2), beginY],
978
+ [beginX - (beginNode.width - beginNode.hGap * 2), beginY]
979
+ ];
980
+ curve = [...underline, ...curve];
827
981
  }
828
- // 上下布局,插到右边
829
- const parentLayout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
830
- if (isVerticalLogicLayout(parentLayout)) {
831
- const lastChildNodeIndex = targetComponent.node.children.length - 1;
832
- newDropTarget.target = targetComponent.node.children[lastChildNodeIndex].origin;
833
- newDropTarget.detectResult = 'right';
834
- return newDropTarget;
982
+ else {
983
+ const underline = [
984
+ [endX + (endNode.width - endNode.hGap * 2), endY],
985
+ [endX + (endNode.width - endNode.hGap * 2), endY],
986
+ [endX + (endNode.width - endNode.hGap * 2), endY]
987
+ ];
988
+ curve = [...curve, ...underline];
835
989
  }
836
990
  }
837
- return newDropTarget;
838
- }
839
- return dropTarget;
840
- };
841
-
842
- const separateChildren = (parentElement) => {
843
- const rightNodeCount = parentElement.rightNodeCount;
844
- const children = parentElement.children;
845
- let rightChildren = [], leftChildren = [];
846
- for (let i = 0; i < children.length; i++) {
847
- const child = children[i];
848
- if (AbstractNode.isAbstract(child) && child.end < rightNodeCount) {
849
- rightChildren.push(child);
850
- continue;
851
- }
852
- if (AbstractNode.isAbstract(child) && child.start >= rightNodeCount) {
853
- leftChildren.push(child);
854
- continue;
855
- }
856
- if (i < rightNodeCount) {
857
- rightChildren.push(child);
858
- }
859
- else {
860
- leftChildren.push(child);
861
- }
862
- }
863
- return { leftChildren, rightChildren };
864
- };
865
- const isSetAbstract = (element) => {
866
- const component = PlaitElement.getComponent(element);
867
- const parent = component.parent;
868
- if (!parent)
869
- return false;
870
- const elementIndex = parent.children.indexOf(component.node);
871
- return parent.children.some(child => {
872
- return AbstractNode.isAbstract(child.origin) && elementIndex >= child.origin.start && elementIndex <= child.origin.end;
873
- });
874
- };
875
- const canSetAbstract = (element) => {
876
- return !PlaitElement.isRootElement(element) && !AbstractNode.isAbstract(element) && !isSetAbstract(element);
877
- };
878
- const setAbstract = (board, elements) => {
879
- let elementGroup = filterChildElement(elements);
880
- const { parentElements, abstractIncludedGroups } = divideElementByParent(elementGroup);
881
- abstractIncludedGroups.forEach((group, index) => {
882
- const groupParent = parentElements[index];
883
- setAbstractByElements(board, groupParent, group);
884
- });
885
- };
886
- const setAbstractByElements = (board, groupParent, group) => {
887
- const indexArray = group.map(child => groupParent.children.indexOf(child)).sort((a, b) => a - b);
888
- const rightNodeCount = groupParent?.rightNodeCount;
889
- const start = indexArray[0], end = indexArray[indexArray.length - 1];
890
- if (isStandardLayout(MindQueries.getLayoutByElement(groupParent)) &&
891
- rightNodeCount &&
892
- start < rightNodeCount &&
893
- end >= rightNodeCount) {
894
- const childrenLength = groupParent.children.length;
895
- const path = [...PlaitBoard.findPath(board, groupParent), childrenLength];
896
- const leftChildren = indexArray.filter(index => index >= rightNodeCount);
897
- const rightCHildren = indexArray.filter(index => index < rightNodeCount);
898
- insetAbstractNode(board, path, rightCHildren[0], rightCHildren[rightCHildren.length - 1]);
899
- insetAbstractNode(board, Path.next(path), leftChildren[0], leftChildren[leftChildren.length - 1]);
991
+ const points = pointsOnBezierCurves(curve);
992
+ return PlaitBoard.getRoughSVG(board).curve(points, { stroke, strokeWidth });
900
993
  }
901
994
  else {
902
- const path = [...PlaitBoard.findPath(board, groupParent), groupParent.children.length];
903
- insetAbstractNode(board, path, start, end);
995
+ let curve = [
996
+ [beginX, beginY],
997
+ [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
998
+ [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
999
+ [endX, endY]
1000
+ ];
1001
+ if (!node.origin.isRoot) {
1002
+ if (isTopLayout(layout)) {
1003
+ curve = [
1004
+ [beginX, beginY],
1005
+ [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
1006
+ [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
1007
+ [endX, endY - 12]
1008
+ ];
1009
+ const line = [
1010
+ [endX, endY - 12],
1011
+ [endX, endY - 12],
1012
+ [endX, endY]
1013
+ ];
1014
+ curve = [...curve, ...line];
1015
+ }
1016
+ else {
1017
+ curve = [
1018
+ [beginX, beginY + 12],
1019
+ [beginX, beginY + (beginNode.vGap + endNode.vGap) / 2],
1020
+ [endX, endY - (beginNode.vGap + endNode.vGap) / 2],
1021
+ [endX, endY]
1022
+ ];
1023
+ const line = [
1024
+ [beginX, beginY],
1025
+ [beginX, beginY + 12],
1026
+ [beginX, beginY + 12]
1027
+ ];
1028
+ curve = [...line, ...curve];
1029
+ }
1030
+ }
1031
+ const points = pointsOnBezierCurves(curve);
1032
+ return PlaitBoard.getRoughSVG(board).curve(points, { stroke, strokeWidth });
904
1033
  }
905
- };
906
- const insetAbstractNode = (board, path, start, end) => {
907
- const mindElement = createMindElement('概要', 28, 20, {
908
- strokeColor: GRAY_COLOR,
909
- linkLineColor: GRAY_COLOR
910
- });
911
- mindElement.start = start;
912
- mindElement.end = end;
913
- Transforms.insertNode(board, mindElement, path);
914
- };
1034
+ }
915
1035
 
916
- const getRectangleByResizingLocation = (abstractRectangle, location, activeHandlePosition, isHorizontal) => {
917
- if (isHorizontal) {
918
- if (activeHandlePosition === AbstractHandlePosition.start) {
919
- return {
920
- ...abstractRectangle,
921
- y: location,
922
- height: abstractRectangle.height + abstractRectangle.y - location
923
- };
924
- }
925
- else {
926
- return {
927
- ...abstractRectangle,
928
- height: location - abstractRectangle.y
929
- };
930
- }
1036
+ const drawPlaceholderDropNodeG = (board, dropTarget, fakeDropNodeG) => {
1037
+ const targetComponent = PlaitElement.getComponent(dropTarget.target);
1038
+ const targetRect = getRectangleByNode(targetComponent.node);
1039
+ if (dropTarget.detectResult && ['right', 'left'].includes(dropTarget.detectResult)) {
1040
+ drawStraightDropNodeG(board, targetRect, dropTarget.detectResult, targetComponent, fakeDropNodeG);
931
1041
  }
932
- else {
933
- if (activeHandlePosition === AbstractHandlePosition.start) {
934
- return {
935
- ...abstractRectangle,
936
- x: location,
937
- width: abstractRectangle.width + abstractRectangle.x - location
938
- };
939
- }
940
- else {
941
- return {
942
- ...abstractRectangle,
943
- width: location - abstractRectangle.x
944
- };
945
- }
1042
+ if (targetComponent.parent && dropTarget.detectResult && ['top', 'bottom'].includes(dropTarget.detectResult)) {
1043
+ const parentComponent = PlaitElement.getComponent(targetComponent.parent.origin);
1044
+ const targetIndex = parentComponent.node.origin.children.indexOf(targetComponent.node.origin);
1045
+ drawCurvePlaceholderDropNodeG(board, targetRect, dropTarget.detectResult, targetIndex, targetComponent, parentComponent, fakeDropNodeG);
946
1046
  }
947
1047
  };
948
- const getLocationScope = (board, handlePosition, parentChildren, element, parent, isHorizontal) => {
949
- const node = MindElement.getNode(element);
950
- const { start, end } = getCorrectStartEnd(node.origin, parent);
951
- const startNode = parentChildren[start];
952
- const endNode = parentChildren[end];
953
- if (handlePosition === AbstractHandlePosition.start) {
954
- const abstractNode = parentChildren.filter(child => AbstractNode.isAbstract(child) && child.end < element.start);
955
- let minNode;
956
- if (abstractNode.length) {
957
- const index = abstractNode
958
- .map(node => {
959
- const { end } = getCorrectStartEnd(node, parent);
960
- return end;
961
- })
962
- .sort((a, b) => b - a)[0];
963
- minNode = parentChildren[index + 1];
964
- }
965
- else {
966
- minNode = parentChildren[0];
1048
+ const drawCurvePlaceholderDropNodeG = (board, targetRect, detectResult, targetIndex, targetComponent, parentComponent, fakeDropNodeG) => {
1049
+ const parentNodeLayout = MindQueries.getCorrectLayoutByElement(parentComponent.node.origin);
1050
+ const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.parent.origin);
1051
+ const strokeWidth = targetComponent.node.origin.branchWidth ? targetComponent.node.origin.branchWidth : STROKE_WIDTH;
1052
+ let fakeX = targetComponent.node.x, fakeY = targetRect.y - 30, fakeRectangleStartX = targetRect.x, fakeRectangleEndX = targetRect.x + 30, fakeRectangleStartY = fakeY, fakeRectangleEndY = fakeRectangleStartY + 12, width = 30;
1053
+ if (isLeftLayout(layout)) {
1054
+ fakeX = targetComponent.node.x + targetComponent.node.width - 30;
1055
+ fakeRectangleStartX = targetRect.x + targetRect.width - 30;
1056
+ fakeRectangleEndX = targetRect.x + targetRect.width;
1057
+ }
1058
+ if (isHorizontalLogicLayout(parentNodeLayout)) {
1059
+ fakeY = getHorizontalFakeY(detectResult, targetIndex, parentComponent.node, targetRect, layout, fakeY);
1060
+ if (isStandardLayout(parentNodeLayout)) {
1061
+ const rightNodeCount = parentComponent.node.origin.rightNodeCount || 0;
1062
+ const idx = parentComponent.node.children.findIndex(x => x === targetComponent.node);
1063
+ const isLeft = idx >= rightNodeCount;
1064
+ // 标准布局的左,需要调整 x
1065
+ if (isLeft) {
1066
+ fakeX = targetComponent.node.x + targetComponent.node.width - 30;
1067
+ fakeRectangleStartX = targetRect.x + targetRect.width - 30;
1068
+ fakeRectangleEndX = targetRect.x + targetRect.width;
1069
+ }
1070
+ const isLeftFirst = idx === rightNodeCount;
1071
+ const isRightLast = idx === rightNodeCount - 1;
1072
+ // 拖拽至左第一个节点的情况
1073
+ if (detectResult === 'top' && isLeftFirst) {
1074
+ fakeY = targetRect.y - targetRect.height;
1075
+ }
1076
+ if (detectResult === 'bottom' && isRightLast) {
1077
+ fakeY = targetRect.y + targetRect.height + 30;
1078
+ }
967
1079
  }
968
- const minNodeRectangle = getRectangleByElements(board, [minNode], true);
969
- const endNodeRectangle = getRectangleByElements(board, [endNode], false);
970
- if (isHorizontal) {
971
- return {
972
- max: endNodeRectangle.y - ABSTRACT_INCLUDED_OUTLINE_OFFSET,
973
- min: minNodeRectangle.y - ABSTRACT_INCLUDED_OUTLINE_OFFSET
974
- };
1080
+ fakeRectangleStartY = fakeY;
1081
+ fakeRectangleEndY = fakeRectangleStartY + 12;
1082
+ }
1083
+ if (isVerticalLogicLayout(layout)) {
1084
+ parentComponent = targetComponent;
1085
+ targetComponent = PlaitElement.getComponent(targetComponent.parent.origin);
1086
+ fakeX = parentComponent.node.x;
1087
+ width = parentComponent.node.width;
1088
+ const vGap = BASE * 6 + strokeWidth;
1089
+ if (isTopLayout(layout) && detectResult === 'top') {
1090
+ fakeY = targetRect.y - vGap;
1091
+ fakeRectangleStartY = fakeY - vGap + strokeWidth;
975
1092
  }
976
- else {
977
- return {
978
- max: endNodeRectangle.x - ABSTRACT_INCLUDED_OUTLINE_OFFSET,
979
- min: minNodeRectangle.x - ABSTRACT_INCLUDED_OUTLINE_OFFSET
980
- };
1093
+ if (isBottomLayout(layout) && detectResult === 'bottom') {
1094
+ fakeY = targetRect.y + targetRect.height + vGap;
1095
+ fakeRectangleStartY = fakeY + vGap - strokeWidth;
981
1096
  }
1097
+ fakeRectangleStartX = fakeX + Math.ceil(parentComponent.node.width / 2) - parentComponent.node.hGap - Math.ceil(strokeWidth / 2);
1098
+ fakeRectangleEndX = fakeRectangleStartX + 30;
1099
+ fakeRectangleEndY = fakeRectangleStartY + 12;
982
1100
  }
983
- else {
984
- const abstractNode = parentChildren.filter(child => AbstractNode.isAbstract(child) && child.start > element.end);
985
- let maxNode;
986
- if (abstractNode.length) {
987
- const index = abstractNode
988
- .map(node => {
989
- const { start } = getCorrectStartEnd(node, parent);
990
- return start;
991
- })
992
- .sort((a, b) => a - b)[0];
993
- maxNode = parentChildren[index - 1];
994
- }
995
- else {
996
- const children = parentChildren.filter(child => !AbstractNode.isAbstract(child));
997
- maxNode = parentChildren[children.length - 1];
1101
+ if (isIndentedLayout(layout)) {
1102
+ // 偏移一个 Gap
1103
+ if (isLeftLayout(layout)) {
1104
+ fakeX -= BASE * 4;
998
1105
  }
999
- const maxNodeRectangle = getRectangleByElements(board, [maxNode], true);
1000
- const startNodeRectangle = getRectangleByElements(board, [startNode], false);
1001
- if (isHorizontal) {
1002
- return {
1003
- max: maxNodeRectangle.y + maxNodeRectangle.height + ABSTRACT_INCLUDED_OUTLINE_OFFSET,
1004
- min: startNodeRectangle.y + startNodeRectangle.height + ABSTRACT_INCLUDED_OUTLINE_OFFSET
1005
- };
1106
+ if (isRightLayout(layout)) {
1107
+ fakeX += BASE * 4;
1006
1108
  }
1007
- else {
1008
- return {
1009
- max: maxNodeRectangle.x + maxNodeRectangle.width + ABSTRACT_INCLUDED_OUTLINE_OFFSET,
1010
- min: startNodeRectangle.x + startNodeRectangle.width + ABSTRACT_INCLUDED_OUTLINE_OFFSET
1011
- };
1109
+ if (isTopLayout(layout)) {
1110
+ if (detectResult === 'top') {
1111
+ const isLastNode = targetIndex === parentComponent.node.origin.children.length - 1;
1112
+ if (isLastNode) {
1113
+ fakeY = targetRect.y - targetRect.height - BASE;
1114
+ }
1115
+ else {
1116
+ const nextComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex + 1]);
1117
+ const nextRect = getRectangleByNode(nextComponent.node);
1118
+ fakeY = targetRect.y - Math.abs((nextRect.y + nextRect.height - targetRect.y) / 2);
1119
+ }
1120
+ }
1121
+ if (detectResult === 'bottom') {
1122
+ const isFirstNode = targetIndex === 0;
1123
+ if (isFirstNode) {
1124
+ const parentRect = getRectangleByNode(parentComponent.node);
1125
+ fakeY = parentRect.y - parentRect.height / 2 - BASE;
1126
+ }
1127
+ else {
1128
+ const previousComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex + 1]);
1129
+ const previousRect = getRectangleByNode(previousComponent.node);
1130
+ fakeY = previousRect.y - Math.abs((targetRect.y + targetRect.height - previousRect.y) / 2);
1131
+ }
1132
+ }
1012
1133
  }
1134
+ fakeRectangleStartX = fakeX;
1135
+ fakeRectangleEndX = fakeRectangleStartX + 30;
1136
+ fakeRectangleStartY = fakeY;
1137
+ fakeRectangleEndY = fakeRectangleStartY + 12;
1013
1138
  }
1139
+ // 构造一条曲线
1140
+ const fakeNode = { ...targetComponent.node, x: fakeX, y: fakeY, width, height: 12 };
1141
+ const linkSVGG = isIndentedLayout(layout)
1142
+ ? drawIndentedLink(board, parentComponent.node, fakeNode, PRIMARY_COLOR, false)
1143
+ : drawLink(board, parentComponent.node, fakeNode, PRIMARY_COLOR, isHorizontalLayout(layout), false);
1144
+ // 构造一个矩形框坐标
1145
+ const fakeRectangleG = drawRoundRectangle(PlaitBoard.getRoughSVG(board), fakeRectangleStartX, fakeRectangleStartY, fakeRectangleEndX, fakeRectangleEndY, {
1146
+ stroke: PRIMARY_COLOR,
1147
+ strokeWidth: 2,
1148
+ fill: PRIMARY_COLOR,
1149
+ fillStyle: 'solid'
1150
+ });
1151
+ fakeDropNodeG?.appendChild(linkSVGG);
1152
+ fakeDropNodeG?.appendChild(fakeRectangleG);
1014
1153
  };
1015
- const getHitAbstractHandle = (board, element, point) => {
1016
- const nodeLayout = MindQueries.getCorrectLayoutByElement(element);
1017
- const isHorizontal = isHorizontalLayout(nodeLayout);
1018
- const parentElement = MindElement.getParent(element);
1019
- const includedElements = parentElement.children.slice(element.start, element.end + 1);
1020
- let abstractRectangle = getRectangleByElements(board, includedElements, true);
1021
- abstractRectangle = RectangleClient.getOutlineRectangle(abstractRectangle, -ABSTRACT_INCLUDED_OUTLINE_OFFSET);
1022
- const startHandleRec = getAbstractHandleRectangle(abstractRectangle, isHorizontal, AbstractHandlePosition.start);
1023
- const endHandleRec = getAbstractHandleRectangle(abstractRectangle, isHorizontal, AbstractHandlePosition.end);
1024
- const pointRec = RectangleClient.toRectangleClient([point, point]);
1025
- if (RectangleClient.isHit(pointRec, startHandleRec))
1026
- return AbstractHandlePosition.start;
1027
- if (RectangleClient.isHit(pointRec, endHandleRec))
1028
- return AbstractHandlePosition.end;
1029
- return null;
1030
- };
1031
- const getAbstractHandleRectangle = (rectangle, isHorizontal, position) => {
1032
- let result;
1033
- if (position === AbstractHandlePosition.start) {
1034
- const location = isHorizontal ? rectangle.y : rectangle.x;
1035
- result = getRectangleByResizingLocation(rectangle, location + ABSTRACT_HANDLE_MASK_WIDTH / 2, AbstractHandlePosition.end, isHorizontal);
1036
- result = getRectangleByResizingLocation(result, location - ABSTRACT_HANDLE_MASK_WIDTH / 2, position, isHorizontal);
1037
- }
1038
- else {
1039
- const location = isHorizontal ? rectangle.y + rectangle.height : rectangle.x + rectangle.width;
1040
- result = getRectangleByResizingLocation(rectangle, location - ABSTRACT_HANDLE_MASK_WIDTH / 2, AbstractHandlePosition.start, isHorizontal);
1041
- result = getRectangleByResizingLocation(result, location + ABSTRACT_HANDLE_MASK_WIDTH / 2, position, isHorizontal);
1154
+ const drawStraightDropNodeG = (board, targetRect, detectResult, targetComponent, fakeDropNodeG) => {
1155
+ const { x, y, width, height } = targetRect;
1156
+ const lineLength = 40;
1157
+ let startLinePoint = x + width;
1158
+ let endLinePoint = x + width + lineLength;
1159
+ let startRectanglePointX = x + width + lineLength;
1160
+ let endRectanglePointX = x + lineLength + width + 30;
1161
+ let startRectanglePointY = y + height / 2 - 6;
1162
+ let endRectanglePointY = y + height / 2 - 6 + 12;
1163
+ if (detectResult === 'left') {
1164
+ startLinePoint = x - lineLength;
1165
+ endLinePoint = x;
1166
+ startRectanglePointX = x - lineLength - 30;
1167
+ endRectanglePointX = x - lineLength;
1042
1168
  }
1043
- return result;
1044
- };
1045
- function findLocationLeftIndex(board, parentChildren, location, isHorizontal) {
1046
- const children = parentChildren.filter(child => {
1047
- return !AbstractNode.isAbstract(child);
1048
- });
1049
- const recArray = children.map(child => {
1050
- return getRectangleByElements(board, [child], false);
1051
- });
1052
- const firstRec = getRectangleByElements(board, [children[0]], true);
1053
- const fakeLeftRec = {
1054
- x: firstRec.x - firstRec.width,
1055
- y: firstRec.y - firstRec.height,
1056
- width: firstRec.width,
1057
- height: firstRec.height
1058
- };
1059
- const lastRec = getRectangleByElements(board, [children[children.length - 1]], true);
1060
- const fakeRightRec = {
1061
- x: lastRec.x + lastRec.width,
1062
- y: lastRec.y + lastRec.height,
1063
- width: lastRec.width,
1064
- height: lastRec.height
1169
+ let fakeY = targetComponent.node.y;
1170
+ let fakeX = targetRect.x;
1171
+ const strokeWidth = targetComponent.node.origin.branchWidth ? targetComponent.node.origin.branchWidth : STROKE_WIDTH;
1172
+ const pointOptions = {
1173
+ fakeX,
1174
+ fakeY,
1175
+ x,
1176
+ y,
1177
+ width,
1178
+ height,
1179
+ strokeWidth
1065
1180
  };
1066
- recArray.push(fakeRightRec);
1067
- recArray.unshift(fakeLeftRec);
1068
- for (let i = 0; i < recArray.length - 1; i++) {
1069
- const recXOrY = isHorizontal ? recArray[i].y : recArray[i].x;
1070
- const recWidthOrHeight = isHorizontal ? recArray[i].height : recArray[i].width;
1071
- if (location >= recXOrY + recWidthOrHeight / 2 &&
1072
- location <= recArray[i + 1][isHorizontal ? 'y' : 'x'] + recArray[i + 1][isHorizontal ? 'height' : 'width'] / 2) {
1073
- return i - 1;
1074
- }
1075
- }
1076
- return 0;
1077
- }
1078
- function handleTouchedAbstract(board, touchedAbstract, endPoint) {
1079
- let touchedHandle;
1080
- const abstract = getSelectedElements(board)
1081
- .filter(element => AbstractNode.isAbstract(element))
1082
- .find(element => {
1083
- touchedHandle = getHitAbstractHandle(board, element, endPoint);
1084
- return touchedHandle;
1085
- });
1086
- if (touchedAbstract === abstract) {
1087
- return touchedAbstract;
1088
- }
1089
- if (touchedAbstract) {
1090
- const component = PlaitElement.getComponent(touchedAbstract);
1091
- component.updateAbstractIncludedOutline();
1092
- touchedAbstract = undefined;
1093
- }
1094
- if (abstract) {
1095
- touchedAbstract = abstract;
1096
- const component = PlaitElement.getComponent(touchedAbstract);
1097
- component.updateAbstractIncludedOutline(touchedHandle);
1098
- }
1099
- return touchedAbstract;
1100
- }
1101
-
1102
- /**
1103
- * get correctly layout:
1104
- * 1. root is standard -> left or right
1105
- * 2. correct layout by incorrect layout direction
1106
- * @param element
1107
- */
1108
- const getCorrectLayoutByElement = (element) => {
1109
- const { root } = findUpElement(element);
1110
- const rootLayout = root.layout || getDefaultLayout();
1111
- let correctRootLayout = rootLayout;
1112
- if (element.isRoot) {
1113
- return correctRootLayout;
1114
- }
1115
- const component = PlaitElement.getComponent(element);
1116
- let layout = element.layout;
1117
- let parentComponent = null;
1118
- let parent = component.parent.origin;
1119
- while (!layout && parent) {
1120
- parentComponent = PlaitElement.getComponent(parent);
1121
- layout = parentComponent.node.origin.layout;
1122
- parent = parentComponent.parent?.origin;
1123
- }
1124
- if ((AbstractNode.isAbstract(element) || isChildOfAbstract(MindElement.getNode(element))) &&
1125
- isIndentedLayout(layout)) {
1126
- return getAbstractLayout(layout);
1127
- }
1128
- // handle root standard
1129
- if (rootLayout === MindLayoutType.standard) {
1130
- correctRootLayout = component?.node.left ? MindLayoutType.left : MindLayoutType.right;
1131
- }
1132
- if (parentComponent && parentComponent.node.origin.isRoot) {
1133
- return correctRootLayout;
1134
- }
1135
- if (layout) {
1136
- const incorrectDirection = getInCorrectLayoutDirection(correctRootLayout, layout);
1137
- if (incorrectDirection) {
1138
- return correctLayoutByDirection(layout, incorrectDirection);
1139
- }
1140
- else {
1141
- return layout;
1142
- }
1143
- }
1144
- else {
1145
- return correctRootLayout;
1146
- }
1147
- };
1148
-
1149
- const getBranchLayouts = (element) => {
1150
- const layouts = [];
1151
- if (element.layout) {
1152
- //getCorrectLayoutByElement含有递归操作,getBranchMindmapLayouts本身也有递归操作,有待优化
1153
- layouts.unshift(getCorrectLayoutByElement(element));
1154
- }
1155
- let parent = findParentElement(element);
1156
- while (parent) {
1157
- if (parent.layout) {
1158
- layouts.unshift(parent.layout);
1159
- }
1160
- parent = findParentElement(parent);
1161
- }
1162
- return layouts;
1163
- };
1164
-
1165
- /**
1166
- * get available sub layouts by element
1167
- * @param element
1168
- * @returns MindLayoutType[]
1169
- */
1170
- const getAvailableSubLayoutsByElement = (element) => {
1171
- const parentElement = findParentElement(element);
1172
- if (parentElement) {
1173
- const branchLayouts = getBranchLayouts(parentElement);
1174
- if (branchLayouts[0] === MindLayoutType.standard) {
1175
- const node = MindElement.getNode(element);
1176
- branchLayouts[0] = node.left ? MindLayoutType.left : MindLayoutType.right;
1177
- }
1178
- const currentLayoutDirections = getBranchDirectionsByLayouts(branchLayouts);
1179
- let availableSubLayouts = getAvailableSubLayoutsByLayoutDirections(currentLayoutDirections);
1180
- const parentLayout = [branchLayouts[branchLayouts.length - 1]];
1181
- const parentDirections = getBranchDirectionsByLayouts(parentLayout);
1182
- const parentAvailableSubLayouts = getAvailableSubLayoutsByLayoutDirections(parentDirections);
1183
- availableSubLayouts = availableSubLayouts.filter(layout => parentAvailableSubLayouts.some(parentAvailableSubLayout => parentAvailableSubLayout === layout));
1184
- return availableSubLayouts;
1185
- }
1186
- return undefined;
1187
- };
1188
-
1189
- /**
1190
- * 获取父节点布局类型
1191
- * @param element
1192
- * @returns MindLayoutType
1193
- */
1194
- const getLayoutParentByElement = (element) => {
1195
- let parent = findParentElement(element);
1196
- while (parent) {
1197
- if (parent.layout) {
1198
- return parent.layout;
1181
+ const parentLayout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
1182
+ const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
1183
+ if (!isMixedLayout(parentLayout, layout)) {
1184
+ // 构造一条直线
1185
+ let linePoints = [
1186
+ [startLinePoint, y + height / 2],
1187
+ [endLinePoint, y + height / 2]
1188
+ ];
1189
+ if (isIndentedLayout(parentLayout)) {
1190
+ const fakePoint = getIndentedFakePoint(parentLayout, pointOptions);
1191
+ drawIndentNodeG(board, fakeDropNodeG, fakePoint, targetComponent.node);
1192
+ return;
1199
1193
  }
1200
- parent = findParentElement(parent);
1201
- }
1202
- return getDefaultLayout();
1203
- };
1204
-
1205
- const getLayoutByElement = (element) => {
1206
- const layout = element.layout;
1207
- if (layout) {
1208
- return layout;
1209
- }
1210
- if (AbstractNode.isAbstract(element) ||
1211
- (isChildOfAbstract(MindElement.getNode(element)) && isIndentedLayout(layout))) {
1212
- const parentLayout = getLayoutParentByElement(element);
1213
- return getAbstractLayout(parentLayout);
1214
- }
1215
- return getLayoutParentByElement(element);
1216
- };
1217
-
1218
- const MindQueries = {
1219
- getAvailableSubLayoutsByElement,
1220
- getLayoutParentByElement,
1221
- getBranchLayouts,
1222
- getLayoutByElement,
1223
- getCorrectLayoutByElement
1224
- };
1225
-
1226
- const PlaitMind = {
1227
- isMind: (value) => {
1228
- return value.type === 'mindmap';
1229
- }
1230
- };
1231
- const MindElement = {
1232
- hasLayout(value, layout) {
1233
- const _layout = MindQueries.getLayoutByElement(value);
1234
- return _layout === layout;
1235
- },
1236
- isIndentedLayout(value) {
1237
- const _layout = MindQueries.getLayoutByElement(value);
1238
- return isIndentedLayout(_layout);
1239
- },
1240
- isMindElement(board, element) {
1241
- const path = PlaitBoard.findPath(board, element);
1242
- const rootElement = PlaitNode.get(board, path.slice(0, 1));
1243
- if (PlaitMind.isMind(rootElement)) {
1244
- return true;
1194
+ else if (isVerticalLogicLayout(parentLayout)) {
1195
+ if (!targetComponent.node.origin.isRoot) {
1196
+ /**
1197
+ * 计算逻辑:
1198
+ * 1. 移动到左侧:当前节点 startX - 偏移值,偏移值计算如下:
1199
+ * a. 第一个节点: 固定值(来源于 getMainAxle,第二级节点:BASE * 8,其他 BASE * 3 + strokeWidth / 2);
1200
+ * b. 第二个节点到最后一个节点之间:上一个节点到当前节点间距的一半((当前节点 startX - 上一个节点的 endX) / 2),endX = 当前节点的 startX + width;
1201
+ * 2. 移动到右侧:当前节点 x + width + 偏移值,偏移值计算如下:
1202
+ * a. 第二个节点到最后一个节点之间的右侧:当前节点到下一个节点间距的一半((下一个节点 startX - 当前节点的 endX) / 2),endX = 当前节点的 startX + width;
1203
+ * b. 最后一个节点的右侧:固定值(来源于 getMainAxle,第二级节点:BASE * 8,其他 BASE * 3 + strokeWidth / 2);
1204
+ */
1205
+ fakeY = targetComponent.node.y;
1206
+ const parentComponent = PlaitElement.getComponent(targetComponent.parent.origin);
1207
+ const targetIndex = parentComponent.node.origin.children.indexOf(targetComponent.node.origin);
1208
+ if (detectResult === 'left') {
1209
+ let offsetX = 0;
1210
+ const isFirstNode = targetIndex === 0;
1211
+ if (isFirstNode) {
1212
+ offsetX = parentComponent.node.origin.isRoot ? BASE * 8 : BASE * 3 + strokeWidth / 2;
1213
+ }
1214
+ else {
1215
+ const previousComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex - 1]);
1216
+ const previousRect = getRectangleByNode(previousComponent.node);
1217
+ const space = targetRect.x - (previousRect.x + previousRect.width);
1218
+ offsetX = space / 2;
1219
+ }
1220
+ fakeX = targetRect.x - offsetX - width / 2 - Math.ceil(strokeWidth / 2);
1221
+ }
1222
+ if (detectResult === 'right') {
1223
+ let offsetX = 0;
1224
+ const isLastNode = targetIndex === parentComponent.node.origin.children.length - 1;
1225
+ if (isLastNode) {
1226
+ offsetX = parentComponent.node.origin.isRoot ? BASE * 8 : BASE * 3 + strokeWidth / 2;
1227
+ }
1228
+ else {
1229
+ const nextComponent = PlaitElement.getComponent(parentComponent.node.origin.children[targetIndex + 1]);
1230
+ const nextRect = getRectangleByNode(nextComponent.node);
1231
+ const space = nextRect.x - (targetRect.x + targetRect.width);
1232
+ offsetX = space / 2;
1233
+ }
1234
+ fakeX = targetRect.x + width + offsetX - width / 2 - Math.ceil(strokeWidth / 2);
1235
+ }
1236
+ startRectanglePointX = fakeX;
1237
+ if (isTopLayout(parentLayout)) {
1238
+ // 因为矩形是从左上角为起点向下画的,所以需要向上偏移一个矩形的高度(-12)
1239
+ startRectanglePointY = fakeY + height + targetComponent.node.vGap - 12;
1240
+ }
1241
+ if (isBottomLayout(parentLayout)) {
1242
+ startRectanglePointY = fakeY + targetComponent.node.vGap;
1243
+ }
1244
+ endRectanglePointX = startRectanglePointX + 30;
1245
+ endRectanglePointY = startRectanglePointY + 12;
1246
+ const fakeNode = { ...targetComponent.node, x: fakeX, y: fakeY, width: 30 };
1247
+ const linkSVGG = drawLink(board, parentComponent.node, fakeNode, PRIMARY_COLOR, false, false);
1248
+ fakeDropNodeG?.appendChild(linkSVGG);
1249
+ }
1245
1250
  }
1246
1251
  else {
1247
- return false;
1248
- }
1249
- },
1250
- getParent(node) {
1251
- if (PlaitMind.isMind(node)) {
1252
- throw new Error('mind root node can not get parent');
1252
+ let linkSVGG = PlaitBoard.getRoughSVG(board).linearPath(linePoints, { stroke: PRIMARY_COLOR, strokeWidth });
1253
+ fakeDropNodeG?.appendChild(linkSVGG);
1253
1254
  }
1254
- const parent = NODE_TO_PARENT.get(node);
1255
- return parent;
1256
- },
1257
- getRoot(board, element) {
1258
- const path = PlaitBoard.findPath(board, element);
1259
- return PlaitNode.get(board, path.slice(0, 1));
1260
- },
1261
- getNode(element) {
1262
- const node = ELEMENT_TO_NODE.get(element);
1263
- if (!node) {
1264
- throw new Error(`can not get node from ${JSON.stringify(element)}`);
1255
+ // 构造一个矩形框坐标
1256
+ let fakeRectangleG = drawRoundRectangle(PlaitBoard.getRoughSVG(board), startRectanglePointX, startRectanglePointY, endRectanglePointX, endRectanglePointY, {
1257
+ stroke: PRIMARY_COLOR,
1258
+ strokeWidth: 2,
1259
+ fill: PRIMARY_COLOR,
1260
+ fillStyle: 'solid'
1261
+ });
1262
+ fakeDropNodeG?.appendChild(fakeRectangleG);
1263
+ }
1264
+ else {
1265
+ // 混合布局画线逻辑
1266
+ if (isHorizontalLogicLayout(parentLayout)) {
1267
+ if (isIndentedLayout(layout)) {
1268
+ const fakePoint = getIndentedFakePoint(layout, pointOptions);
1269
+ drawIndentNodeG(board, fakeDropNodeG, fakePoint, targetComponent.node);
1270
+ return;
1271
+ }
1265
1272
  }
1266
- return node;
1267
- },
1268
- hasEmojis(element) {
1269
- if (element.data.emojis) {
1270
- return true;
1273
+ }
1274
+ };
1275
+ const getHorizontalFakeY = (detectResult, targetIndex, parentNode, targetRect, layout, fakeY) => {
1276
+ if (detectResult === 'top') {
1277
+ if (targetIndex === 0 && isTopLayout(layout)) {
1278
+ fakeY = targetRect.y + targetRect.height;
1271
1279
  }
1272
- else {
1273
- return false;
1280
+ if (targetIndex > 0) {
1281
+ const previousComponent = PlaitElement.getComponent(parentNode.origin.children[targetIndex - 1]);
1282
+ const previousRect = getRectangleByNode(previousComponent.node);
1283
+ const topY = previousRect.y + previousRect.height;
1284
+ fakeY = topY + (targetRect.y - topY) / 5;
1274
1285
  }
1275
- },
1276
- getEmojis(element) {
1277
- return element.data.emojis;
1278
1286
  }
1279
- };
1280
-
1281
- const MindNode = {
1282
- get(root, path) {
1283
- let node = root;
1284
- for (let i = 0; i < path.length; i++) {
1285
- const p = path[i];
1286
- if (!node || !node.children || !node.children[p]) {
1287
- throw new Error(`Cannot find a descendant at path [${path}]`);
1288
- }
1289
- node = node.children[p];
1287
+ if (detectResult === 'bottom') {
1288
+ fakeY = targetRect.y + targetRect.height + 30;
1289
+ if (targetIndex < parentNode.origin.children.length - 1) {
1290
+ const nextComponent = PlaitElement.getComponent(parentNode.origin.children[targetIndex + 1]);
1291
+ const nextRect = getRectangleByNode(nextComponent.node);
1292
+ const topY = targetRect.y + targetRect.height;
1293
+ fakeY = topY + (nextRect.y - topY) / 5;
1290
1294
  }
1291
- return node;
1292
- },
1293
- isEquals(node, otherNode) {
1294
- const hasSameSize = node.x === otherNode.x && node.y === otherNode.y && node.width === otherNode.width && node.height === otherNode.height;
1295
- const hasSameOrigin = node.origin === otherNode.origin;
1296
- let hasSameParentOriginChildren = false;
1297
- if (node.parent && otherNode.parent) {
1298
- hasSameParentOriginChildren = node.parent.origin.children == otherNode.parent.origin.children;
1295
+ if (targetIndex === parentNode.origin.children.length - 1) {
1296
+ fakeY = targetRect.y + targetRect.height + 30;
1299
1297
  }
1300
- return hasSameSize && hasSameOrigin && hasSameParentOriginChildren;
1301
1298
  }
1299
+ return fakeY;
1302
1300
  };
1303
-
1304
- var LayoutDirection;
1305
- (function (LayoutDirection) {
1306
- LayoutDirection["top"] = "top";
1307
- LayoutDirection["right"] = "right";
1308
- LayoutDirection["bottom"] = "bottom";
1309
- LayoutDirection["left"] = "left";
1310
- })(LayoutDirection || (LayoutDirection = {}));
1311
- const LayoutDirectionsMap = {
1312
- [MindLayoutType.right]: [LayoutDirection.right],
1313
- [MindLayoutType.left]: [LayoutDirection.left],
1314
- [MindLayoutType.upward]: [LayoutDirection.top],
1315
- [MindLayoutType.downward]: [LayoutDirection.bottom],
1316
- [MindLayoutType.rightBottomIndented]: [LayoutDirection.right, LayoutDirection.bottom],
1317
- [MindLayoutType.rightTopIndented]: [LayoutDirection.right, LayoutDirection.top],
1318
- [MindLayoutType.leftBottomIndented]: [LayoutDirection.left, LayoutDirection.bottom],
1319
- [MindLayoutType.leftTopIndented]: [LayoutDirection.left, LayoutDirection.top]
1320
- };
1321
-
1322
- var AbstractHandlePosition;
1323
- (function (AbstractHandlePosition) {
1324
- AbstractHandlePosition["start"] = "start";
1325
- AbstractHandlePosition["end"] = "end";
1326
- })(AbstractHandlePosition || (AbstractHandlePosition = {}));
1327
- var AbstractResizeState;
1328
- (function (AbstractResizeState) {
1329
- AbstractResizeState["start"] = "start";
1330
- AbstractResizeState["resizing"] = "resizing";
1331
- AbstractResizeState["end"] = "end";
1332
- })(AbstractResizeState || (AbstractResizeState = {}));
1333
-
1334
- function enterNodeEditing(element) {
1335
- const component = ELEMENT_TO_COMPONENT.get(element);
1336
- component.startEditText(false, false);
1337
- }
1338
-
1339
- function findParentElement(element) {
1340
- const component = PlaitElement.getComponent(element);
1341
- if (component && component.parent) {
1342
- return component.parent.origin;
1301
+ const getIndentedFakePoint = (layout, pointOptions) => {
1302
+ let { fakeX, fakeY, x, y, width, height, strokeWidth } = pointOptions;
1303
+ const hGap = BASE * 4;
1304
+ const vGap = BASE * 6;
1305
+ const offsetX = hGap + width / 2 + strokeWidth;
1306
+ const offsetY = vGap + height / 2 + strokeWidth;
1307
+ if (isLeftLayout(layout)) {
1308
+ fakeX = x - offsetX;
1343
1309
  }
1344
- return undefined;
1345
- }
1346
- function findUpElement(element) {
1347
- let branch;
1348
- let root = element;
1349
- let parent = findParentElement(element);
1350
- while (parent) {
1351
- branch = root;
1352
- root = parent;
1353
- parent = findParentElement(parent);
1310
+ if (isRightLayout(layout)) {
1311
+ fakeX = x + offsetX;
1354
1312
  }
1355
- return { root, branch };
1356
- }
1357
- const getChildrenCount = (element) => {
1358
- const count = element.children.reduce((p, c) => {
1359
- return p + getChildrenCount(c);
1360
- }, 0);
1361
- return count + element.children.length;
1362
- };
1363
- const isChildElement = (origin, child) => {
1364
- let parent = findParentElement(child);
1365
- while (parent) {
1366
- if (parent === origin) {
1367
- return true;
1368
- }
1369
- parent = findParentElement(parent);
1313
+ if (isTopLayout(layout)) {
1314
+ fakeY = y - offsetY;
1370
1315
  }
1371
- return false;
1316
+ if (isBottomLayout(layout)) {
1317
+ fakeY = y + height + offsetY;
1318
+ }
1319
+ return { fakeX, fakeY };
1372
1320
  };
1373
- const filterChildElement = (elements) => {
1374
- let result = [];
1375
- elements.forEach(element => {
1376
- const isChild = elements.some(node => {
1377
- return isChildElement(node, element);
1378
- });
1379
- if (!isChild) {
1380
- result.push(element);
1381
- }
1321
+ const drawIndentNodeG = (board, fakeDropNodeG, fakePoint, node) => {
1322
+ const { fakeX, fakeY } = fakePoint;
1323
+ const fakeNode = { ...node, x: fakeX, y: fakeY, width: 30, height: 12 };
1324
+ const linkSVGG = drawIndentedLink(board, node, fakeNode, PRIMARY_COLOR, false);
1325
+ const startRectanglePointX = fakeX, startRectanglePointY = fakeY, endRectanglePointX = fakeX + 30, endRectanglePointY = fakeY + 12;
1326
+ const fakeRectangleG = drawRoundRectangle(PlaitBoard.getRoughSVG(board), startRectanglePointX, startRectanglePointY, endRectanglePointX, endRectanglePointY, {
1327
+ stroke: PRIMARY_COLOR,
1328
+ strokeWidth: 2,
1329
+ fill: PRIMARY_COLOR,
1330
+ fillStyle: 'solid'
1382
1331
  });
1383
- return result;
1384
- };
1385
- const isChildRight = (node, child) => {
1386
- return node.x < child.x;
1387
- };
1388
- const isChildUp = (node, child) => {
1389
- return node.y > child.y;
1390
- };
1391
- const copyNewNode = (node) => {
1392
- const newNode = { ...node };
1393
- newNode.id = idCreator();
1394
- newNode.children = [];
1395
- for (const childNode of node.children) {
1396
- newNode.children.push(copyNewNode(childNode));
1397
- }
1398
- return newNode;
1332
+ fakeDropNodeG?.appendChild(linkSVGG);
1333
+ fakeDropNodeG?.appendChild(fakeRectangleG);
1399
1334
  };
1400
- const transformRootToNode = (board, node) => {
1401
- const newNode = { ...node };
1402
- delete newNode.isRoot;
1403
- delete newNode.rightNodeCount;
1404
- delete newNode.type;
1405
- const text = Node.string(node.data.topic.children[0]) || ' ';
1406
- const { width, height } = getSizeByText(text, PlaitBoard.getViewportContainer(board), TOPIC_DEFAULT_MAX_WORD_COUNT);
1407
- newNode.width = Math.max(width, NODE_MIN_WIDTH);
1408
- newNode.height = height;
1409
- if (newNode.layout === MindLayoutType.standard) {
1410
- delete newNode.layout;
1335
+
1336
+ /**
1337
+ *
1338
+ * @param targetNode
1339
+ * @param centerPoint
1340
+ * @returns DetectResult[] | null
1341
+ */
1342
+ const directionDetector = (targetNode, centerPoint) => {
1343
+ const { x, y, width, height } = getRectangleByNode(targetNode);
1344
+ const yCenter = y + height / 2;
1345
+ const xCenter = x + width / 2;
1346
+ const top = targetNode.y;
1347
+ const bottom = targetNode.y + targetNode.height;
1348
+ const left = targetNode.x;
1349
+ const right = targetNode.x + targetNode.width;
1350
+ const direction = [];
1351
+ // x 轴
1352
+ if (centerPoint[1] > y && centerPoint[1] < y + height) {
1353
+ if (centerPoint[0] > left && centerPoint[0] < xCenter) {
1354
+ direction.push('left');
1355
+ }
1356
+ if (centerPoint[0] > xCenter && centerPoint[0] < right) {
1357
+ direction.push('right');
1358
+ }
1359
+ // 重合区域,返回两个方向
1360
+ if ((centerPoint[0] > x && centerPoint[0] < xCenter) || (centerPoint[0] > xCenter && centerPoint[0] < x + width)) {
1361
+ if (centerPoint[1] < yCenter) {
1362
+ direction.push('top');
1363
+ }
1364
+ else {
1365
+ direction.push('bottom');
1366
+ }
1367
+ }
1368
+ return direction.length ? direction : null;
1411
1369
  }
1412
- return newNode;
1413
- };
1414
- const transformNodeToRoot = (board, node) => {
1415
- const newElement = { ...node };
1416
- let text = Node.string(newElement.data.topic);
1417
- if (!text) {
1418
- text = '思维导图';
1419
- newElement.data.topic = { children: [{ text }] };
1370
+ // y 轴
1371
+ if (centerPoint[0] > x && centerPoint[0] < x + width) {
1372
+ if (centerPoint[1] > top && centerPoint[1] < yCenter) {
1373
+ direction.push('top');
1374
+ }
1375
+ if (centerPoint[1] > yCenter && centerPoint[1] < bottom) {
1376
+ direction.push('bottom');
1377
+ }
1378
+ if ((centerPoint[1] > y && centerPoint[1] < y + height) || (centerPoint[1] > yCenter && centerPoint[1] < y + height)) {
1379
+ if (centerPoint[0] < xCenter) {
1380
+ direction.push('left');
1381
+ }
1382
+ else {
1383
+ direction.push('right');
1384
+ }
1385
+ }
1386
+ return direction.length ? direction : null;
1420
1387
  }
1421
- delete newElement?.strokeColor;
1422
- delete newElement?.fill;
1423
- delete newElement?.shape;
1424
- delete newElement?.strokeWidth;
1425
- const { width, height } = getSizeByText(text, PlaitBoard.getViewportContainer(board), TOPIC_DEFAULT_MAX_WORD_COUNT, ROOT_TOPIC_FONT_SIZE);
1426
- newElement.width = Math.max(width, NODE_MIN_WIDTH);
1427
- newElement.height = height;
1428
- return {
1429
- ...newElement,
1430
- layout: newElement.layout ?? MindLayoutType.right,
1431
- isCollapsed: false,
1432
- isRoot: true,
1433
- type: 'mindmap'
1434
- };
1388
+ return null;
1435
1389
  };
1436
- const extractNodesText = (node) => {
1437
- let str = '';
1438
- if (node) {
1439
- str += Node.string(node.data.topic.children[0]) + ' ';
1440
- for (const childNode of node.children) {
1441
- str += extractNodesText(childNode);
1390
+
1391
+ const directionCorrector = (node, detectResults) => {
1392
+ if (!node.origin.isRoot) {
1393
+ const parentlayout = MindQueries.getCorrectLayoutByElement(node?.parent.origin);
1394
+ if (isStandardLayout(parentlayout)) {
1395
+ const idx = node.parent.children.findIndex(x => x === node);
1396
+ const isLeft = idx >= (node.parent.origin.rightNodeCount || 0);
1397
+ return getAllowedDirection(detectResults, [isLeft ? 'right' : 'left']);
1398
+ }
1399
+ if (isLeftLayout(parentlayout)) {
1400
+ return getAllowedDirection(detectResults, ['right']);
1401
+ }
1402
+ if (isRightLayout(parentlayout)) {
1403
+ return getAllowedDirection(detectResults, ['left']);
1404
+ }
1405
+ if (parentlayout === MindLayoutType.upward) {
1406
+ return getAllowedDirection(detectResults, ['bottom']);
1407
+ }
1408
+ if (parentlayout === MindLayoutType.downward) {
1409
+ return getAllowedDirection(detectResults, ['top']);
1442
1410
  }
1443
1411
  }
1444
- return str;
1445
- };
1446
- const changeRightNodeCount = (board, parentPath, changeNumber) => {
1447
- const _rightNodeCount = board.children[parentPath[0]].rightNodeCount;
1448
- Transforms.setNode(board, {
1449
- rightNodeCount: changeNumber >= 0
1450
- ? _rightNodeCount + changeNumber
1451
- : _rightNodeCount + changeNumber < 0
1452
- ? 0
1453
- : _rightNodeCount + changeNumber
1454
- }, parentPath);
1455
- };
1456
- const shouldChangeRightNodeCount = (selectedElement) => {
1457
- const parentElement = findParentElement(selectedElement);
1458
- if (parentElement) {
1459
- const nodeIndex = parentElement.children.findIndex(item => item.id === selectedElement.id);
1460
- if (parentElement.isRoot &&
1461
- getRootLayout(parentElement) === MindLayoutType.standard &&
1462
- parentElement.rightNodeCount &&
1463
- nodeIndex <= parentElement.rightNodeCount - 1) {
1464
- return true;
1412
+ else {
1413
+ const layout = MindQueries.getCorrectLayoutByElement(node?.origin);
1414
+ if (isStandardLayout(layout)) {
1415
+ return getAllowedDirection(detectResults, ['top', 'bottom']);
1416
+ }
1417
+ if (isTopLayout(layout)) {
1418
+ return getAllowedDirection(detectResults, ['left', 'right', 'bottom']);
1419
+ }
1420
+ if (isBottomLayout(layout)) {
1421
+ return getAllowedDirection(detectResults, ['left', 'right', 'top']);
1422
+ }
1423
+ if (layout === MindLayoutType.left) {
1424
+ return getAllowedDirection(detectResults, ['right', 'top', 'bottom']);
1425
+ }
1426
+ if (layout === MindLayoutType.right) {
1427
+ return getAllowedDirection(detectResults, ['left', 'top', 'bottom']);
1465
1428
  }
1466
1429
  }
1467
- return false;
1430
+ return null;
1468
1431
  };
1469
- const createDefaultMindMapElement = (point, rightNodeCount, layout) => {
1470
- const root = createMindElement('思维导图', 72, ROOT_DEFAULT_HEIGHT, { shape: MindNodeShape.roundRectangle, layout });
1471
- root.rightNodeCount = rightNodeCount;
1472
- root.isRoot = true;
1473
- root.type = 'mindmap';
1474
- root.points = [point];
1475
- const children = [1, 1, 1].map(() => {
1476
- return createMindElement('新建节点', 56, TEXT_DEFAULT_HEIGHT, { shape: MindNodeShape.roundRectangle });
1432
+ const getAllowedDirection = (detectResults, illegalDirections) => {
1433
+ const directions = detectResults;
1434
+ illegalDirections.forEach(item => {
1435
+ const bottomDirectionIndex = directions.findIndex(direction => direction === item);
1436
+ if (bottomDirectionIndex !== -1) {
1437
+ directions.splice(bottomDirectionIndex, 1);
1438
+ }
1477
1439
  });
1478
- root.children = children;
1479
- return root;
1440
+ return directions.length ? directions : null;
1480
1441
  };
1481
- const createMindElement = (text, width, height, options) => {
1482
- const newElement = {
1483
- id: idCreator(),
1484
- data: {
1485
- topic: { children: [{ text }] }
1486
- },
1487
- children: [],
1488
- width,
1489
- height,
1490
- fill: options.fill,
1491
- strokeColor: options.strokeColor,
1492
- strokeWidth: options.strokeWidth,
1493
- shape: options.shape
1494
- };
1495
- if (options.fill) {
1496
- newElement.fill = options.fill;
1497
- }
1498
- if (options.strokeColor) {
1499
- newElement.strokeColor = options.strokeColor;
1500
- }
1501
- if (!isNullOrUndefined(options.strokeWidth)) {
1502
- newElement.strokeWidth = options.strokeWidth;
1503
- }
1504
- if (options.shape) {
1505
- newElement.shape = options.shape;
1506
- }
1507
- if (options.layout) {
1508
- newElement.layout = options.layout;
1509
- }
1510
- if (options.linkLineColor) {
1511
- newElement.linkLineColor = options.linkLineColor;
1442
+
1443
+ /* 根据布局调整 target 以及 direction */
1444
+ const readjustmentDropTarget = (dropTarget) => {
1445
+ const { target, detectResult } = dropTarget;
1446
+ const newDropTarget = { target, detectResult };
1447
+ const targetComponent = PlaitElement.getComponent(target);
1448
+ if (targetComponent.node.children.length > 0 && dropTarget.detectResult) {
1449
+ const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
1450
+ const parentLayout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
1451
+ if (['right', 'left'].includes(dropTarget.detectResult)) {
1452
+ if (!isMixedLayout(parentLayout, layout)) {
1453
+ if (targetComponent.node.origin.isRoot) {
1454
+ const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
1455
+ // 标准布局,根节点
1456
+ if (isStandardLayout(layout)) {
1457
+ const rightNodeCount = targetComponent.node.origin.rightNodeCount;
1458
+ if (detectResult === 'left') {
1459
+ // 作为左的第一个节点
1460
+ if (targetComponent.node.children.length === rightNodeCount) {
1461
+ return newDropTarget;
1462
+ }
1463
+ }
1464
+ else {
1465
+ // 作为右的第一个节点或最后一个节点
1466
+ if (rightNodeCount === 0) {
1467
+ newDropTarget.target = target;
1468
+ }
1469
+ else {
1470
+ newDropTarget.target = targetComponent.node.children[rightNodeCount - 1].origin;
1471
+ newDropTarget.detectResult = 'bottom';
1472
+ }
1473
+ return newDropTarget;
1474
+ }
1475
+ }
1476
+ }
1477
+ // 缩进布局探测到第一个子节点
1478
+ if (isIndentedLayout(parentLayout)) {
1479
+ newDropTarget.target = targetComponent.node.children[0].origin;
1480
+ newDropTarget.detectResult = isTopLayout(parentLayout) ? 'bottom' : 'top';
1481
+ return newDropTarget;
1482
+ }
1483
+ // 上下布局的根节点只可以探测到上或者下,子节点的左右探测不处理,跳过。
1484
+ if (isVerticalLogicLayout(parentLayout)) {
1485
+ return newDropTarget;
1486
+ }
1487
+ // 剩下是水平布局的默认情况:插入最后一个子节点的下方
1488
+ const lastChildNodeIndex = targetComponent.node.children.length - 1;
1489
+ newDropTarget.target = targetComponent.node.children[lastChildNodeIndex].origin;
1490
+ newDropTarget.detectResult = 'bottom';
1491
+ }
1492
+ else {
1493
+ // 处理左右布局下的混合布局
1494
+ if ([MindLayoutType.left, MindLayoutType.right].includes(parentLayout)) {
1495
+ const layout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin);
1496
+ if (isIndentedLayout(layout)) {
1497
+ newDropTarget.target = targetComponent.node.children[0].origin;
1498
+ newDropTarget.detectResult = isTopLayout(layout) ? 'bottom' : 'top';
1499
+ return newDropTarget;
1500
+ }
1501
+ }
1502
+ }
1503
+ }
1504
+ if (['top', 'bottom'].includes(dropTarget.detectResult)) {
1505
+ // 缩进布局移动至第一个节点
1506
+ if (targetComponent.node.origin.isRoot && isIndentedLayout(layout)) {
1507
+ newDropTarget.target = targetComponent.node.children[0].origin;
1508
+ newDropTarget.detectResult = isTopLayout(layout) ? 'bottom' : 'top';
1509
+ return newDropTarget;
1510
+ }
1511
+ // 上下布局,插到右边
1512
+ const parentLayout = MindQueries.getCorrectLayoutByElement(targetComponent.node.origin.isRoot ? targetComponent.node.origin : targetComponent.node.parent.origin);
1513
+ if (isVerticalLogicLayout(parentLayout)) {
1514
+ const lastChildNodeIndex = targetComponent.node.children.length - 1;
1515
+ newDropTarget.target = targetComponent.node.children[lastChildNodeIndex].origin;
1516
+ newDropTarget.detectResult = 'right';
1517
+ return newDropTarget;
1518
+ }
1519
+ }
1520
+ return newDropTarget;
1512
1521
  }
1513
- return newElement;
1522
+ return dropTarget;
1514
1523
  };
1515
- // layoutLevel 用来表示插入兄弟节点还是子节点
1516
- const insertMindElement = (board, inheritNode, path) => {
1517
- let fill, strokeColor, strokeWidth, shape = MindNodeShape.roundRectangle;
1518
- if (!inheritNode.isRoot) {
1519
- fill = inheritNode.fill;
1520
- strokeColor = inheritNode.strokeColor;
1521
- strokeWidth = inheritNode.strokeWidth;
1522
- }
1523
- shape = inheritNode.shape;
1524
- const newElement = createMindElement('', NODE_MIN_WIDTH, TEXT_DEFAULT_HEIGHT, { fill, strokeColor, strokeWidth, shape });
1525
- const index = path[path.length - 1];
1526
- const abstractNode = inheritNode.children.find(child => AbstractNode.isAbstract(child) && index > child.start && index <= child.end + 1);
1527
- if (abstractNode) {
1528
- const path = PlaitBoard.findPath(board, abstractNode);
1529
- Transforms.setNode(board, { end: abstractNode.end + 1 }, path);
1524
+
1525
+ const getRectangleByResizingLocation = (abstractRectangle, location, activeHandlePosition, isHorizontal) => {
1526
+ if (isHorizontal) {
1527
+ if (activeHandlePosition === AbstractHandlePosition.start) {
1528
+ return {
1529
+ ...abstractRectangle,
1530
+ y: location,
1531
+ height: abstractRectangle.height + abstractRectangle.y - location
1532
+ };
1533
+ }
1534
+ else {
1535
+ return {
1536
+ ...abstractRectangle,
1537
+ height: location - abstractRectangle.y
1538
+ };
1539
+ }
1530
1540
  }
1531
- Transforms.insertNode(board, newElement, path);
1532
- clearSelectedElement(board);
1533
- addSelectedElement(board, newElement);
1534
- setTimeout(() => {
1535
- enterNodeEditing(newElement);
1536
- });
1537
- };
1538
- const findLastChild = (child) => {
1539
- let result = child;
1540
- while (result.children.length !== 0) {
1541
- result = result.children[result.children.length - 1];
1541
+ else {
1542
+ if (activeHandlePosition === AbstractHandlePosition.start) {
1543
+ return {
1544
+ ...abstractRectangle,
1545
+ x: location,
1546
+ width: abstractRectangle.width + abstractRectangle.x - location
1547
+ };
1548
+ }
1549
+ else {
1550
+ return {
1551
+ ...abstractRectangle,
1552
+ width: location - abstractRectangle.x
1553
+ };
1554
+ }
1542
1555
  }
1543
- return result;
1544
1556
  };
1545
- const deleteSelectedELements = (board, selectedElements) => {
1546
- //翻转,从下到上修改,防止找不到 path
1547
- const deletableElements = filterChildElement(selectedElements).reverse();
1548
- const relativeAbstracts = [];
1549
- const accumulativeProperties = new WeakMap();
1550
- deletableElements.forEach(node => {
1551
- if (!PlaitMind.isMind(node)) {
1552
- const parentElement = MindElement.getParent(node);
1553
- const index = parentElement.children.indexOf(node);
1554
- const abstracts = parentElement.children.filter(value => AbstractNode.isAbstract(value));
1555
- abstracts.forEach(abstract => {
1556
- const abstractNode = abstract;
1557
- if (index >= abstractNode.start && index <= abstractNode.end) {
1558
- let newProperties = accumulativeProperties.get(abstractNode);
1559
- if (!newProperties) {
1560
- newProperties = { start: abstractNode.start, end: abstractNode.end };
1561
- accumulativeProperties.set(abstractNode, newProperties);
1562
- relativeAbstracts.push(abstractNode);
1563
- }
1564
- newProperties.end = newProperties.end - 1;
1565
- }
1566
- });
1557
+ const getLocationScope = (board, handlePosition, parentChildren, element, parent, isHorizontal) => {
1558
+ const node = MindElement.getNode(element);
1559
+ const { start, end } = getCorrectStartEnd(node.origin, parent);
1560
+ const startNode = parentChildren[start];
1561
+ const endNode = parentChildren[end];
1562
+ if (handlePosition === AbstractHandlePosition.start) {
1563
+ const abstractNode = parentChildren.filter(child => AbstractNode.isAbstract(child) && child.end < element.start);
1564
+ let minNode;
1565
+ if (abstractNode.length) {
1566
+ const index = abstractNode
1567
+ .map(node => {
1568
+ const { end } = getCorrectStartEnd(node, parent);
1569
+ return end;
1570
+ })
1571
+ .sort((a, b) => b - a)[0];
1572
+ minNode = parentChildren[index + 1];
1567
1573
  }
1568
- });
1569
- const abstractHandles = relativeAbstracts.map(value => {
1570
- const newProperties = accumulativeProperties.get(value);
1571
- if (newProperties) {
1572
- const path = PlaitBoard.findPath(board, value);
1573
- return () => {
1574
- if (newProperties.start > newProperties.end) {
1575
- Transforms.removeNode(board, path);
1576
- }
1577
- else {
1578
- Transforms.setNode(board, newProperties, path);
1579
- }
1574
+ else {
1575
+ minNode = parentChildren[0];
1576
+ }
1577
+ const minNodeRectangle = getRectangleByElements(board, [minNode], true);
1578
+ const endNodeRectangle = getRectangleByElements(board, [endNode], false);
1579
+ if (isHorizontal) {
1580
+ return {
1581
+ max: endNodeRectangle.y - ABSTRACT_INCLUDED_OUTLINE_OFFSET,
1582
+ min: minNodeRectangle.y - ABSTRACT_INCLUDED_OUTLINE_OFFSET
1580
1583
  };
1581
1584
  }
1582
- return () => { };
1583
- });
1584
- const deletableHandles = deletableElements.map(node => {
1585
- const path = PlaitBoard.findPath(board, node);
1586
- return () => {
1587
- if (shouldChangeRightNodeCount(node)) {
1588
- changeRightNodeCount(board, path.slice(0, path.length - 1), -1);
1589
- }
1590
- Transforms.removeNode(board, path);
1591
- };
1592
- });
1593
- abstractHandles.forEach(action => action());
1594
- deletableHandles.forEach(action => action());
1595
- };
1596
- const divideElementByParent = (elements) => {
1597
- const abstractIncludedGroups = [];
1598
- const parentElements = [];
1599
- for (let i = 0; i < elements.length; i++) {
1600
- const parent = MindElement.getParent(elements[i]);
1601
- const parentIndex = parentElements.indexOf(parent);
1602
- if (parentIndex === -1) {
1603
- parentElements.push(parent);
1604
- abstractIncludedGroups.push([elements[i]]);
1585
+ else {
1586
+ return {
1587
+ max: endNodeRectangle.x - ABSTRACT_INCLUDED_OUTLINE_OFFSET,
1588
+ min: minNodeRectangle.x - ABSTRACT_INCLUDED_OUTLINE_OFFSET
1589
+ };
1590
+ }
1591
+ }
1592
+ else {
1593
+ const abstractNode = parentChildren.filter(child => AbstractNode.isAbstract(child) && child.start > element.end);
1594
+ let maxNode;
1595
+ if (abstractNode.length) {
1596
+ const index = abstractNode
1597
+ .map(node => {
1598
+ const { start } = getCorrectStartEnd(node, parent);
1599
+ return start;
1600
+ })
1601
+ .sort((a, b) => a - b)[0];
1602
+ maxNode = parentChildren[index - 1];
1605
1603
  }
1606
1604
  else {
1607
- abstractIncludedGroups[parentIndex].push(elements[i]);
1605
+ const children = parentChildren.filter(child => !AbstractNode.isAbstract(child));
1606
+ maxNode = parentChildren[children.length - 1];
1607
+ }
1608
+ const maxNodeRectangle = getRectangleByElements(board, [maxNode], true);
1609
+ const startNodeRectangle = getRectangleByElements(board, [startNode], false);
1610
+ if (isHorizontal) {
1611
+ return {
1612
+ max: maxNodeRectangle.y + maxNodeRectangle.height + ABSTRACT_INCLUDED_OUTLINE_OFFSET,
1613
+ min: startNodeRectangle.y + startNodeRectangle.height + ABSTRACT_INCLUDED_OUTLINE_OFFSET
1614
+ };
1615
+ }
1616
+ else {
1617
+ return {
1618
+ max: maxNodeRectangle.x + maxNodeRectangle.width + ABSTRACT_INCLUDED_OUTLINE_OFFSET,
1619
+ min: startNodeRectangle.x + startNodeRectangle.width + ABSTRACT_INCLUDED_OUTLINE_OFFSET
1620
+ };
1608
1621
  }
1609
1622
  }
1610
- return { parentElements, abstractIncludedGroups };
1611
1623
  };
1612
-
1613
- const getStrokeByMindElement = (element) => {
1614
- let stroke = element.strokeColor;
1615
- if (stroke) {
1616
- return stroke;
1617
- }
1618
- const { root, branch } = findUpElement(element);
1619
- if (branch) {
1620
- const index = root.children.indexOf(branch);
1621
- const length = COLORS.length;
1622
- const remainder = index % length;
1623
- return COLORS[remainder];
1624
+ const getHitAbstractHandle = (board, element, point) => {
1625
+ const nodeLayout = MindQueries.getCorrectLayoutByElement(element);
1626
+ const isHorizontal = isHorizontalLayout(nodeLayout);
1627
+ const parentElement = MindElement.getParent(element);
1628
+ const includedElements = parentElement.children.slice(element.start, element.end + 1);
1629
+ let abstractRectangle = getRectangleByElements(board, includedElements, true);
1630
+ abstractRectangle = RectangleClient.getOutlineRectangle(abstractRectangle, -ABSTRACT_INCLUDED_OUTLINE_OFFSET);
1631
+ const startHandleRec = getAbstractHandleRectangle(abstractRectangle, isHorizontal, AbstractHandlePosition.start);
1632
+ const endHandleRec = getAbstractHandleRectangle(abstractRectangle, isHorizontal, AbstractHandlePosition.end);
1633
+ const pointRec = RectangleClient.toRectangleClient([point, point]);
1634
+ if (RectangleClient.isHit(pointRec, startHandleRec))
1635
+ return AbstractHandlePosition.start;
1636
+ if (RectangleClient.isHit(pointRec, endHandleRec))
1637
+ return AbstractHandlePosition.end;
1638
+ return undefined;
1639
+ };
1640
+ const getAbstractHandleRectangle = (rectangle, isHorizontal, position) => {
1641
+ let result;
1642
+ if (position === AbstractHandlePosition.start) {
1643
+ const location = isHorizontal ? rectangle.y : rectangle.x;
1644
+ result = getRectangleByResizingLocation(rectangle, location + ABSTRACT_HANDLE_MASK_WIDTH / 2, AbstractHandlePosition.end, isHorizontal);
1645
+ result = getRectangleByResizingLocation(result, location - ABSTRACT_HANDLE_MASK_WIDTH / 2, position, isHorizontal);
1624
1646
  }
1625
1647
  else {
1626
- return ROOT_NODE_STROKE;
1648
+ const location = isHorizontal ? rectangle.y + rectangle.height : rectangle.x + rectangle.width;
1649
+ result = getRectangleByResizingLocation(rectangle, location - ABSTRACT_HANDLE_MASK_WIDTH / 2, AbstractHandlePosition.start, isHorizontal);
1650
+ result = getRectangleByResizingLocation(result, location + ABSTRACT_HANDLE_MASK_WIDTH / 2, position, isHorizontal);
1627
1651
  }
1652
+ return result;
1628
1653
  };
1629
- const getLinkLineColorByMindElement = (element) => {
1630
- let color = element.linkLineColor;
1631
- if (color) {
1632
- return color;
1654
+ function findLocationLeftIndex(board, parentChildren, location, isHorizontal) {
1655
+ const children = parentChildren.filter(child => {
1656
+ return !AbstractNode.isAbstract(child);
1657
+ });
1658
+ const recArray = children.map(child => {
1659
+ return getRectangleByElements(board, [child], false);
1660
+ });
1661
+ const firstRec = getRectangleByElements(board, [children[0]], true);
1662
+ const fakeLeftRec = {
1663
+ x: firstRec.x - firstRec.width,
1664
+ y: firstRec.y - firstRec.height,
1665
+ width: firstRec.width,
1666
+ height: firstRec.height
1667
+ };
1668
+ const lastRec = getRectangleByElements(board, [children[children.length - 1]], true);
1669
+ const fakeRightRec = {
1670
+ x: lastRec.x + lastRec.width,
1671
+ y: lastRec.y + lastRec.height,
1672
+ width: lastRec.width,
1673
+ height: lastRec.height
1674
+ };
1675
+ recArray.push(fakeRightRec);
1676
+ recArray.unshift(fakeLeftRec);
1677
+ for (let i = 0; i < recArray.length - 1; i++) {
1678
+ const recXOrY = isHorizontal ? recArray[i].y : recArray[i].x;
1679
+ const recWidthOrHeight = isHorizontal ? recArray[i].height : recArray[i].width;
1680
+ if (location >= recXOrY + recWidthOrHeight / 2 &&
1681
+ location <= recArray[i + 1][isHorizontal ? 'y' : 'x'] + recArray[i + 1][isHorizontal ? 'height' : 'width'] / 2) {
1682
+ return i - 1;
1683
+ }
1633
1684
  }
1634
- const { root, branch } = findUpElement(element);
1635
- if (branch) {
1636
- const index = root.children.indexOf(branch);
1637
- const length = COLORS.length;
1638
- const remainder = index % length;
1639
- return COLORS[remainder];
1685
+ return 0;
1686
+ }
1687
+ function handleTouchedAbstract(board, touchedAbstract, endPoint) {
1688
+ let touchedHandle;
1689
+ const abstract = getSelectedElements(board)
1690
+ .filter(element => AbstractNode.isAbstract(element))
1691
+ .find(element => {
1692
+ touchedHandle = getHitAbstractHandle(board, element, endPoint);
1693
+ return touchedHandle;
1694
+ });
1695
+ if (touchedAbstract === abstract) {
1696
+ return touchedAbstract;
1640
1697
  }
1641
- else {
1642
- throw new Error('root element should not have link line');
1698
+ if (touchedAbstract) {
1699
+ const component = PlaitElement.getComponent(touchedAbstract);
1700
+ component.updateAbstractIncludedOutline();
1701
+ touchedAbstract = undefined;
1643
1702
  }
1644
- };
1645
- const getRootLinkLineColorByMindElement = (root) => {
1646
- const index = root.children.length;
1647
- const length = COLORS.length;
1648
- const remainder = index % length;
1649
- return COLORS[remainder];
1650
- };
1703
+ if (abstract) {
1704
+ touchedAbstract = abstract;
1705
+ const component = PlaitElement.getComponent(touchedAbstract);
1706
+ component.updateAbstractIncludedOutline(touchedHandle);
1707
+ }
1708
+ return touchedAbstract;
1709
+ }
1651
1710
 
1652
- function drawIndentedLink(roughSVG, node, child, defaultStroke = null, needDrawUnderline = true) {
1711
+ function drawIndentedLink(board, node, child, defaultStroke = null, needDrawUnderline = true) {
1653
1712
  const isUnderlineShap = getNodeShapeByElement(child.origin) === MindNodeShape.underline;
1654
1713
  let beginX, beginY, endX, endY, beginNode = node, endNode = child;
1655
1714
  const beginRectangle = getRectangleByNode(beginNode);
@@ -1661,7 +1720,7 @@ function drawIndentedLink(roughSVG, node, child, defaultStroke = null, needDrawU
1661
1720
  //根据位置,设置正负参数
1662
1721
  let plusMinus = isChildUp(node, child) ? (node.left ? [-1, -1] : [1, -1]) : node.left ? [-1, 1] : [1, 1];
1663
1722
  const layout = MindQueries.getCorrectLayoutByElement(node.origin);
1664
- const strokeWidth = child.origin.linkLineWidth ? child.origin.linkLineWidth : STROKE_WIDTH;
1723
+ const strokeWidth = child.origin.branchWidth ? child.origin.branchWidth : STROKE_WIDTH;
1665
1724
  if (beginNode.origin.isRoot) {
1666
1725
  if (layout === MindLayoutType.leftBottomIndented || layout === MindLayoutType.rightBottomIndented) {
1667
1726
  beginY += strokeWidth;
@@ -1682,9 +1741,9 @@ function drawIndentedLink(roughSVG, node, child, defaultStroke = null, needDrawU
1682
1741
  isUnderlineShap && needDrawUnderline ? [endX + (endNode.width - endNode.hGap * 2) * plusMinus[0], endY] : [endX, endY],
1683
1742
  isUnderlineShap && needDrawUnderline ? [endX + (endNode.width - endNode.hGap * 2) * plusMinus[0], endY] : [endX, endY]
1684
1743
  ];
1685
- const stroke = defaultStroke || getLinkLineColorByMindElement(child.origin);
1744
+ const stroke = defaultStroke || getBranchColorByMindElement(board, child.origin);
1686
1745
  const points = pointsOnBezierCurves(curve);
1687
- return roughSVG.curve(points, { stroke, strokeWidth });
1746
+ return PlaitBoard.getRoughSVG(board).curve(points, { stroke, strokeWidth });
1688
1747
  }
1689
1748
 
1690
1749
  var HorizontalPlacement;
@@ -1797,9 +1856,9 @@ const transformPlacement = (placement, direction) => {
1797
1856
  }
1798
1857
  };
1799
1858
 
1800
- function drawLogicLink(roughSVG, node, parent, isHorizontal) {
1801
- const stroke = getLinkLineColorByMindElement(node.origin);
1802
- const strokeWidth = node.origin.linkLineWidth ? node.origin.linkLineWidth : STROKE_WIDTH;
1859
+ function drawLogicLink(board, node, parent, isHorizontal) {
1860
+ const branchColor = getBranchColorByMindElement(board, node.origin);
1861
+ const strokeWidth = node.origin.branchWidth ? node.origin.branchWidth : STROKE_WIDTH;
1803
1862
  const hasStraightLine = !parent.origin.isRoot;
1804
1863
  const hasUnderlineShape = node.origin.shape === MindNodeShape.underline;
1805
1864
  const hasUnderlineShapeOfParent = parent.origin.shape === MindNodeShape.underline;
@@ -1839,7 +1898,7 @@ function drawLogicLink(roughSVG, node, parent, isHorizontal) {
1839
1898
  const underlineEnd = movePoint(endPoint, nodeClient.width, linkDirection);
1840
1899
  const underline = hasUnderlineShape && isHorizontal ? [underlineEnd, underlineEnd, underlineEnd] : [];
1841
1900
  const points = pointsOnBezierCurves([...straightLine, ...curve, ...underline]);
1842
- return roughSVG.curve(points, { stroke, strokeWidth });
1901
+ return PlaitBoard.getRoughSVG(board).curve(points, { stroke: branchColor, strokeWidth });
1843
1902
  }
1844
1903
 
1845
1904
  function getEmojisRectangle(element) {
@@ -1961,7 +2020,7 @@ function getRichtextRectangleByNode(node) {
1961
2020
  function drawRectangleNode(board, node) {
1962
2021
  const { x, y, width, height } = getRectangleByNode(node);
1963
2022
  const fill = node.origin.fill ? node.origin.fill : node.origin.isRoot ? ROOT_NODE_FILL : NODE_FILL;
1964
- const stroke = getStrokeByMindElement(node.origin);
2023
+ const stroke = getStrokeByMindElement(board, node.origin);
1965
2024
  const strokeWidth = node.origin.strokeWidth ? node.origin.strokeWidth : STROKE_WIDTH;
1966
2025
  const nodeG = drawRoundRectangle(PlaitBoard.getRoughSVG(board), x, y, x + width, y + height, {
1967
2026
  stroke,
@@ -2020,6 +2079,7 @@ class EmojiDrawer {
2020
2079
  const componentType = this.board.drawEmoji(emoji, element);
2021
2080
  this.componentRef = this.viewContainerRef.createComponent(componentType);
2022
2081
  this.componentRef.instance.emojiItem = emoji;
2082
+ this.componentRef.instance.board = this.board;
2023
2083
  const fontSize = PlaitMind.isMind(element) ? 18 : 14;
2024
2084
  this.componentRef.instance.fontSize = fontSize;
2025
2085
  }
@@ -2084,6 +2144,10 @@ class EmojisDrawer {
2084
2144
 
2085
2145
  const setLayout = (board, layout, path) => {
2086
2146
  correctLogicLayoutNode(board, layout, path);
2147
+ const element = PlaitNode.get(board, path);
2148
+ if (PlaitMind.isMind(element) && isStandardLayout(layout)) {
2149
+ handleAbstractIncluded(board, element);
2150
+ }
2087
2151
  Transforms.setNode(board, { layout }, path);
2088
2152
  };
2089
2153
  const correctLogicLayoutNode = (board, layout, path) => {
@@ -2250,6 +2314,14 @@ function hasAfterDraw(value) {
2250
2314
  return false;
2251
2315
  }
2252
2316
 
2317
+ function findNewChildNodePath(board, element) {
2318
+ return PlaitBoard.findPath(board, element).concat((element.children || []).filter(child => !AbstractNode.isAbstract(child)).length);
2319
+ }
2320
+ function findNewSiblingNodePath(board, element) {
2321
+ const path = PlaitBoard.findPath(board, element);
2322
+ return Path$1.next(path);
2323
+ }
2324
+
2253
2325
  class QuickInsertDrawer extends BaseDrawer {
2254
2326
  canDraw(element) {
2255
2327
  if (PlaitBoard.isReadonly(this.board) || element?.isCollapsed) {
@@ -2273,7 +2345,7 @@ class QuickInsertDrawer extends BaseDrawer {
2273
2345
  */
2274
2346
  const shape = getNodeShapeByElement(element);
2275
2347
  // 形状是矩形要偏移边框的线宽
2276
- const strokeWidth = element.linkLineWidth ? element.linkLineWidth : STROKE_WIDTH;
2348
+ const strokeWidth = element.branchWidth ? element.branchWidth : STROKE_WIDTH;
2277
2349
  let offsetBorderLineWidth = 0;
2278
2350
  if (shape === MindNodeShape.roundRectangle && offset === 0) {
2279
2351
  offsetBorderLineWidth = strokeWidth;
@@ -2381,7 +2453,7 @@ class QuickInsertDrawer extends BaseDrawer {
2381
2453
  underlineCoordinates[MindLayoutType.right].startY -= height * 0.5;
2382
2454
  underlineCoordinates[MindLayoutType.right].endY -= height * 0.5;
2383
2455
  }
2384
- const stroke = element.isRoot ? getRootLinkLineColorByMindElement(element) : getLinkLineColorByMindElement(element);
2456
+ const branchColor = PlaitMind.isMind(element) ? getNextBranchColor(element) : getBranchColorByMindElement(this.board, element);
2385
2457
  let nodeLayout = MindQueries.getCorrectLayoutByElement(element);
2386
2458
  if (element.isRoot && isStandardLayout(nodeLayout)) {
2387
2459
  const root = element;
@@ -2389,7 +2461,7 @@ class QuickInsertDrawer extends BaseDrawer {
2389
2461
  }
2390
2462
  const underlineCoordinate = underlineCoordinates[nodeLayout];
2391
2463
  if (underlineCoordinate) {
2392
- const underline = PlaitBoard.getRoughSVG(this.board).line(underlineCoordinate.startX, underlineCoordinate.startY, underlineCoordinate.endX, underlineCoordinate.endY, { stroke, strokeWidth });
2464
+ const underline = PlaitBoard.getRoughSVG(this.board).line(underlineCoordinate.startX, underlineCoordinate.startY, underlineCoordinate.endX, underlineCoordinate.endY, { stroke: branchColor, strokeWidth });
2393
2465
  const circleCoordinates = {
2394
2466
  startX: underlineCoordinate.endX,
2395
2467
  startY: underlineCoordinate.endY
@@ -2440,7 +2512,7 @@ class QuickInsertDrawer extends BaseDrawer {
2440
2512
  fromEvent(this.g, 'mouseup')
2441
2513
  .pipe(take(1))
2442
2514
  .subscribe(() => {
2443
- const path = PlaitBoard.findPath(this.board, element).concat(element.children.filter(child => !AbstractNode.isAbstract(child)).length);
2515
+ const path = findNewChildNodePath(this.board, element);
2444
2516
  insertMindElement(this.board, element, path);
2445
2517
  });
2446
2518
  }
@@ -2555,10 +2627,10 @@ class MindNodeComponent extends PlaitPluginElementComponent {
2555
2627
  this.linkG = drawAbstractLink(this.board, this.node, isHorizontalLayout(layout));
2556
2628
  }
2557
2629
  else if (MindElement.isIndentedLayout(this.parent.origin)) {
2558
- this.linkG = drawIndentedLink(this.roughSVG, this.parent, this.node);
2630
+ this.linkG = drawIndentedLink(this.board, this.parent, this.node);
2559
2631
  }
2560
2632
  else {
2561
- this.linkG = drawLogicLink(this.roughSVG, this.node, this.parent, isHorizontalLayout(layout));
2633
+ this.linkG = drawLogicLink(this.board, this.node, this.parent, isHorizontalLayout(layout));
2562
2634
  }
2563
2635
  this.g.append(this.linkG);
2564
2636
  }
@@ -2715,8 +2787,8 @@ class MindNodeComponent extends PlaitPluginElementComponent {
2715
2787
  Transforms.setNode(this.board, newElement, path);
2716
2788
  });
2717
2789
  const { x, y, width, height } = getRectangleByNode(this.node);
2718
- const stroke = getLinkLineColorByMindElement(this.element);
2719
- const strokeWidth = this.node.origin.linkLineWidth ? this.node.origin.linkLineWidth : STROKE_WIDTH;
2790
+ const stroke = getBranchColorByMindElement(this.board, this.element);
2791
+ const strokeWidth = this.node.origin.branchWidth ? this.node.origin.branchWidth : STROKE_WIDTH;
2720
2792
  const extendY = y + height / 2;
2721
2793
  const nodeLayout = MindQueries.getCorrectLayoutByElement(this.element);
2722
2794
  let extendLineXY = [
@@ -3217,7 +3289,7 @@ const withDnd = (board) => {
3217
3289
  });
3218
3290
  if (dropTarget?.target) {
3219
3291
  dropTarget = readjustmentDropTarget(dropTarget);
3220
- drawPlaceholderDropNodeG(dropTarget, roughSVG, fakeDropNodeG);
3292
+ drawPlaceholderDropNodeG(board, dropTarget, fakeDropNodeG);
3221
3293
  }
3222
3294
  }
3223
3295
  mousemove(event);
@@ -3376,7 +3448,7 @@ const setIsDragging = (board, state) => {
3376
3448
 
3377
3449
  const buildClipboardData = (board, selectedElements) => {
3378
3450
  let result = [];
3379
- const selectedMindNodes = selectedElements.map((value) => MindElement.getNode(value));
3451
+ const selectedMindNodes = selectedElements.map(value => MindElement.getNode(value));
3380
3452
  const nodesRectangle = getRectangleByElements(board, selectedElements, true);
3381
3453
  selectedElements.forEach((node, index) => {
3382
3454
  const nodeRectangle = getRectangleByNode(selectedMindNodes[index]);
@@ -3415,12 +3487,18 @@ const insertClipboardData = (board, elements, targetPoint) => {
3415
3487
  if (item.isRoot) {
3416
3488
  newElement = transformRootToNode(board, newElement);
3417
3489
  }
3490
+ if (AbstractNode.isAbstract(item)) {
3491
+ newElement = transformAbstractToNode(newElement);
3492
+ }
3418
3493
  const selectedElementPath = PlaitBoard.findPath(board, selectedElements[0]);
3419
3494
  path = selectedElementPath.concat((selectedElements[0].children || []).length + index);
3420
3495
  }
3421
3496
  else {
3422
3497
  const point = [targetPoint[0] + item.points[0][0], targetPoint[1] + item.points[0][1]];
3423
3498
  newElement.points = [point];
3499
+ if (AbstractNode.isAbstract(item)) {
3500
+ newElement = transformAbstractToNode(newElement);
3501
+ }
3424
3502
  if (!item.isRoot) {
3425
3503
  newElement = transformNodeToRoot(board, newElement);
3426
3504
  }
@@ -3439,15 +3517,6 @@ const insertClipboardText = (board, parentElement, text, width, height) => {
3439
3517
  return;
3440
3518
  };
3441
3519
 
3442
- function findNewChildNodePath(board, element) {
3443
- const path = PlaitBoard.findPath(board, element);
3444
- return path.concat((element.children || []).length);
3445
- }
3446
- function findNewSiblingNodePath(board, element) {
3447
- const path = PlaitBoard.findPath(board, element);
3448
- return Path$1.next(path);
3449
- }
3450
-
3451
3520
  const withEmoji = (board) => {
3452
3521
  const newBoard = board;
3453
3522
  newBoard.drawEmoji = (emoji, element) => {
@@ -3535,7 +3604,7 @@ const withAbstract = (board) => {
3535
3604
  };
3536
3605
  board.mouseup = (event) => {
3537
3606
  startPoint = undefined;
3538
- abstractHandlePosition = null;
3607
+ abstractHandlePosition = undefined;
3539
3608
  if (activeAbstractElement) {
3540
3609
  if (newBoard?.abstractResize) {
3541
3610
  newBoard.abstractResize(AbstractResizeState.end);
@@ -3618,6 +3687,7 @@ const withMind = (board) => {
3618
3687
  if (shouldChangeRightNodeCount(selectedElement)) {
3619
3688
  changeRightNodeCount(board, selectedElementPath.slice(0, 1), 1);
3620
3689
  }
3690
+ insertSiblingElementHandleAbstract(board, selectedElement);
3621
3691
  insertMindElement(board, selectedElement, findNewSiblingNodePath(board, selectedElement));
3622
3692
  }
3623
3693
  return;
@@ -3734,13 +3804,15 @@ class MindEmojiBaseComponent {
3734
3804
  }
3735
3805
  }
3736
3806
  MindEmojiBaseComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindEmojiBaseComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
3737
- MindEmojiBaseComponent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.5", type: MindEmojiBaseComponent, inputs: { fontSize: "fontSize", emojiItem: "emojiItem" }, ngImport: i0 });
3807
+ MindEmojiBaseComponent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.5", type: MindEmojiBaseComponent, inputs: { fontSize: "fontSize", emojiItem: "emojiItem", board: "board" }, ngImport: i0 });
3738
3808
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: MindEmojiBaseComponent, decorators: [{
3739
3809
  type: Directive
3740
3810
  }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { fontSize: [{
3741
3811
  type: Input
3742
3812
  }], emojiItem: [{
3743
3813
  type: Input
3814
+ }], board: [{
3815
+ type: Input
3744
3816
  }] } });
3745
3817
 
3746
3818
  /*
@@ -3751,5 +3823,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImpor
3751
3823
  * Generated bundle index. Do not edit.
3752
3824
  */
3753
3825
 
3754
- export { ABSTRACT_HANDLE_COLOR, ABSTRACT_HANDLE_LENGTH, ABSTRACT_HANDLE_MASK_WIDTH, ABSTRACT_INCLUDED_OUTLINE_OFFSET, AbstractHandlePosition, AbstractResizeState, BASE, COLORS, ELEMENT_TO_NODE, EXTEND_OFFSET, EXTEND_RADIUS, GRAY_COLOR, LayoutDirection, LayoutDirectionsMap, MAX_RADIUS, MINDMAP_KEY, MindElement, MindEmojiBaseComponent, MindModule, MindNode, MindNodeComponent, MindNodeShape, MindQueries, MindTransforms, 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, canSetAbstract, changeRightNodeCount, copyNewNode, correctLayoutByDirection, createDefaultMindMapElement, createMindElement, deleteSelectedELements, directionCorrector, directionDetector, divideElementByParent, drawCurvePlaceholderDropNodeG, drawIndentNodeG, drawPlaceholderDropNodeG, drawStraightDropNodeG, extractNodesText, filterChildElement, findLastChild, findLocationLeftIndex, findParentElement, findUpElement, getAbstractHandleRectangle, getAllowedDirection, getAvailableSubLayoutsByLayoutDirections, getBranchDirectionsByLayouts, getChildrenCount, getDefaultLayout, getEmojiFontSize, getEmojisRectangle, getHitAbstractHandle, getHorizontalFakeY, getInCorrectLayoutDirection, getIndentedFakePoint, getLayoutDirection$1 as getLayoutDirection, getLayoutReverseDirection, getLinkLineColorByMindElement, getLocationScope, getNodeShapeByElement, getRectangleByNode, getRectangleByResizingLocation, getRootLayout, getRootLinkLineColorByMindElement, getStrokeByMindElement, handleTouchedAbstract, hitMindElement, insertMindElement, insetAbstractNode, isChildElement, isChildRight, isChildUp, isCorrectLayout, isMixedLayout, isSetAbstract, isVirtualKey, readjustmentDropTarget, separateChildren, setAbstract, setAbstractByElements, shouldChangeRightNodeCount, transformNodeToRoot, transformRootToNode, withEmoji, withMind };
3826
+ export { ABSTRACT_HANDLE_COLOR, ABSTRACT_HANDLE_LENGTH, ABSTRACT_HANDLE_MASK_WIDTH, ABSTRACT_INCLUDED_OUTLINE_OFFSET, AbstractHandlePosition, AbstractResizeState, BASE, COLORS, ELEMENT_TO_NODE, EXTEND_OFFSET, EXTEND_RADIUS, GRAY_COLOR, LayoutDirection, LayoutDirectionsMap, MAX_RADIUS, MINDMAP_KEY, MindElement, MindEmojiBaseComponent, MindModule, MindNode, MindNodeComponent, MindNodeShape, MindQueries, MindTransforms, 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, canSetAbstract, changeRightNodeCount, copyNewNode, correctLayoutByDirection, createDefaultMindMapElement, createMindElement, deleteSelectedELements, directionCorrector, directionDetector, divideElementByParent, drawCurvePlaceholderDropNodeG, drawIndentNodeG, drawPlaceholderDropNodeG, drawStraightDropNodeG, extractNodesText, filterChildElement, findLastChild, findLocationLeftIndex, findParentElement, findUpElement, getAbstractHandleRectangle, getAllowedDirection, getAvailableSubLayoutsByLayoutDirections, getBehindAbstracts, getBranchColorByMindElement, getBranchDirectionsByLayouts, getChildrenCount, getCorrespondingAbstract, getDefaultLayout, getEmojiFontSize, getEmojisRectangle, getHitAbstractHandle, getHorizontalFakeY, getInCorrectLayoutDirection, getIndentedFakePoint, getLayoutDirection$1 as getLayoutDirection, getLayoutReverseDirection, getLocationScope, getNextBranchColor, getNodeShapeByElement, getRectangleByNode, getRectangleByResizingLocation, getRootLayout, getStrokeByMindElement, handleAbstractIncluded, handleTouchedAbstract, hitMindElement, insertAbstractNode, insertMindElement, insertSiblingElementHandleAbstract, isChildElement, isChildRight, isChildUp, isCorrectLayout, isMixedLayout, isSetAbstract, isVirtualKey, moveAbstractPosition, readjustmentDropTarget, separateChildren, setAbstract, setAbstractByElements, shouldChangeRightNodeCount, transformAbstractToNode, transformNodeToRoot, transformRootToNode, withEmoji, withMind };
3755
3827
  //# sourceMappingURL=plait-mind.mjs.map