@plait/core 0.53.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/board/board.component.d.ts +5 -2
- package/constants/index.d.ts +2 -0
- package/constants/selection.d.ts +1 -0
- package/core/element/context.d.ts +6 -2
- package/core/element/plugin-element.d.ts +13 -4
- package/core/list-render.d.ts +16 -0
- package/esm2022/board/board.component.mjs +28 -23
- package/esm2022/constants/index.mjs +3 -1
- package/esm2022/constants/selection.mjs +2 -1
- package/esm2022/core/element/context.mjs +1 -1
- package/esm2022/core/element/plugin-element.mjs +79 -12
- package/esm2022/core/list-render.mjs +209 -0
- package/esm2022/interfaces/board.mjs +6 -2
- package/esm2022/interfaces/element.mjs +28 -2
- package/esm2022/interfaces/node.mjs +18 -1
- package/esm2022/interfaces/path.mjs +56 -57
- package/esm2022/interfaces/rectangle-client.mjs +4 -1
- package/esm2022/interfaces/selection.mjs +2 -2
- package/esm2022/plugins/create-board.mjs +15 -13
- package/esm2022/plugins/with-hotkey.mjs +33 -4
- package/esm2022/plugins/with-moving.mjs +15 -14
- package/esm2022/plugins/with-related-fragment.mjs +5 -5
- package/esm2022/plugins/with-selection.mjs +7 -5
- package/esm2022/public-api.mjs +1 -3
- package/esm2022/transforms/element.mjs +2 -2
- package/esm2022/transforms/group.mjs +23 -6
- package/esm2022/transforms/index.mjs +6 -3
- package/esm2022/transforms/z-index.mjs +20 -0
- package/esm2022/utils/angle.mjs +56 -3
- package/esm2022/utils/clipboard/clipboard.mjs +5 -5
- package/esm2022/utils/clipboard/common.mjs +5 -5
- package/esm2022/utils/clipboard/types.mjs +1 -1
- package/esm2022/utils/common.mjs +29 -2
- package/esm2022/utils/debug.mjs +16 -1
- package/esm2022/utils/element.mjs +3 -3
- package/esm2022/utils/fragment.mjs +24 -0
- package/esm2022/utils/group.mjs +37 -5
- package/esm2022/utils/helper.mjs +37 -1
- package/esm2022/utils/index.mjs +5 -1
- package/esm2022/utils/math.mjs +37 -1
- package/esm2022/utils/position.mjs +3 -3
- package/esm2022/utils/selection.mjs +2 -3
- package/esm2022/utils/snap/snap-moving.mjs +199 -0
- package/esm2022/utils/snap/snap.mjs +208 -0
- package/esm2022/utils/to-image.mjs +2 -2
- package/esm2022/utils/weak-maps.mjs +4 -1
- package/esm2022/utils/z-index.mjs +166 -0
- package/fesm2022/plait-core.mjs +2110 -1458
- package/fesm2022/plait-core.mjs.map +1 -1
- package/interfaces/board.d.ts +9 -7
- package/interfaces/element.d.ts +5 -0
- package/interfaces/node.d.ts +1 -0
- package/interfaces/rectangle-client.d.ts +1 -0
- package/interfaces/selection.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +0 -2
- package/styles/styles.scss +9 -0
- package/transforms/group.d.ts +4 -0
- package/transforms/index.d.ts +3 -2
- package/transforms/z-index.d.ts +13 -0
- package/utils/angle.d.ts +8 -1
- package/utils/clipboard/common.d.ts +1 -1
- package/utils/clipboard/types.d.ts +1 -1
- package/utils/common.d.ts +8 -0
- package/utils/debug.d.ts +1 -0
- package/utils/fragment.d.ts +4 -0
- package/utils/group.d.ts +4 -2
- package/utils/helper.d.ts +4 -1
- package/utils/index.d.ts +4 -0
- package/utils/math.d.ts +1 -0
- package/utils/position.d.ts +1 -1
- package/utils/selection.d.ts +1 -1
- package/utils/snap/snap-moving.d.ts +5 -0
- package/utils/snap/snap.d.ts +31 -0
- package/utils/weak-maps.d.ts +3 -0
- package/utils/z-index.d.ts +5 -0
- package/core/children/children.component.d.ts +0 -17
- package/core/children/effect.d.ts +0 -2
- package/core/element/element.component.d.ts +0 -30
- package/esm2022/core/children/children.component.mjs +0 -60
- package/esm2022/core/children/effect.mjs +0 -2
- package/esm2022/core/element/element.component.mjs +0 -105
- package/esm2022/utils/reaction-manager.mjs +0 -371
- package/utils/reaction-manager.d.ts +0 -41
package/fesm2022/plait-core.mjs
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Directive, Input, Injectable,
|
|
2
|
+
import { IterableDiffers, inject, ViewContainerRef, Directive, Input, Injectable, EventEmitter, ElementRef, Component, ChangeDetectionStrategy, Output, HostBinding, ViewChild, ContentChildren } from '@angular/core';
|
|
3
3
|
import rough from 'roughjs/bin/rough';
|
|
4
4
|
import { timer, Subject, fromEvent } from 'rxjs';
|
|
5
5
|
import { takeUntil, filter, tap } from 'rxjs/operators';
|
|
6
6
|
import { isKeyHotkey, isHotkey } from 'is-hotkey';
|
|
7
|
-
import
|
|
8
|
-
import { NgFor } from '@angular/common';
|
|
7
|
+
import { createDraft, finishDraft, isDraft } from 'immer';
|
|
9
8
|
|
|
10
9
|
// record richtext type status
|
|
11
10
|
const IS_BOARD_CACHE = new WeakMap();
|
|
12
11
|
const FLUSHING = new WeakMap();
|
|
13
12
|
const NODE_TO_INDEX = new WeakMap();
|
|
14
13
|
const NODE_TO_PARENT = new WeakMap();
|
|
14
|
+
const NODE_TO_G = new WeakMap();
|
|
15
|
+
const NODE_TO_CONTAINER_G = new WeakMap();
|
|
15
16
|
const IS_TEXT_EDITABLE = new WeakMap();
|
|
16
17
|
const BOARD_TO_ON_CHANGE = new WeakMap();
|
|
17
18
|
const BOARD_TO_AFTER_CHANGE = new WeakMap();
|
|
18
19
|
const BOARD_TO_COMPONENT = new WeakMap();
|
|
19
20
|
const BOARD_TO_ROUGH_SVG = new WeakMap();
|
|
20
21
|
const BOARD_TO_HOST = new WeakMap();
|
|
22
|
+
const IS_BOARD_ALIVE = new WeakMap();
|
|
21
23
|
const BOARD_TO_ELEMENT_HOST = new WeakMap();
|
|
22
24
|
const BOARD_TO_SELECTED_ELEMENT = new WeakMap();
|
|
23
25
|
const BOARD_TO_MOVING_POINT_IN_BOARD = new WeakMap();
|
|
@@ -64,7 +66,7 @@ const getIsRecursionFunc = (board) => {
|
|
|
64
66
|
};
|
|
65
67
|
|
|
66
68
|
const SELECTION_BORDER_COLOR = '#6698FF';
|
|
67
|
-
const SELECTION_FILL_COLOR = '#
|
|
69
|
+
const SELECTION_FILL_COLOR = '#6698FF25'; // opacity 0.25
|
|
68
70
|
const Selection = {
|
|
69
71
|
isCollapsed(selection) {
|
|
70
72
|
if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
|
|
@@ -76,11 +78,11 @@ const Selection = {
|
|
|
76
78
|
}
|
|
77
79
|
};
|
|
78
80
|
|
|
79
|
-
const sortElements = (board, elements) => {
|
|
81
|
+
const sortElements = (board, elements, ascendingOrder = true) => {
|
|
80
82
|
return [...elements].sort((a, b) => {
|
|
81
83
|
const pathA = PlaitBoard.findPath(board, a);
|
|
82
84
|
const pathB = PlaitBoard.findPath(board, b);
|
|
83
|
-
return pathA[0] - pathB[0];
|
|
85
|
+
return ascendingOrder ? pathA[0] - pathB[0] : pathB[0] - pathA[0];
|
|
84
86
|
});
|
|
85
87
|
};
|
|
86
88
|
|
|
@@ -185,6 +187,9 @@ const RectangleClient = {
|
|
|
185
187
|
getCenterPoint: (rectangle) => {
|
|
186
188
|
return [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
|
|
187
189
|
},
|
|
190
|
+
getCenterPointByPoints: (points) => {
|
|
191
|
+
return RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(points));
|
|
192
|
+
},
|
|
188
193
|
getEdgeCenterPoints: (rectangle) => {
|
|
189
194
|
return [
|
|
190
195
|
[rectangle.x + rectangle.width / 2, rectangle.y],
|
|
@@ -497,10 +502,13 @@ const RESIZE_CURSORS = [ResizeCursorClass.ns, ResizeCursorClass.nesw, ResizeCurs
|
|
|
497
502
|
|
|
498
503
|
const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
|
|
499
504
|
const ACTIVE_STROKE_WIDTH = 1;
|
|
505
|
+
const SNAPPING_STROKE_WIDTH = 2;
|
|
500
506
|
const SELECTION_RECTANGLE_CLASS_NAME = 'selection-rectangle';
|
|
501
507
|
|
|
502
508
|
const HOST_CLASS_NAME = 'plait-board-container';
|
|
503
509
|
const ACTIVE_MOVING_CLASS_NAME = 'active-with-moving';
|
|
510
|
+
const ROTATE_HANDLE_CLASS_NAME = 'rotate-handle';
|
|
511
|
+
const RESIZE_HANDLE_CLASS_NAME = 'resize-handle';
|
|
504
512
|
const SCROLL_BAR_WIDTH = 20;
|
|
505
513
|
const MAX_RADIUS = 16;
|
|
506
514
|
const POINTER_BUTTON = {
|
|
@@ -616,7 +624,215 @@ function hasOnContextChanged(value) {
|
|
|
616
624
|
return false;
|
|
617
625
|
}
|
|
618
626
|
|
|
627
|
+
class ListRender {
|
|
628
|
+
constructor(board, viewContainerRef) {
|
|
629
|
+
this.board = board;
|
|
630
|
+
this.viewContainerRef = viewContainerRef;
|
|
631
|
+
this.children = [];
|
|
632
|
+
this.componentRefs = [];
|
|
633
|
+
this.contexts = [];
|
|
634
|
+
this.differ = null;
|
|
635
|
+
this.initialized = false;
|
|
636
|
+
}
|
|
637
|
+
initialize(children, childrenContext) {
|
|
638
|
+
this.initialized = true;
|
|
639
|
+
this.children = children;
|
|
640
|
+
children.forEach((descendant, index) => {
|
|
641
|
+
NODE_TO_INDEX.set(descendant, index);
|
|
642
|
+
NODE_TO_PARENT.set(descendant, childrenContext.parent);
|
|
643
|
+
const context = getContext(this.board, descendant, index, childrenContext.parent);
|
|
644
|
+
const componentType = getComponentType(this.board, context);
|
|
645
|
+
const componentRef = createPluginComponent(componentType, context, this.viewContainerRef, childrenContext);
|
|
646
|
+
this.componentRefs.push(componentRef);
|
|
647
|
+
this.contexts.push(context);
|
|
648
|
+
});
|
|
649
|
+
const newDiffers = this.viewContainerRef.injector.get(IterableDiffers);
|
|
650
|
+
this.differ = newDiffers.find(children).create(trackBy);
|
|
651
|
+
this.differ.diff(children);
|
|
652
|
+
}
|
|
653
|
+
update(children, childrenContext) {
|
|
654
|
+
if (!this.initialized) {
|
|
655
|
+
this.initialize(children, childrenContext);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (!this.differ) {
|
|
659
|
+
throw new Error('Exception: Can not find differ ');
|
|
660
|
+
}
|
|
661
|
+
const { board, parent } = childrenContext;
|
|
662
|
+
const diffResult = this.differ.diff(children);
|
|
663
|
+
if (diffResult) {
|
|
664
|
+
const newContexts = [];
|
|
665
|
+
const newComponentRefs = [];
|
|
666
|
+
let currentIndexForFirstElement = null;
|
|
667
|
+
diffResult.forEachItem((record) => {
|
|
668
|
+
NODE_TO_INDEX.set(record.item, record.currentIndex);
|
|
669
|
+
NODE_TO_PARENT.set(record.item, childrenContext.parent);
|
|
670
|
+
const previousContext = record.previousIndex === null ? undefined : this.contexts[record.previousIndex];
|
|
671
|
+
const context = getContext(board, record.item, record.currentIndex, parent, previousContext);
|
|
672
|
+
if (record.previousIndex === null) {
|
|
673
|
+
const componentType = getComponentType(board, context);
|
|
674
|
+
const componentRef = createPluginComponent(componentType, context, this.viewContainerRef, childrenContext);
|
|
675
|
+
newContexts.push(context);
|
|
676
|
+
newComponentRefs.push(componentRef);
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
const componentRef = this.componentRefs[record.previousIndex];
|
|
680
|
+
componentRef.instance.context = context;
|
|
681
|
+
newComponentRefs.push(componentRef);
|
|
682
|
+
newContexts.push(context);
|
|
683
|
+
}
|
|
684
|
+
if (record.item === this.children[0]) {
|
|
685
|
+
currentIndexForFirstElement = record.currentIndex;
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
diffResult.forEachOperation(record => {
|
|
689
|
+
// removed
|
|
690
|
+
if (record.currentIndex === null) {
|
|
691
|
+
const componentRef = this.componentRefs[record.previousIndex];
|
|
692
|
+
componentRef?.destroy();
|
|
693
|
+
}
|
|
694
|
+
// moved
|
|
695
|
+
if (record.previousIndex !== null && record.currentIndex !== null) {
|
|
696
|
+
mountOnItemMove(record.item, record.currentIndex, childrenContext, currentIndexForFirstElement);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
this.componentRefs = newComponentRefs;
|
|
700
|
+
this.contexts = newContexts;
|
|
701
|
+
this.children = children;
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
const newContexts = [];
|
|
705
|
+
this.children.forEach((element, index) => {
|
|
706
|
+
NODE_TO_INDEX.set(element, index);
|
|
707
|
+
NODE_TO_PARENT.set(element, childrenContext.parent);
|
|
708
|
+
const previousContext = this.contexts[index];
|
|
709
|
+
const previousComponentRef = this.componentRefs[index];
|
|
710
|
+
const context = getContext(board, element, index, parent, previousContext);
|
|
711
|
+
previousComponentRef.instance.context = context;
|
|
712
|
+
newContexts.push(context);
|
|
713
|
+
});
|
|
714
|
+
this.contexts = newContexts;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
destroy() {
|
|
718
|
+
this.children.forEach((element, index) => {
|
|
719
|
+
if (this.componentRefs[index]) {
|
|
720
|
+
this.componentRefs[index].destroy();
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
this.componentRefs = [];
|
|
724
|
+
this.children = [];
|
|
725
|
+
this.contexts = [];
|
|
726
|
+
this.initialized = false;
|
|
727
|
+
this.differ = null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const trackBy = (index, element) => {
|
|
731
|
+
return element.id;
|
|
732
|
+
};
|
|
733
|
+
const createPluginComponent = (componentType, context, viewContainerRef, childrenContext) => {
|
|
734
|
+
const componentRef = viewContainerRef.createComponent(componentType);
|
|
735
|
+
const instance = componentRef.instance;
|
|
736
|
+
instance.context = context;
|
|
737
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
738
|
+
const g = componentRef.instance.getContainerG();
|
|
739
|
+
mountElementG(context.index, g, childrenContext);
|
|
740
|
+
componentRef.instance.initializeListRender();
|
|
741
|
+
return componentRef;
|
|
742
|
+
};
|
|
743
|
+
const getComponentType = (board, context) => {
|
|
744
|
+
const result = board.drawElement(context);
|
|
745
|
+
return result;
|
|
746
|
+
};
|
|
747
|
+
const getContext = (board, element, index, parent, previousContext) => {
|
|
748
|
+
let isSelected = isSelectedElement(board, element);
|
|
749
|
+
const previousElement = previousContext && previousContext.element;
|
|
750
|
+
if (previousElement && previousElement !== element && isSelectedElement(board, previousElement)) {
|
|
751
|
+
isSelected = true;
|
|
752
|
+
removeSelectedElement(board, previousElement);
|
|
753
|
+
addSelectedElement(board, element);
|
|
754
|
+
}
|
|
755
|
+
const context = {
|
|
756
|
+
element: element,
|
|
757
|
+
parent: parent,
|
|
758
|
+
board: board,
|
|
759
|
+
selected: isSelected,
|
|
760
|
+
index
|
|
761
|
+
};
|
|
762
|
+
return context;
|
|
763
|
+
};
|
|
764
|
+
// the g depth of root element:[1]-[2]-[3]-[4]
|
|
765
|
+
// the g depth of root element and children element(the [2] element has children):
|
|
766
|
+
// [1]-
|
|
767
|
+
// [2]([2-1-1][2-1-2][2-1][2-2][2-3-1][2-3-2][2-3][2])-
|
|
768
|
+
// [3]-
|
|
769
|
+
// [4]
|
|
770
|
+
const mountElementG = (index, g, childrenContext,
|
|
771
|
+
// for moving scene: the current index for first element before moving
|
|
772
|
+
currentIndexForFirstElement = null) => {
|
|
773
|
+
const { parent, parentG } = childrenContext;
|
|
774
|
+
if (PlaitBoard.isBoard(parent)) {
|
|
775
|
+
if (index > 0) {
|
|
776
|
+
const previousElement = parent.children[index - 1];
|
|
777
|
+
const previousContainerG = PlaitElement.getContainerG(previousElement, { suppressThrow: false });
|
|
778
|
+
previousContainerG.insertAdjacentElement('afterend', g);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
if (currentIndexForFirstElement !== null) {
|
|
782
|
+
const firstElement = parent.children[currentIndexForFirstElement];
|
|
783
|
+
const firstContainerG = firstElement && PlaitElement.getContainerG(firstElement, { suppressThrow: true });
|
|
784
|
+
if (firstElement && firstContainerG) {
|
|
785
|
+
parentG.insertBefore(g, firstContainerG);
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
throw new Error('fail to mount container on moving');
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
parentG.append(g);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
if (index > 0) {
|
|
798
|
+
const previousElement = parent.children[index - 1];
|
|
799
|
+
const previousElementG = PlaitElement.getElementG(previousElement);
|
|
800
|
+
previousElementG.insertAdjacentElement('afterend', g);
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
if (currentIndexForFirstElement) {
|
|
804
|
+
const nextElement = parent.children[currentIndexForFirstElement];
|
|
805
|
+
const nextPath = nextElement && PlaitBoard.findPath(childrenContext.board, nextElement);
|
|
806
|
+
const first = nextPath && PlaitNode.first(childrenContext.board, nextPath);
|
|
807
|
+
const firstContainerG = first && PlaitElement.getContainerG(first, { suppressThrow: false });
|
|
808
|
+
if (firstContainerG) {
|
|
809
|
+
parentG.insertBefore(g, firstContainerG);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
throw new Error('fail to mount container on moving');
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
let parentElementG = PlaitElement.getElementG(parent);
|
|
817
|
+
parentG.insertBefore(g, parentElementG);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
const mountOnItemMove = (element, index, childrenContext, currentIndexForFirstElement) => {
|
|
823
|
+
const containerG = PlaitElement.getContainerG(element, { suppressThrow: false });
|
|
824
|
+
mountElementG(index, containerG, childrenContext, currentIndexForFirstElement);
|
|
825
|
+
if (element.children && !PlaitElement.isRootElement(element)) {
|
|
826
|
+
element.children.forEach((child, index) => {
|
|
827
|
+
mountOnItemMove(child, index, { ...childrenContext, parent: element }, null);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
|
|
619
832
|
class PlaitPluginElementComponent {
|
|
833
|
+
get hasChildren() {
|
|
834
|
+
return !!this.element.children;
|
|
835
|
+
}
|
|
620
836
|
set context(value) {
|
|
621
837
|
if (hasBeforeContextChange(this)) {
|
|
622
838
|
this.beforeContextChange(value);
|
|
@@ -627,20 +843,28 @@ class PlaitPluginElementComponent {
|
|
|
627
843
|
ELEMENT_TO_COMPONENT.set(this.element, this);
|
|
628
844
|
}
|
|
629
845
|
if (this.initialized) {
|
|
846
|
+
const elementG = this.getElementG();
|
|
847
|
+
const containerG = this.getContainerG();
|
|
848
|
+
NODE_TO_G.set(this.element, elementG);
|
|
849
|
+
NODE_TO_CONTAINER_G.set(this.element, containerG);
|
|
850
|
+
this.updateListRender();
|
|
630
851
|
this.cdr.markForCheck();
|
|
631
852
|
if (hasOnContextChanged(this)) {
|
|
632
853
|
this.onContextChanged(value, previousContext);
|
|
633
854
|
}
|
|
634
855
|
}
|
|
635
856
|
else {
|
|
636
|
-
if (PlaitElement.isRootElement(this.element) && this.
|
|
637
|
-
this.
|
|
638
|
-
this.
|
|
639
|
-
this.
|
|
857
|
+
if (PlaitElement.isRootElement(this.element) && this.hasChildren) {
|
|
858
|
+
this._g = createG();
|
|
859
|
+
this._containerG = createG();
|
|
860
|
+
this._containerG.append(this._g);
|
|
640
861
|
}
|
|
641
862
|
else {
|
|
642
|
-
this.
|
|
863
|
+
this._g = createG();
|
|
864
|
+
this._containerG = this._g;
|
|
643
865
|
}
|
|
866
|
+
NODE_TO_G.set(this.element, this._g);
|
|
867
|
+
NODE_TO_CONTAINER_G.set(this.element, this._containerG);
|
|
644
868
|
}
|
|
645
869
|
}
|
|
646
870
|
get context() {
|
|
@@ -655,25 +879,79 @@ class PlaitPluginElementComponent {
|
|
|
655
879
|
get selected() {
|
|
656
880
|
return this.context && this.context.selected;
|
|
657
881
|
}
|
|
658
|
-
|
|
659
|
-
return this.
|
|
882
|
+
getContainerG() {
|
|
883
|
+
return this._containerG;
|
|
884
|
+
}
|
|
885
|
+
getElementG() {
|
|
886
|
+
return this._g;
|
|
660
887
|
}
|
|
661
888
|
constructor(cdr) {
|
|
662
889
|
this.cdr = cdr;
|
|
890
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
663
891
|
this.initialized = false;
|
|
664
892
|
}
|
|
665
893
|
ngOnInit() {
|
|
666
894
|
if (this.element.type) {
|
|
667
|
-
|
|
895
|
+
this.getContainerG().setAttribute(`plait-${this.element.type}`, 'true');
|
|
896
|
+
}
|
|
897
|
+
if (this.hasChildren) {
|
|
898
|
+
if (PlaitElement.isRootElement(this.element)) {
|
|
899
|
+
this._rootContainerG = this._containerG;
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
const path = PlaitBoard.findPath(this.board, this.element);
|
|
903
|
+
const rootNode = PlaitNode.get(this.board, path.slice(0, 1));
|
|
904
|
+
this._rootContainerG = PlaitElement.getContainerG(rootNode, { suppressThrow: false });
|
|
905
|
+
}
|
|
668
906
|
}
|
|
907
|
+
this.getContainerG().setAttribute('plait-data-id', this.element.id);
|
|
669
908
|
this.initialized = true;
|
|
670
909
|
}
|
|
910
|
+
initializeListRender() {
|
|
911
|
+
if (this.hasChildren) {
|
|
912
|
+
this.listRender = new ListRender(this.board, this.viewContainerRef);
|
|
913
|
+
if (this.board.isExpanded(this.element)) {
|
|
914
|
+
this.listRender.initialize(this.element.children, this.initializeChildrenContext());
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
updateListRender() {
|
|
919
|
+
if (this.hasChildren) {
|
|
920
|
+
if (!this.listRender) {
|
|
921
|
+
throw new Error('incorrectly initialize list render');
|
|
922
|
+
}
|
|
923
|
+
if (this.board.isExpanded(this.element)) {
|
|
924
|
+
this.listRender.update(this.element.children, this.initializeChildrenContext());
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
if (this.listRender.initialized) {
|
|
928
|
+
this.listRender.destroy();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
initializeChildrenContext() {
|
|
934
|
+
if (!this._rootContainerG) {
|
|
935
|
+
throw new Error('can not resolve root container g');
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
board: this.board,
|
|
939
|
+
parent: this.element,
|
|
940
|
+
parentG: this._rootContainerG
|
|
941
|
+
};
|
|
942
|
+
}
|
|
671
943
|
ngOnDestroy() {
|
|
672
944
|
if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
|
|
673
945
|
ELEMENT_TO_COMPONENT.delete(this.element);
|
|
674
946
|
}
|
|
947
|
+
if (NODE_TO_G.get(this.element) === this._g) {
|
|
948
|
+
NODE_TO_G.delete(this.element);
|
|
949
|
+
}
|
|
950
|
+
if (NODE_TO_CONTAINER_G.get(this.element) === this._containerG) {
|
|
951
|
+
NODE_TO_CONTAINER_G.delete(this.element);
|
|
952
|
+
}
|
|
675
953
|
removeSelectedElement(this.board, this.element);
|
|
676
|
-
|
|
954
|
+
this.getContainerG().remove();
|
|
677
955
|
}
|
|
678
956
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitPluginElementComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
679
957
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.4", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
|
|
@@ -963,6 +1241,42 @@ function toFixed(v) {
|
|
|
963
1241
|
function approximately(a, b, precision = 0.000001) {
|
|
964
1242
|
return Math.abs(a - b) <= precision;
|
|
965
1243
|
}
|
|
1244
|
+
// https://medium.com/@steveruiz/find-the-points-where-a-line-segment-intercepts-an-angled-ellipse-in-javascript-typescript-e451524beece
|
|
1245
|
+
function getCrossingPointsBetweenEllipseAndSegment(startPoint, endPoint, cx, cy, rx, ry, segment_only = true) {
|
|
1246
|
+
// If the ellipse or line segment are empty, return no tValues.
|
|
1247
|
+
if (rx === 0 || ry === 0 || (startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1])) {
|
|
1248
|
+
return [];
|
|
1249
|
+
}
|
|
1250
|
+
rx = rx < 0 ? rx : -rx;
|
|
1251
|
+
ry = ry < 0 ? ry : -ry;
|
|
1252
|
+
startPoint[0] -= cx;
|
|
1253
|
+
startPoint[1] -= cy;
|
|
1254
|
+
endPoint[0] -= cx;
|
|
1255
|
+
endPoint[1] -= cy;
|
|
1256
|
+
// Calculate the quadratic parameters.
|
|
1257
|
+
var A = ((endPoint[0] - startPoint[0]) * (endPoint[0] - startPoint[0])) / rx / rx +
|
|
1258
|
+
((endPoint[1] - startPoint[1]) * (endPoint[1] - startPoint[1])) / ry / ry;
|
|
1259
|
+
var B = (2 * startPoint[0] * (endPoint[0] - startPoint[0])) / rx / rx + (2 * startPoint[1] * (endPoint[1] - startPoint[1])) / ry / ry;
|
|
1260
|
+
var C = (startPoint[0] * startPoint[0]) / rx / rx + (startPoint[1] * startPoint[1]) / ry / ry - 1;
|
|
1261
|
+
// Make a list of t values (normalized points on the line where intersections occur).
|
|
1262
|
+
var tValues = [];
|
|
1263
|
+
// Calculate the discriminant.
|
|
1264
|
+
var discriminant = B * B - 4 * A * C;
|
|
1265
|
+
if (discriminant === 0) {
|
|
1266
|
+
// One real solution.
|
|
1267
|
+
tValues.push(-B / 2 / A);
|
|
1268
|
+
}
|
|
1269
|
+
else if (discriminant > 0) {
|
|
1270
|
+
// Two real solutions.
|
|
1271
|
+
tValues.push((-B + Math.sqrt(discriminant)) / 2 / A);
|
|
1272
|
+
tValues.push((-B - Math.sqrt(discriminant)) / 2 / A);
|
|
1273
|
+
}
|
|
1274
|
+
return (tValues
|
|
1275
|
+
// Filter to only points that are on the segment.
|
|
1276
|
+
.filter(t => !segment_only || (t >= 0 && t <= 1))
|
|
1277
|
+
// Solve for points.
|
|
1278
|
+
.map(t => [startPoint[0] + (endPoint[0] - startPoint[0]) * t + cx, startPoint[1] + (endPoint[1] - startPoint[1]) * t + cy]));
|
|
1279
|
+
}
|
|
966
1280
|
|
|
967
1281
|
function isInPlaitBoard(board, x, y) {
|
|
968
1282
|
const plaitBoardElement = PlaitBoard.getBoardContainer(board);
|
|
@@ -1050,6 +1364,42 @@ function uniqueById(elements) {
|
|
|
1050
1364
|
});
|
|
1051
1365
|
return Array.from(uniqueMap.values());
|
|
1052
1366
|
}
|
|
1367
|
+
const findLastIndex = (array, cb, fromIndex = array.length - 1) => {
|
|
1368
|
+
if (fromIndex < 0) {
|
|
1369
|
+
fromIndex = array.length + fromIndex;
|
|
1370
|
+
}
|
|
1371
|
+
fromIndex = Math.min(array.length - 1, Math.max(fromIndex, 0));
|
|
1372
|
+
let index = fromIndex + 1;
|
|
1373
|
+
while (--index > -1) {
|
|
1374
|
+
if (cb(array[index], index, array)) {
|
|
1375
|
+
return index;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return -1;
|
|
1379
|
+
};
|
|
1380
|
+
const findIndex = (array, cb, fromIndex = 0) => {
|
|
1381
|
+
// fromIndex = 2
|
|
1382
|
+
if (fromIndex < 0) {
|
|
1383
|
+
fromIndex = array.length + fromIndex;
|
|
1384
|
+
}
|
|
1385
|
+
fromIndex = Math.min(array.length, Math.max(fromIndex, 0));
|
|
1386
|
+
let index = fromIndex - 1;
|
|
1387
|
+
while (++index < array.length) {
|
|
1388
|
+
if (cb(array[index], index, array)) {
|
|
1389
|
+
return index;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return -1;
|
|
1393
|
+
};
|
|
1394
|
+
const isIndicesContinuous = (indexes) => {
|
|
1395
|
+
indexes.sort((a, b) => a - b);
|
|
1396
|
+
for (let i = 1; i < indexes.length; i++) {
|
|
1397
|
+
if (indexes[i] !== indexes[i - 1] + 1) {
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return true;
|
|
1402
|
+
};
|
|
1053
1403
|
|
|
1054
1404
|
/**
|
|
1055
1405
|
* Check whether to merge an operation into the previous operation.
|
|
@@ -1689,157 +2039,456 @@ const setIsFromViewportChange = (board, state) => {
|
|
|
1689
2039
|
};
|
|
1690
2040
|
function scrollToRectangle(board, client) { }
|
|
1691
2041
|
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
}
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
value[key] = null;
|
|
1705
|
-
BOARD_TO_RAF.set(board, value);
|
|
1706
|
-
fn();
|
|
1707
|
-
});
|
|
1708
|
-
const state = getRAFState(board);
|
|
1709
|
-
state[key] = timerId;
|
|
1710
|
-
BOARD_TO_RAF.set(board, state);
|
|
1711
|
-
};
|
|
1712
|
-
let timerId = getTimerId(board, key);
|
|
1713
|
-
if (timerId !== null) {
|
|
1714
|
-
cancelAnimationFrame(timerId);
|
|
1715
|
-
}
|
|
1716
|
-
scheduleFunc();
|
|
1717
|
-
};
|
|
1718
|
-
const debounce = (func, wait, options) => {
|
|
1719
|
-
let timerSubscription = null;
|
|
1720
|
-
return () => {
|
|
1721
|
-
if (timerSubscription && !timerSubscription.closed) {
|
|
1722
|
-
timerSubscription.unsubscribe();
|
|
1723
|
-
timerSubscription = timer(wait).subscribe(() => {
|
|
1724
|
-
func();
|
|
1725
|
-
});
|
|
2042
|
+
const Path = {
|
|
2043
|
+
/**
|
|
2044
|
+
* Get a list of ancestor paths for a given path.
|
|
2045
|
+
*
|
|
2046
|
+
* The paths are sorted from shallowest to deepest ancestor. However, if the
|
|
2047
|
+
* `reverse: true` option is passed, they are reversed.
|
|
2048
|
+
*/
|
|
2049
|
+
ancestors(path, options = {}) {
|
|
2050
|
+
const { reverse = false } = options;
|
|
2051
|
+
let paths = Path.levels(path, options);
|
|
2052
|
+
if (reverse) {
|
|
2053
|
+
paths = paths.slice(1);
|
|
1726
2054
|
}
|
|
1727
2055
|
else {
|
|
1728
|
-
|
|
1729
|
-
timer(0).subscribe(() => {
|
|
1730
|
-
func();
|
|
1731
|
-
});
|
|
1732
|
-
}
|
|
1733
|
-
timerSubscription = timer(wait).subscribe();
|
|
2056
|
+
paths = paths.slice(0, -1);
|
|
1734
2057
|
}
|
|
1735
|
-
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2058
|
+
return paths;
|
|
2059
|
+
},
|
|
2060
|
+
/**
|
|
2061
|
+
* Get a list of paths at every level down to a path. Note: this is the same
|
|
2062
|
+
* as `Path.ancestors`, but including the path itself.
|
|
2063
|
+
*
|
|
2064
|
+
* The paths are sorted from shallowest to deepest. However, if the `reverse:
|
|
2065
|
+
* true` option is passed, they are reversed.
|
|
2066
|
+
*/
|
|
2067
|
+
levels(path, options = {}) {
|
|
2068
|
+
const { reverse = false } = options;
|
|
2069
|
+
const list = [];
|
|
2070
|
+
for (let i = 0; i <= path.length; i++) {
|
|
2071
|
+
list.push(path.slice(0, i));
|
|
2072
|
+
}
|
|
2073
|
+
if (reverse) {
|
|
2074
|
+
list.reverse();
|
|
2075
|
+
}
|
|
2076
|
+
return list;
|
|
2077
|
+
},
|
|
2078
|
+
parent(path) {
|
|
2079
|
+
if (path.length === 0) {
|
|
2080
|
+
throw new Error(`Cannot get the parent path of the root path [${path}].`);
|
|
2081
|
+
}
|
|
2082
|
+
return path.slice(0, -1);
|
|
2083
|
+
},
|
|
2084
|
+
next(path) {
|
|
2085
|
+
if (path.length === 0) {
|
|
2086
|
+
throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
|
|
2087
|
+
}
|
|
2088
|
+
const last = path[path.length - 1];
|
|
2089
|
+
return path.slice(0, -1).concat(last + 1);
|
|
2090
|
+
},
|
|
2091
|
+
hasPrevious(path) {
|
|
2092
|
+
return path[path.length - 1] > 0;
|
|
2093
|
+
},
|
|
2094
|
+
previous(path) {
|
|
2095
|
+
if (path.length === 0) {
|
|
2096
|
+
throw new Error(`Cannot get the previous path of a root path [${path}], because it has no previous index.`);
|
|
2097
|
+
}
|
|
2098
|
+
const last = path[path.length - 1];
|
|
2099
|
+
if (last <= 0) {
|
|
2100
|
+
throw new Error(`Cannot get the previous path of a first child path [${path}] because it would result in a negative index.`);
|
|
2101
|
+
}
|
|
2102
|
+
return path.slice(0, -1).concat(last - 1);
|
|
2103
|
+
},
|
|
2104
|
+
/**
|
|
2105
|
+
* Check if a path is an ancestor of another.
|
|
2106
|
+
*/
|
|
2107
|
+
isAncestor(path, another) {
|
|
2108
|
+
return path.length < another.length && Path.compare(path, another) === 0;
|
|
2109
|
+
},
|
|
2110
|
+
/**
|
|
2111
|
+
* Compare a path to another, returning an integer indicating whether the path
|
|
2112
|
+
* was before, at, or after the other.
|
|
2113
|
+
*
|
|
2114
|
+
* Note: Two paths of unequal length can still receive a `0` result if one is
|
|
2115
|
+
* directly above or below the other. If you want exact matching, use
|
|
2116
|
+
* [[Path.equals]] instead.
|
|
2117
|
+
*/
|
|
2118
|
+
compare(path, another) {
|
|
2119
|
+
const min = Math.min(path.length, another.length);
|
|
2120
|
+
for (let i = 0; i < min; i++) {
|
|
2121
|
+
if (path[i] < another[i])
|
|
2122
|
+
return -1;
|
|
2123
|
+
if (path[i] > another[i])
|
|
2124
|
+
return 1;
|
|
2125
|
+
}
|
|
2126
|
+
return 0;
|
|
2127
|
+
},
|
|
2128
|
+
/**
|
|
2129
|
+
* Check if a path is exactly equal to another.
|
|
2130
|
+
*/
|
|
2131
|
+
equals(path, another) {
|
|
2132
|
+
return path.length === another.length && path.every((n, i) => n === another[i]);
|
|
2133
|
+
},
|
|
2134
|
+
/**
|
|
2135
|
+
* Check if a path ends before one of the indexes in another.
|
|
2136
|
+
*/
|
|
2137
|
+
endsBefore(path, another) {
|
|
2138
|
+
const i = path.length - 1;
|
|
2139
|
+
const as = path.slice(0, i);
|
|
2140
|
+
const bs = another.slice(0, i);
|
|
2141
|
+
const av = path[i];
|
|
2142
|
+
const bv = another[i];
|
|
2143
|
+
return Path.equals(as, bs) && av < bv;
|
|
2144
|
+
},
|
|
2145
|
+
/**
|
|
2146
|
+
* Check if a path is a sibling of another.
|
|
2147
|
+
*/
|
|
2148
|
+
isSibling(path, another) {
|
|
2149
|
+
if (path.length !== another.length) {
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2152
|
+
const as = path.slice(0, -1);
|
|
2153
|
+
const bs = another.slice(0, -1);
|
|
2154
|
+
const al = path[path.length - 1];
|
|
2155
|
+
const bl = another[another.length - 1];
|
|
2156
|
+
return al !== bl && Path.equals(as, bs);
|
|
2157
|
+
},
|
|
2158
|
+
transform(path, operation) {
|
|
2159
|
+
if (!path)
|
|
2160
|
+
return null;
|
|
2161
|
+
// PERF: use destructing instead of immer
|
|
2162
|
+
const p = [...path];
|
|
2163
|
+
// PERF: Exit early if the operation is guaranteed not to have an effect.
|
|
2164
|
+
if (path.length === 0) {
|
|
2165
|
+
return p;
|
|
2166
|
+
}
|
|
2167
|
+
switch (operation.type) {
|
|
2168
|
+
case 'insert_node': {
|
|
2169
|
+
const { path: op } = operation;
|
|
2170
|
+
if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
|
|
2171
|
+
p[op.length - 1] += 1;
|
|
2172
|
+
}
|
|
2173
|
+
break;
|
|
2174
|
+
}
|
|
2175
|
+
case 'remove_node': {
|
|
2176
|
+
const { path: op } = operation;
|
|
2177
|
+
if (Path.equals(op, p) || Path.isAncestor(op, p)) {
|
|
2178
|
+
return null;
|
|
2179
|
+
}
|
|
2180
|
+
else if (Path.endsBefore(op, p)) {
|
|
2181
|
+
p[op.length - 1] -= 1;
|
|
2182
|
+
}
|
|
2183
|
+
break;
|
|
2184
|
+
}
|
|
2185
|
+
case 'move_node': {
|
|
2186
|
+
const { path: op, newPath: onp } = operation;
|
|
2187
|
+
// If the old and new path are the same, it's a no-op.
|
|
2188
|
+
if (Path.equals(op, onp)) {
|
|
2189
|
+
return p;
|
|
2190
|
+
}
|
|
2191
|
+
if (Path.isAncestor(op, p) || Path.equals(op, p)) {
|
|
2192
|
+
const copy = onp.slice();
|
|
2193
|
+
if (Path.endsBefore(op, onp) && op.length < onp.length) {
|
|
2194
|
+
copy[op.length - 1] -= 1;
|
|
2195
|
+
}
|
|
2196
|
+
return copy.concat(p.slice(op.length));
|
|
2197
|
+
}
|
|
2198
|
+
else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
|
|
2199
|
+
if (Path.endsBefore(op, p)) {
|
|
2200
|
+
p[op.length - 1] -= 1;
|
|
2201
|
+
}
|
|
2202
|
+
else {
|
|
2203
|
+
p[op.length - 1] += 1;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
|
|
2207
|
+
if (Path.endsBefore(op, p)) {
|
|
2208
|
+
p[op.length - 1] -= 1;
|
|
2209
|
+
}
|
|
2210
|
+
p[onp.length - 1] += 1;
|
|
2211
|
+
}
|
|
2212
|
+
else if (Path.endsBefore(op, p)) {
|
|
2213
|
+
if (Path.equals(onp, p)) {
|
|
2214
|
+
p[onp.length - 1] += 1;
|
|
2215
|
+
}
|
|
2216
|
+
p[op.length - 1] -= 1;
|
|
2217
|
+
}
|
|
2218
|
+
break;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return p;
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
|
|
2225
|
+
const PlaitNode = {
|
|
2226
|
+
parent: (board, path) => {
|
|
2227
|
+
const parentPath = Path.parent(path);
|
|
2228
|
+
const p = PlaitNode.get(board, parentPath);
|
|
2229
|
+
return p;
|
|
2230
|
+
},
|
|
2231
|
+
/**
|
|
2232
|
+
* Return a generator of all the ancestor nodes above a specific path.
|
|
2233
|
+
*
|
|
2234
|
+
* By default the order is top-down, from highest to lowest ancestor in
|
|
2235
|
+
* the tree, but you can pass the `reverse: true` option to go bottom-up.
|
|
2236
|
+
*/
|
|
2237
|
+
*parents(root, path, options = {}) {
|
|
2238
|
+
for (const p of Path.ancestors(path, options)) {
|
|
2239
|
+
const n = PlaitNode.get(root, p);
|
|
2240
|
+
yield n;
|
|
2241
|
+
}
|
|
2242
|
+
},
|
|
2243
|
+
get(root, path) {
|
|
2244
|
+
let node = root;
|
|
2245
|
+
for (let i = 0; i < path.length; i++) {
|
|
2246
|
+
const p = path[i];
|
|
2247
|
+
if (!node || !node.children || !node.children[p]) {
|
|
2248
|
+
throw new Error(`Cannot find a descendant at path [${path}]`);
|
|
2249
|
+
}
|
|
2250
|
+
node = node.children[p];
|
|
2251
|
+
}
|
|
2252
|
+
return node;
|
|
2253
|
+
},
|
|
2254
|
+
last(board, path) {
|
|
2255
|
+
let n = PlaitNode.get(board, path);
|
|
2256
|
+
while (n && n.children && n.children.length > 0) {
|
|
2257
|
+
const i = n.children.length - 1;
|
|
2258
|
+
n = n.children[i];
|
|
2259
|
+
}
|
|
2260
|
+
return n;
|
|
2261
|
+
},
|
|
2262
|
+
first(board, path) {
|
|
2263
|
+
const p = path.slice();
|
|
2264
|
+
let n = PlaitNode.get(board, p);
|
|
2265
|
+
if (!n.children) {
|
|
2266
|
+
return n;
|
|
2267
|
+
}
|
|
2268
|
+
while (n) {
|
|
2269
|
+
if (n.children.length === 0) {
|
|
2270
|
+
break;
|
|
2271
|
+
}
|
|
2272
|
+
else {
|
|
2273
|
+
n = n.children[0];
|
|
2274
|
+
p.push(0);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return n;
|
|
2278
|
+
}
|
|
2279
|
+
};
|
|
2280
|
+
|
|
2281
|
+
function insertNode(board, node, path) {
|
|
2282
|
+
const operation = { type: 'insert_node', node, path };
|
|
2283
|
+
board.apply(operation);
|
|
2284
|
+
}
|
|
2285
|
+
function setNode(board, props, path) {
|
|
2286
|
+
const properties = {};
|
|
2287
|
+
const newProperties = {};
|
|
2288
|
+
const node = PlaitNode.get(board, path);
|
|
2289
|
+
for (const k in props) {
|
|
2290
|
+
if (node[k] !== props[k]) {
|
|
2291
|
+
if (node.hasOwnProperty(k)) {
|
|
2292
|
+
properties[k] = node[k];
|
|
2293
|
+
}
|
|
2294
|
+
if (props[k] != null)
|
|
2295
|
+
newProperties[k] = props[k];
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
const operation = { type: 'set_node', properties, newProperties, path };
|
|
2299
|
+
board.apply(operation);
|
|
2300
|
+
}
|
|
2301
|
+
function removeNode(board, path) {
|
|
2302
|
+
const node = PlaitNode.get(board, path);
|
|
2303
|
+
const operation = { type: 'remove_node', path, node };
|
|
2304
|
+
board.apply(operation);
|
|
2305
|
+
}
|
|
2306
|
+
function moveNode(board, path, newPath) {
|
|
2307
|
+
const operation = { type: 'move_node', path, newPath };
|
|
2308
|
+
board.apply(operation);
|
|
2309
|
+
}
|
|
2310
|
+
const NodeTransforms = {
|
|
2311
|
+
insertNode,
|
|
2312
|
+
setNode,
|
|
2313
|
+
removeNode,
|
|
2314
|
+
moveNode
|
|
2315
|
+
};
|
|
2316
|
+
|
|
2317
|
+
const BOARD_TO_RAF = new WeakMap();
|
|
2318
|
+
const getTimerId = (board, key) => {
|
|
2319
|
+
const state = getRAFState(board);
|
|
2320
|
+
return state[key] || null;
|
|
2321
|
+
};
|
|
2322
|
+
const getRAFState = (board) => {
|
|
2323
|
+
return BOARD_TO_RAF.get(board) || {};
|
|
2324
|
+
};
|
|
2325
|
+
const throttleRAF = (board, key, fn) => {
|
|
2326
|
+
const scheduleFunc = () => {
|
|
2327
|
+
let timerId = requestAnimationFrame(() => {
|
|
2328
|
+
const value = BOARD_TO_RAF.get(board) || {};
|
|
2329
|
+
value[key] = null;
|
|
2330
|
+
BOARD_TO_RAF.set(board, value);
|
|
2331
|
+
PlaitBoard.isAlive(board) && fn();
|
|
2332
|
+
});
|
|
2333
|
+
const state = getRAFState(board);
|
|
2334
|
+
state[key] = timerId;
|
|
2335
|
+
BOARD_TO_RAF.set(board, state);
|
|
2336
|
+
};
|
|
2337
|
+
let timerId = getTimerId(board, key);
|
|
2338
|
+
if (timerId !== null) {
|
|
2339
|
+
cancelAnimationFrame(timerId);
|
|
2340
|
+
}
|
|
2341
|
+
scheduleFunc();
|
|
2342
|
+
};
|
|
2343
|
+
const debounce = (func, wait, options) => {
|
|
2344
|
+
let timerSubscription = null;
|
|
2345
|
+
return () => {
|
|
2346
|
+
if (timerSubscription && !timerSubscription.closed) {
|
|
2347
|
+
timerSubscription.unsubscribe();
|
|
2348
|
+
timerSubscription = timer(wait).subscribe(() => {
|
|
2349
|
+
func();
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
else {
|
|
2353
|
+
if (options?.leading) {
|
|
2354
|
+
timer(0).subscribe(() => {
|
|
2355
|
+
func();
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
timerSubscription = timer(wait).subscribe();
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
};
|
|
2362
|
+
const getElementsIndices = (board, elements) => {
|
|
2363
|
+
sortElements(board, elements);
|
|
2364
|
+
return elements.map(item => {
|
|
2365
|
+
return board.children.map(item => item.id).indexOf(item.id);
|
|
2366
|
+
});
|
|
2367
|
+
};
|
|
2368
|
+
const getHighestIndexOfElement = (board, elements) => {
|
|
2369
|
+
const indices = getElementsIndices(board, elements);
|
|
2370
|
+
return indices[indices.length - 1];
|
|
2371
|
+
};
|
|
2372
|
+
const moveElementsToNewPath = (board, moveOptions) => {
|
|
2373
|
+
moveOptions
|
|
2374
|
+
.map(item => {
|
|
2375
|
+
const path = PlaitBoard.findPath(board, item.element);
|
|
2376
|
+
const ref = board.pathRef(path);
|
|
2377
|
+
return () => {
|
|
2378
|
+
ref.current && NodeTransforms.moveNode(board, ref.current, item.newPath);
|
|
2379
|
+
ref.unref();
|
|
2380
|
+
};
|
|
2381
|
+
})
|
|
2382
|
+
.forEach(action => {
|
|
2383
|
+
action();
|
|
2384
|
+
});
|
|
2385
|
+
};
|
|
2386
|
+
|
|
2387
|
+
const IS_DRAGGING = new WeakMap();
|
|
2388
|
+
const isDragging = (board) => {
|
|
2389
|
+
return !!IS_DRAGGING.get(board);
|
|
2390
|
+
};
|
|
2391
|
+
const setDragging = (board, state) => {
|
|
2392
|
+
IS_DRAGGING.set(board, state);
|
|
2393
|
+
};
|
|
2394
|
+
|
|
2395
|
+
const getMovingElements = (board) => {
|
|
2396
|
+
return BOARD_TO_MOVING_ELEMENT.get(board) || [];
|
|
2397
|
+
};
|
|
2398
|
+
const isMovingElements = (board) => {
|
|
2399
|
+
return (BOARD_TO_MOVING_ELEMENT.get(board) || []).length > 0;
|
|
2400
|
+
};
|
|
2401
|
+
const removeMovingElements = (board) => {
|
|
2402
|
+
BOARD_TO_MOVING_ELEMENT.delete(board);
|
|
2403
|
+
setDragging(board, false);
|
|
2404
|
+
};
|
|
2405
|
+
const cacheMovingElements = (board, elements) => {
|
|
2406
|
+
BOARD_TO_MOVING_ELEMENT.set(board, elements);
|
|
2407
|
+
setDragging(board, true);
|
|
2408
|
+
};
|
|
2409
|
+
|
|
2410
|
+
const IMAGE_CONTAINER = 'plait-image-container';
|
|
2411
|
+
/**
|
|
2412
|
+
* Is element node
|
|
2413
|
+
* @param node
|
|
2414
|
+
* @returns
|
|
2415
|
+
*/
|
|
2416
|
+
function isElementNode(node) {
|
|
2417
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
2418
|
+
}
|
|
2419
|
+
/**
|
|
2420
|
+
* load image resources
|
|
2421
|
+
* @param url image url
|
|
2422
|
+
* @returns image element
|
|
2423
|
+
*/
|
|
2424
|
+
function loadImage(src) {
|
|
2425
|
+
return new Promise((resolve, reject) => {
|
|
2426
|
+
const img = new Image();
|
|
2427
|
+
img.crossOrigin = 'Anonymous';
|
|
2428
|
+
img.onload = () => resolve(img);
|
|
2429
|
+
img.onerror = () => reject(new Error('Failed to load image'));
|
|
2430
|
+
img.src = src;
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* create and return canvas and context
|
|
2435
|
+
* @param width canvas width
|
|
2436
|
+
* @param height canvas height
|
|
2437
|
+
* @param fillStyle fill style
|
|
2438
|
+
* @returns canvas and context
|
|
2439
|
+
*/
|
|
2440
|
+
function createCanvas(width, height, fillStyle = 'transparent') {
|
|
2441
|
+
const canvas = document.createElement('canvas');
|
|
2442
|
+
const ctx = canvas.getContext('2d');
|
|
2443
|
+
canvas.width = width;
|
|
2444
|
+
canvas.height = height;
|
|
2445
|
+
canvas.style.width = `${width}px`;
|
|
2446
|
+
canvas.style.height = `${height}px`;
|
|
2447
|
+
ctx.strokeStyle = '#ffffff';
|
|
2448
|
+
ctx.fillStyle = fillStyle;
|
|
2449
|
+
ctx.fillRect(0, 0, width, height);
|
|
2450
|
+
return {
|
|
2451
|
+
canvas,
|
|
2452
|
+
ctx
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* convert image to base64
|
|
2457
|
+
* @param url image url
|
|
2458
|
+
* @returns image base64
|
|
2459
|
+
*/
|
|
2460
|
+
function convertImageToBase64(url) {
|
|
2461
|
+
return loadImage(url).then(img => {
|
|
2462
|
+
const { canvas, ctx } = createCanvas(img.width, img.height);
|
|
2463
|
+
ctx?.drawImage(img, 0, 0);
|
|
2464
|
+
return canvas.toDataURL('image/png');
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* clone node style
|
|
2469
|
+
* @param nativeNode source node
|
|
2470
|
+
* @param clonedNode clone node
|
|
2471
|
+
*/
|
|
2472
|
+
function cloneCSSStyle(nativeNode, clonedNode) {
|
|
2473
|
+
const targetStyle = clonedNode?.style;
|
|
2474
|
+
if (!targetStyle) {
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
2478
|
+
if (sourceStyle.cssText) {
|
|
2479
|
+
targetStyle.cssText = sourceStyle.cssText;
|
|
2480
|
+
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
2481
|
+
}
|
|
2482
|
+
else {
|
|
2483
|
+
Array.from(sourceStyle).forEach(name => {
|
|
2484
|
+
let value = sourceStyle.getPropertyValue(name);
|
|
2485
|
+
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* batch clone target styles
|
|
2491
|
+
* @param sourceNode
|
|
1843
2492
|
* @param cloneNode
|
|
1844
2493
|
* @param inlineStyleClassNames
|
|
1845
2494
|
*/
|
|
@@ -1894,7 +2543,7 @@ async function cloneSvg(board, elements, rectangle, options) {
|
|
|
1894
2543
|
const { width, height, x, y } = rectangle;
|
|
1895
2544
|
const { padding = 4, inlineStyleClassNames } = options;
|
|
1896
2545
|
const sourceSvg = PlaitBoard.getHost(board);
|
|
1897
|
-
const selectedGElements = elements.map(value => PlaitElement.
|
|
2546
|
+
const selectedGElements = elements.map(value => PlaitElement.getElementG(value));
|
|
1898
2547
|
const cloneSvgElement = sourceSvg.cloneNode();
|
|
1899
2548
|
const newHostElement = PlaitBoard.getElementHost(board).cloneNode();
|
|
1900
2549
|
cloneSvgElement.style.width = `${width}px`;
|
|
@@ -2008,19 +2657,19 @@ const getProbablySupportsClipboardWriteText = () => {
|
|
|
2008
2657
|
const getProbablySupportsClipboardRead = () => {
|
|
2009
2658
|
return 'clipboard' in navigator && 'read' in navigator.clipboard;
|
|
2010
2659
|
};
|
|
2011
|
-
const createClipboardContext = (type,
|
|
2660
|
+
const createClipboardContext = (type, elements, text) => {
|
|
2012
2661
|
return {
|
|
2013
2662
|
type,
|
|
2014
|
-
|
|
2663
|
+
elements,
|
|
2015
2664
|
text
|
|
2016
2665
|
};
|
|
2017
2666
|
};
|
|
2018
2667
|
const addClipboardContext = (clipboardContext, addition) => {
|
|
2019
|
-
const { type,
|
|
2668
|
+
const { type, elements, text } = clipboardContext;
|
|
2020
2669
|
if (type === addition.type) {
|
|
2021
2670
|
return {
|
|
2022
2671
|
type,
|
|
2023
|
-
|
|
2672
|
+
elements: elements.concat(addition.elements),
|
|
2024
2673
|
text: text + ' ' + addition.text
|
|
2025
2674
|
};
|
|
2026
2675
|
}
|
|
@@ -2092,123 +2741,562 @@ const getNavigatorClipboard = async () => {
|
|
|
2092
2741
|
files
|
|
2093
2742
|
};
|
|
2094
2743
|
}
|
|
2095
|
-
if (item.types.includes('text/html')) {
|
|
2096
|
-
const htmlContent = await blobAsString(await item.getType('text/html'));
|
|
2097
|
-
const htmlClipboardData = getClipboardFromHtml(htmlContent);
|
|
2098
|
-
if (htmlClipboardData) {
|
|
2099
|
-
return htmlClipboardData;
|
|
2744
|
+
if (item.types.includes('text/html')) {
|
|
2745
|
+
const htmlContent = await blobAsString(await item.getType('text/html'));
|
|
2746
|
+
const htmlClipboardData = getClipboardFromHtml(htmlContent);
|
|
2747
|
+
if (htmlClipboardData) {
|
|
2748
|
+
return htmlClipboardData;
|
|
2749
|
+
}
|
|
2750
|
+
if (htmlContent && htmlContent.trim()) {
|
|
2751
|
+
clipboardData = { text: stripHtml(htmlContent) };
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if (item.types.includes('text/plain')) {
|
|
2755
|
+
const textContent = await blobAsString(await item.getType('text/plain'));
|
|
2756
|
+
clipboardData = {
|
|
2757
|
+
text: stripHtml(textContent)
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return clipboardData;
|
|
2763
|
+
};
|
|
2764
|
+
const isFile = (item) => {
|
|
2765
|
+
return item.types.find(i => i.match(/^image\//));
|
|
2766
|
+
};
|
|
2767
|
+
const blobAsString = (blob) => {
|
|
2768
|
+
return new Promise((resolve, reject) => {
|
|
2769
|
+
const reader = new FileReader();
|
|
2770
|
+
reader.addEventListener('loadend', () => {
|
|
2771
|
+
const text = reader.result;
|
|
2772
|
+
resolve(text);
|
|
2773
|
+
});
|
|
2774
|
+
reader.addEventListener('error', () => {
|
|
2775
|
+
reject(reader.error);
|
|
2776
|
+
});
|
|
2777
|
+
reader.readAsText(blob);
|
|
2778
|
+
});
|
|
2779
|
+
};
|
|
2780
|
+
|
|
2781
|
+
const getClipboardData = async (dataTransfer) => {
|
|
2782
|
+
let clipboardData = {};
|
|
2783
|
+
if (dataTransfer) {
|
|
2784
|
+
if (dataTransfer.files.length) {
|
|
2785
|
+
return { files: Array.from(dataTransfer.files) };
|
|
2786
|
+
}
|
|
2787
|
+
clipboardData = getDataTransferClipboard(dataTransfer);
|
|
2788
|
+
if (Object.keys(clipboardData).length === 0) {
|
|
2789
|
+
clipboardData = getDataTransferClipboardText(dataTransfer);
|
|
2790
|
+
}
|
|
2791
|
+
return clipboardData;
|
|
2792
|
+
}
|
|
2793
|
+
if (getProbablySupportsClipboardRead()) {
|
|
2794
|
+
return await getNavigatorClipboard();
|
|
2795
|
+
}
|
|
2796
|
+
return clipboardData;
|
|
2797
|
+
};
|
|
2798
|
+
const setClipboardData = async (dataTransfer, clipboardContext) => {
|
|
2799
|
+
if (!clipboardContext) {
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
const { type, elements, text } = clipboardContext;
|
|
2803
|
+
if (getProbablySupportsClipboardWrite()) {
|
|
2804
|
+
return await setNavigatorClipboard(type, elements, text);
|
|
2805
|
+
}
|
|
2806
|
+
if (dataTransfer) {
|
|
2807
|
+
setDataTransferClipboard(dataTransfer, type, elements);
|
|
2808
|
+
setDataTransferClipboardText(dataTransfer, text);
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
// Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
|
|
2812
|
+
// Such as contextmenu copy in Firefox.
|
|
2813
|
+
if (getProbablySupportsClipboardWriteText()) {
|
|
2814
|
+
return await navigator.clipboard.writeText(buildPlaitHtml(type, elements));
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2817
|
+
|
|
2818
|
+
const BOARD_TO_TOUCH_REF = new WeakMap();
|
|
2819
|
+
const isPreventTouchMove = (board) => {
|
|
2820
|
+
return !!BOARD_TO_TOUCH_REF.get(board);
|
|
2821
|
+
};
|
|
2822
|
+
const preventTouchMove = (board, event, state) => {
|
|
2823
|
+
const hostElement = PlaitBoard.getElementHost(board);
|
|
2824
|
+
const activeHostElement = PlaitBoard.getElementActiveHost(board);
|
|
2825
|
+
if (state) {
|
|
2826
|
+
if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) &&
|
|
2827
|
+
(hostElement.contains(event.target) || activeHostElement.contains(event.target))) {
|
|
2828
|
+
BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
|
|
2829
|
+
}
|
|
2830
|
+
else {
|
|
2831
|
+
BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
else {
|
|
2835
|
+
const ref = BOARD_TO_TOUCH_REF.get(board);
|
|
2836
|
+
if (ref) {
|
|
2837
|
+
BOARD_TO_TOUCH_REF.delete(board);
|
|
2838
|
+
ref.host?.remove();
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
/**
|
|
2843
|
+
* some intersection maybe cause target is removed from current browser window,
|
|
2844
|
+
* after it was removed touch move event will not be fired
|
|
2845
|
+
* so scroll behavior will can not be prevented in mobile browser device
|
|
2846
|
+
* this function will prevent target element being remove.
|
|
2847
|
+
*/
|
|
2848
|
+
const handleTouchTarget = (board) => {
|
|
2849
|
+
const touchRef = BOARD_TO_TOUCH_REF.get(board);
|
|
2850
|
+
if (touchRef &&
|
|
2851
|
+
touchRef.target &&
|
|
2852
|
+
!PlaitBoard.getElementHost(board).contains(touchRef.target) &&
|
|
2853
|
+
!PlaitBoard.getElementActiveHost(board).contains(touchRef.target)) {
|
|
2854
|
+
touchRef.target.style.opacity = '0';
|
|
2855
|
+
const host = createG();
|
|
2856
|
+
host.appendChild(touchRef.target);
|
|
2857
|
+
touchRef.host = host;
|
|
2858
|
+
host.classList.add('touch-target');
|
|
2859
|
+
PlaitBoard.getElementActiveHost(board).append(host);
|
|
2860
|
+
}
|
|
2861
|
+
};
|
|
2862
|
+
|
|
2863
|
+
const Viewport = {
|
|
2864
|
+
isViewport: (value) => {
|
|
2865
|
+
return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
|
|
2869
|
+
const applyToDraft = (board, selection, viewport, theme, op) => {
|
|
2870
|
+
switch (op.type) {
|
|
2871
|
+
case 'insert_node': {
|
|
2872
|
+
const { path, node } = op;
|
|
2873
|
+
const parent = PlaitNode.parent(board, path);
|
|
2874
|
+
const index = path[path.length - 1];
|
|
2875
|
+
if (!parent.children || index > parent.children.length) {
|
|
2876
|
+
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
2877
|
+
}
|
|
2878
|
+
parent.children.splice(index, 0, node);
|
|
2879
|
+
break;
|
|
2880
|
+
}
|
|
2881
|
+
case 'remove_node': {
|
|
2882
|
+
const { path } = op;
|
|
2883
|
+
const parent = PlaitNode.parent(board, path);
|
|
2884
|
+
const index = path[path.length - 1];
|
|
2885
|
+
if (!parent.children || index > parent.children.length) {
|
|
2886
|
+
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
2887
|
+
}
|
|
2888
|
+
parent.children.splice(index, 1);
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2891
|
+
case 'move_node': {
|
|
2892
|
+
const { path, newPath } = op;
|
|
2893
|
+
if (Path.isAncestor(path, newPath)) {
|
|
2894
|
+
throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
|
|
2895
|
+
}
|
|
2896
|
+
const node = PlaitNode.get(board, path);
|
|
2897
|
+
const parent = PlaitNode.parent(board, path);
|
|
2898
|
+
const index = path[path.length - 1];
|
|
2899
|
+
// This is tricky, but since the `path` and `newPath` both refer to
|
|
2900
|
+
// the same snapshot in time, there's a mismatch. After either
|
|
2901
|
+
// removing the original position, the second step's path can be out
|
|
2902
|
+
// of date. So instead of using the `op.newPath` directly, we
|
|
2903
|
+
// transform `op.path` to ascertain what the `newPath` would be after
|
|
2904
|
+
// the operation was applied.
|
|
2905
|
+
parent.children?.splice(index, 1);
|
|
2906
|
+
const truePath = Path.transform(path, op);
|
|
2907
|
+
const newParent = PlaitNode.get(board, Path.parent(truePath));
|
|
2908
|
+
const newIndex = truePath[truePath.length - 1];
|
|
2909
|
+
newParent.children?.splice(newIndex, 0, node);
|
|
2910
|
+
break;
|
|
2911
|
+
}
|
|
2912
|
+
case 'set_node': {
|
|
2913
|
+
const { path, properties, newProperties } = op;
|
|
2914
|
+
if (path.length === 0) {
|
|
2915
|
+
throw new Error(`Cannot set properties on the root node!`);
|
|
2916
|
+
}
|
|
2917
|
+
const node = PlaitNode.get(board, path);
|
|
2918
|
+
for (const key in newProperties) {
|
|
2919
|
+
const value = newProperties[key];
|
|
2920
|
+
if (value == null) {
|
|
2921
|
+
delete node[key];
|
|
2922
|
+
}
|
|
2923
|
+
else {
|
|
2924
|
+
node[key] = value;
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
// properties that were previously defined, but are now missing, must be deleted
|
|
2928
|
+
for (const key in properties) {
|
|
2929
|
+
if (!newProperties.hasOwnProperty(key)) {
|
|
2930
|
+
delete node[key];
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
break;
|
|
2934
|
+
}
|
|
2935
|
+
case 'set_viewport': {
|
|
2936
|
+
const { newProperties } = op;
|
|
2937
|
+
if (newProperties == null) {
|
|
2938
|
+
viewport = newProperties;
|
|
2939
|
+
}
|
|
2940
|
+
else {
|
|
2941
|
+
if (viewport == null) {
|
|
2942
|
+
if (!Viewport.isViewport(newProperties)) {
|
|
2943
|
+
throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
|
|
2944
|
+
}
|
|
2945
|
+
viewport = { ...newProperties };
|
|
2946
|
+
}
|
|
2947
|
+
for (const key in newProperties) {
|
|
2948
|
+
const value = newProperties[key];
|
|
2949
|
+
if (value == null) {
|
|
2950
|
+
delete viewport[key];
|
|
2951
|
+
}
|
|
2952
|
+
else {
|
|
2953
|
+
viewport[key] = value;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
case 'set_selection': {
|
|
2960
|
+
const { newProperties } = op;
|
|
2961
|
+
if (newProperties == null) {
|
|
2962
|
+
selection = newProperties;
|
|
2963
|
+
}
|
|
2964
|
+
else {
|
|
2965
|
+
if (selection === null) {
|
|
2966
|
+
selection = op.newProperties;
|
|
2100
2967
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2968
|
+
else {
|
|
2969
|
+
selection = newProperties;
|
|
2103
2970
|
}
|
|
2104
2971
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2972
|
+
break;
|
|
2973
|
+
}
|
|
2974
|
+
case 'set_theme': {
|
|
2975
|
+
const { newProperties } = op;
|
|
2976
|
+
theme = newProperties;
|
|
2977
|
+
break;
|
|
2111
2978
|
}
|
|
2112
2979
|
}
|
|
2113
|
-
return
|
|
2114
|
-
};
|
|
2115
|
-
const isFile = (item) => {
|
|
2116
|
-
return item.types.find(i => i.match(/^image\//));
|
|
2980
|
+
return { selection, viewport, theme };
|
|
2117
2981
|
};
|
|
2118
|
-
const
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2982
|
+
const GeneralTransforms = {
|
|
2983
|
+
/**
|
|
2984
|
+
* Transform the board by an operation.
|
|
2985
|
+
*/
|
|
2986
|
+
transform(board, op) {
|
|
2987
|
+
board.children = createDraft(board.children);
|
|
2988
|
+
let viewport = board.viewport && createDraft(board.viewport);
|
|
2989
|
+
let selection = board.selection && createDraft(board.selection);
|
|
2990
|
+
let theme = board.theme && createDraft(board.theme);
|
|
2991
|
+
try {
|
|
2992
|
+
const state = applyToDraft(board, selection, viewport, theme, op);
|
|
2993
|
+
viewport = state.viewport;
|
|
2994
|
+
selection = state.selection;
|
|
2995
|
+
theme = state.theme;
|
|
2996
|
+
}
|
|
2997
|
+
finally {
|
|
2998
|
+
board.children = finishDraft(board.children);
|
|
2999
|
+
if (selection) {
|
|
3000
|
+
board.selection = isDraft(selection) ? finishDraft(selection) : selection;
|
|
3001
|
+
}
|
|
3002
|
+
else {
|
|
3003
|
+
board.selection = null;
|
|
3004
|
+
}
|
|
3005
|
+
board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
|
|
3006
|
+
board.theme = isDraft(theme) ? finishDraft(theme) : theme;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
2130
3009
|
};
|
|
2131
3010
|
|
|
2132
|
-
const
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
3011
|
+
const addGroup = (board, elements) => {
|
|
3012
|
+
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3013
|
+
const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
|
|
3014
|
+
const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
|
|
3015
|
+
const group = createGroup();
|
|
3016
|
+
if (canAddGroup(board)) {
|
|
3017
|
+
highestSelectedElements.forEach(item => {
|
|
3018
|
+
const path = PlaitBoard.findPath(board, item);
|
|
3019
|
+
NodeTransforms.setNode(board, { groupId: group.id }, path);
|
|
3020
|
+
});
|
|
3021
|
+
const selectedElements = getSelectedElements(board);
|
|
3022
|
+
const highestIndexOfSelectedElement = getHighestIndexOfElement(board, selectedElements);
|
|
3023
|
+
const indices = getElementsIndices(board, highestSelectedElements);
|
|
3024
|
+
const isContinuous = isIndicesContinuous(indices);
|
|
3025
|
+
if (!isContinuous) {
|
|
3026
|
+
moveElementsToNewPathAfterAddGroup(board, selectedElements, [highestIndexOfSelectedElement - 1]);
|
|
2137
3027
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
3028
|
+
if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
|
|
3029
|
+
const newGroupId = selectedIsolatedElements[0].groupId;
|
|
3030
|
+
NodeTransforms.insertNode(board, {
|
|
3031
|
+
...group,
|
|
3032
|
+
groupId: newGroupId
|
|
3033
|
+
}, [board.children.length]);
|
|
3034
|
+
}
|
|
3035
|
+
else {
|
|
3036
|
+
NodeTransforms.insertNode(board, group, [board.children.length]);
|
|
2141
3037
|
}
|
|
2142
|
-
return clipboardData;
|
|
2143
|
-
}
|
|
2144
|
-
if (getProbablySupportsClipboardRead()) {
|
|
2145
|
-
return await getNavigatorClipboard();
|
|
2146
3038
|
}
|
|
2147
|
-
return clipboardData;
|
|
2148
3039
|
};
|
|
2149
|
-
const
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
3040
|
+
const removeGroup = (board, elements) => {
|
|
3041
|
+
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3042
|
+
if (canRemoveGroup(board)) {
|
|
3043
|
+
selectedGroups.forEach(group => {
|
|
3044
|
+
const elementsInGroup = findElements(board, {
|
|
3045
|
+
match: item => item.groupId === group.id,
|
|
3046
|
+
recursion: () => false
|
|
3047
|
+
});
|
|
3048
|
+
elementsInGroup.forEach(element => {
|
|
3049
|
+
const path = PlaitBoard.findPath(board, element);
|
|
3050
|
+
NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
|
|
3051
|
+
});
|
|
3052
|
+
});
|
|
3053
|
+
selectedGroups
|
|
3054
|
+
.map(group => {
|
|
3055
|
+
const groupPath = PlaitBoard.findPath(board, group);
|
|
3056
|
+
const groupRef = board.pathRef(groupPath);
|
|
3057
|
+
return () => {
|
|
3058
|
+
groupRef.current && NodeTransforms.removeNode(board, groupRef.current);
|
|
3059
|
+
groupRef.unref();
|
|
3060
|
+
};
|
|
3061
|
+
})
|
|
3062
|
+
.forEach(action => {
|
|
3063
|
+
action();
|
|
3064
|
+
});
|
|
2166
3065
|
}
|
|
2167
3066
|
};
|
|
3067
|
+
const GroupTransforms = {
|
|
3068
|
+
addGroup,
|
|
3069
|
+
removeGroup
|
|
3070
|
+
};
|
|
2168
3071
|
|
|
2169
|
-
|
|
2170
|
-
const
|
|
2171
|
-
|
|
3072
|
+
function setSelection(board, selection) {
|
|
3073
|
+
const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
|
|
3074
|
+
board.apply(operation);
|
|
3075
|
+
}
|
|
3076
|
+
const SelectionTransforms = {
|
|
3077
|
+
setSelection,
|
|
3078
|
+
addSelectionWithTemporaryElements
|
|
2172
3079
|
};
|
|
2173
|
-
|
|
2174
|
-
const
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
}
|
|
3080
|
+
function addSelectionWithTemporaryElements(board, elements) {
|
|
3081
|
+
const timeoutId = setTimeout(() => {
|
|
3082
|
+
setSelection(board, { anchor: [0, 0], focus: [0, 0] });
|
|
3083
|
+
}, 0);
|
|
3084
|
+
let ref = getTemporaryRef(board);
|
|
3085
|
+
if (ref) {
|
|
3086
|
+
clearTimeout(ref.timeoutId);
|
|
3087
|
+
const currentElements = ref.elements;
|
|
3088
|
+
ref.elements.push(...elements.filter(element => !currentElements.includes(element)));
|
|
3089
|
+
ref.timeoutId = timeoutId;
|
|
2184
3090
|
}
|
|
2185
3091
|
else {
|
|
2186
|
-
|
|
2187
|
-
if (ref) {
|
|
2188
|
-
BOARD_TO_TOUCH_REF.delete(board);
|
|
2189
|
-
ref.host?.remove();
|
|
2190
|
-
}
|
|
3092
|
+
BOARD_TO_TEMPORARY_ELEMENTS.set(board, { timeoutId, elements });
|
|
2191
3093
|
}
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
const getOneMoveOptions = (board, direction) => {
|
|
3097
|
+
const indicesToMove = getElementsIndices(board, getSelectedElements(board));
|
|
3098
|
+
let groupedIndices = toContiguousGroups(board, indicesToMove);
|
|
3099
|
+
if (direction === 'up') {
|
|
3100
|
+
groupedIndices = groupedIndices.reverse();
|
|
3101
|
+
}
|
|
3102
|
+
let moveContents = [];
|
|
3103
|
+
groupedIndices.forEach((indices, i) => {
|
|
3104
|
+
const leadingIndex = indices[0];
|
|
3105
|
+
const trailingIndex = indices[indices.length - 1];
|
|
3106
|
+
const boundaryIndex = direction === 'down' ? leadingIndex : trailingIndex;
|
|
3107
|
+
const targetIndex = getTargetIndex(board, boundaryIndex, direction);
|
|
3108
|
+
if (targetIndex === -1 || boundaryIndex === targetIndex) {
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
3111
|
+
if (direction === 'down') {
|
|
3112
|
+
indices = indices.reverse();
|
|
3113
|
+
}
|
|
3114
|
+
moveContents.push(...indices.map(path => {
|
|
3115
|
+
return {
|
|
3116
|
+
element: board.children[path],
|
|
3117
|
+
newPath: [targetIndex]
|
|
3118
|
+
};
|
|
3119
|
+
}));
|
|
3120
|
+
});
|
|
3121
|
+
return moveContents;
|
|
3122
|
+
};
|
|
3123
|
+
const getAllMoveOptions = (board, direction) => {
|
|
3124
|
+
const indicesToMove = getElementsIndices(board, getSelectedElements(board));
|
|
3125
|
+
let groupedIndices = toContiguousGroups(board, indicesToMove);
|
|
3126
|
+
let moveContents = [];
|
|
3127
|
+
if (direction === 'down') {
|
|
3128
|
+
groupedIndices = groupedIndices.reverse();
|
|
3129
|
+
}
|
|
3130
|
+
groupedIndices.forEach(indices => {
|
|
3131
|
+
const leadingIndex = indices[0];
|
|
3132
|
+
const trailingIndex = indices[indices.length - 1];
|
|
3133
|
+
const boundaryIndex = direction === 'down' ? leadingIndex : trailingIndex;
|
|
3134
|
+
const sourceElement = board.children[boundaryIndex];
|
|
3135
|
+
const editingGroup = getEditingGroup(board, sourceElement);
|
|
3136
|
+
let targetIndex = direction === 'down' ? 0 : board.children.length - 1;
|
|
3137
|
+
if (editingGroup) {
|
|
3138
|
+
const elementsInGroup = sortElements(board, getElementsInGroup(board, editingGroup, true, true));
|
|
3139
|
+
targetIndex =
|
|
3140
|
+
direction === 'down'
|
|
3141
|
+
? board.children.indexOf(elementsInGroup[0])
|
|
3142
|
+
: board.children.indexOf(elementsInGroup[elementsInGroup.length - 1]);
|
|
3143
|
+
}
|
|
3144
|
+
if (direction === 'down') {
|
|
3145
|
+
indices = indices.reverse();
|
|
3146
|
+
}
|
|
3147
|
+
moveContents.push(...indices.map(path => {
|
|
3148
|
+
return {
|
|
3149
|
+
element: board.children[path],
|
|
3150
|
+
newPath: [targetIndex]
|
|
3151
|
+
};
|
|
3152
|
+
}));
|
|
3153
|
+
});
|
|
3154
|
+
return moveContents;
|
|
3155
|
+
};
|
|
3156
|
+
const canSetZIndex = (board) => {
|
|
3157
|
+
const selectedElements = getSelectedElements(board).filter(item => board.canSetZIndex(item));
|
|
3158
|
+
return selectedElements.length > 0;
|
|
3159
|
+
};
|
|
3160
|
+
const toContiguousGroups = (board, array) => {
|
|
3161
|
+
let cursor = 0;
|
|
3162
|
+
return array.reduce((acc, value, index) => {
|
|
3163
|
+
if (index > 0) {
|
|
3164
|
+
const currentElement = board.children[value];
|
|
3165
|
+
const previousElement = board.children[array[index - 1]];
|
|
3166
|
+
const isContiguous = value - 1 === array[index - 1]
|
|
3167
|
+
? true
|
|
3168
|
+
: board.children.every((item, childIndex) => {
|
|
3169
|
+
if (childIndex > array[index - 1] && childIndex <= value - 1) {
|
|
3170
|
+
return PlaitGroupElement.isGroup(item);
|
|
3171
|
+
}
|
|
3172
|
+
return true;
|
|
3173
|
+
});
|
|
3174
|
+
let isPartialSelectGroupElement = false;
|
|
3175
|
+
if (previousElement.groupId || (currentElement.groupId && previousElement.groupId !== currentElement.groupId)) {
|
|
3176
|
+
let isPartialSelectPreviousGroup = false;
|
|
3177
|
+
let isPartialSelectCurrentElement = false;
|
|
3178
|
+
if (previousElement.groupId) {
|
|
3179
|
+
const highestGroup = getHighestGroup(board, previousElement);
|
|
3180
|
+
isPartialSelectPreviousGroup = !isSelectedAllElementsInGroup(board, highestGroup);
|
|
3181
|
+
}
|
|
3182
|
+
if (currentElement.groupId) {
|
|
3183
|
+
const highestGroup = getHighestGroup(board, currentElement);
|
|
3184
|
+
isPartialSelectCurrentElement = !isSelectedAllElementsInGroup(board, highestGroup);
|
|
3185
|
+
}
|
|
3186
|
+
isPartialSelectGroupElement = isPartialSelectPreviousGroup || isPartialSelectCurrentElement;
|
|
3187
|
+
}
|
|
3188
|
+
if (!isContiguous || isPartialSelectGroupElement) {
|
|
3189
|
+
cursor = ++cursor;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
(acc[cursor] || (acc[cursor] = [])).push(value);
|
|
3193
|
+
return acc;
|
|
3194
|
+
}, []);
|
|
2192
3195
|
};
|
|
2193
3196
|
/**
|
|
2194
|
-
*
|
|
2195
|
-
*
|
|
2196
|
-
* so scroll behavior will can not be prevented in mobile browser device
|
|
2197
|
-
* this function will prevent target element being remove.
|
|
3197
|
+
* Returns next candidate index that's available to be moved to. Currently that
|
|
3198
|
+
* is a non-deleted element, and not inside a group (unless we're editing it).
|
|
2198
3199
|
*/
|
|
2199
|
-
const
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
3200
|
+
const getTargetIndex = (board, boundaryIndex, direction) => {
|
|
3201
|
+
if ((boundaryIndex === 0 && direction === 'down') || (boundaryIndex === board.children.length - 1 && direction === 'up')) {
|
|
3202
|
+
return -1;
|
|
3203
|
+
}
|
|
3204
|
+
const indexFilter = (element) => {
|
|
3205
|
+
if (element.isDeleted || PlaitGroupElement.isGroup(element)) {
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
return true;
|
|
3209
|
+
};
|
|
3210
|
+
const candidateIndex = direction === 'down'
|
|
3211
|
+
? findLastIndex(board.children, el => indexFilter(el), Math.max(0, boundaryIndex - 1))
|
|
3212
|
+
: findIndex(board.children, el => indexFilter(el), boundaryIndex + 1);
|
|
3213
|
+
const nextElement = board.children[candidateIndex];
|
|
3214
|
+
if (!nextElement) {
|
|
3215
|
+
return -1;
|
|
3216
|
+
}
|
|
3217
|
+
const elements = [...board.children];
|
|
3218
|
+
const sourceElement = elements[boundaryIndex];
|
|
3219
|
+
const editingGroup = getEditingGroup(board, sourceElement);
|
|
3220
|
+
const nextElementGroups = (getGroupByElement(board, nextElement, true) || []);
|
|
3221
|
+
// candidate element is a sibling in current editing group → return
|
|
3222
|
+
if (editingGroup && sourceElement?.groupId !== nextElement?.groupId) {
|
|
3223
|
+
// candidate element is outside current editing group → prevent
|
|
3224
|
+
if (!nextElementGroups.find(item => item.id === editingGroup.id)) {
|
|
3225
|
+
return -1;
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
if (!nextElement.groupId) {
|
|
3229
|
+
return candidateIndex;
|
|
3230
|
+
}
|
|
3231
|
+
let siblingGroup;
|
|
3232
|
+
if (editingGroup) {
|
|
3233
|
+
siblingGroup = nextElementGroups[nextElementGroups.indexOf(editingGroup) - 1];
|
|
3234
|
+
}
|
|
3235
|
+
else {
|
|
3236
|
+
siblingGroup = nextElementGroups[nextElementGroups.length - 1];
|
|
3237
|
+
}
|
|
3238
|
+
if (siblingGroup) {
|
|
3239
|
+
let elementsInSiblingGroup = getElementsInGroup(board, siblingGroup, true, false);
|
|
3240
|
+
if (elementsInSiblingGroup.length) {
|
|
3241
|
+
elementsInSiblingGroup.sort((a, b) => {
|
|
3242
|
+
const indexA = board.children.findIndex(child => child.id === a.id);
|
|
3243
|
+
const indexB = board.children.findIndex(child => child.id === b.id);
|
|
3244
|
+
return indexA - indexB;
|
|
3245
|
+
});
|
|
3246
|
+
// assumes getElementsInGroup() returned elements are sorted
|
|
3247
|
+
// by zIndex (ascending)
|
|
3248
|
+
return direction === 'down'
|
|
3249
|
+
? elements.indexOf(elementsInSiblingGroup[0])
|
|
3250
|
+
: elements.indexOf(elementsInSiblingGroup[elementsInSiblingGroup.length - 1]);
|
|
3251
|
+
}
|
|
2211
3252
|
}
|
|
3253
|
+
return candidateIndex;
|
|
3254
|
+
};
|
|
3255
|
+
|
|
3256
|
+
const moveToTop = (board) => {
|
|
3257
|
+
const moveOptions = getAllMoveOptions(board, 'up');
|
|
3258
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3259
|
+
};
|
|
3260
|
+
const moveToBottom = (board) => {
|
|
3261
|
+
const moveOptions = getAllMoveOptions(board, 'down');
|
|
3262
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3263
|
+
};
|
|
3264
|
+
const moveUp = (board) => {
|
|
3265
|
+
const moveOptions = getOneMoveOptions(board, 'up');
|
|
3266
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3267
|
+
};
|
|
3268
|
+
const moveDown = (board) => {
|
|
3269
|
+
const moveOptions = getOneMoveOptions(board, 'down');
|
|
3270
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3271
|
+
};
|
|
3272
|
+
const ZIndexTransforms = { moveUp, moveDown, moveToTop, moveToBottom };
|
|
3273
|
+
|
|
3274
|
+
const removeElements = (board, elements) => {
|
|
3275
|
+
elements
|
|
3276
|
+
.map(element => {
|
|
3277
|
+
const path = PlaitBoard.findPath(board, element);
|
|
3278
|
+
const ref = board.pathRef(path);
|
|
3279
|
+
return () => {
|
|
3280
|
+
ref.current && removeNode(board, ref.current);
|
|
3281
|
+
ref.unref();
|
|
3282
|
+
removeSelectedElement(board, element, true);
|
|
3283
|
+
};
|
|
3284
|
+
})
|
|
3285
|
+
.forEach(action => {
|
|
3286
|
+
action();
|
|
3287
|
+
});
|
|
3288
|
+
};
|
|
3289
|
+
const CoreTransforms = {
|
|
3290
|
+
removeElements
|
|
3291
|
+
};
|
|
3292
|
+
|
|
3293
|
+
const Transforms = {
|
|
3294
|
+
...GeneralTransforms,
|
|
3295
|
+
...ViewportTransforms$1,
|
|
3296
|
+
...SelectionTransforms,
|
|
3297
|
+
...NodeTransforms,
|
|
3298
|
+
...GroupTransforms,
|
|
3299
|
+
...ZIndexTransforms
|
|
2212
3300
|
};
|
|
2213
3301
|
|
|
2214
3302
|
const rotatePoints = (points, centerPoint, angle) => {
|
|
@@ -2234,7 +3322,14 @@ const getSelectionAngle = (elements) => {
|
|
|
2234
3322
|
return angle;
|
|
2235
3323
|
};
|
|
2236
3324
|
const hasSameAngle = (elements) => {
|
|
2237
|
-
|
|
3325
|
+
if (!elements.length) {
|
|
3326
|
+
return false;
|
|
3327
|
+
}
|
|
3328
|
+
const angle = elements[0].angle;
|
|
3329
|
+
if (angle === undefined) {
|
|
3330
|
+
return false;
|
|
3331
|
+
}
|
|
3332
|
+
return !elements.some(item => item.angle !== angle);
|
|
2238
3333
|
};
|
|
2239
3334
|
const getRotatedBoundingRectangle = (rectanglesCornerPoints, angle) => {
|
|
2240
3335
|
let rectanglesFromOrigin = [];
|
|
@@ -2273,16 +3368,60 @@ const rotatePointsByElement = (points, element) => {
|
|
|
2273
3368
|
return null;
|
|
2274
3369
|
}
|
|
2275
3370
|
};
|
|
2276
|
-
const rotateAntiPointsByElement = (points, element) => {
|
|
2277
|
-
if (hasValidAngle(element)) {
|
|
2278
|
-
let rectangle = RectangleClient.getRectangleByPoints(element.points);
|
|
3371
|
+
const rotateAntiPointsByElement = (points, element) => {
|
|
3372
|
+
if (hasValidAngle(element)) {
|
|
3373
|
+
let rectangle = RectangleClient.getRectangleByPoints(element.points);
|
|
3374
|
+
const centerPoint = RectangleClient.getCenterPoint(rectangle);
|
|
3375
|
+
return rotatePoints(points, centerPoint, -element.angle);
|
|
3376
|
+
}
|
|
3377
|
+
else {
|
|
3378
|
+
return null;
|
|
3379
|
+
}
|
|
3380
|
+
};
|
|
3381
|
+
const getRectangleByAngle = (rectangle, angle) => {
|
|
3382
|
+
if (angle) {
|
|
3383
|
+
const cornerPoints = RectangleClient.getCornerPoints(rectangle);
|
|
2279
3384
|
const centerPoint = RectangleClient.getCenterPoint(rectangle);
|
|
2280
|
-
return rotatePoints(
|
|
3385
|
+
return RectangleClient.getRectangleByPoints(rotatePoints(cornerPoints, centerPoint, angle));
|
|
2281
3386
|
}
|
|
2282
3387
|
else {
|
|
2283
3388
|
return null;
|
|
2284
3389
|
}
|
|
2285
3390
|
};
|
|
3391
|
+
const isAxisChangedByAngle = (angle) => {
|
|
3392
|
+
const unitAngle = Math.abs(angle) % Math.PI;
|
|
3393
|
+
return unitAngle >= (1 / 4) * Math.PI && unitAngle <= (3 / 4) * Math.PI;
|
|
3394
|
+
};
|
|
3395
|
+
function degreesToRadians(d) {
|
|
3396
|
+
return (d / 180) * Math.PI;
|
|
3397
|
+
}
|
|
3398
|
+
function radiansToDegrees(r) {
|
|
3399
|
+
return (r / Math.PI) * 180;
|
|
3400
|
+
}
|
|
3401
|
+
function rotateElements(board, elements, angle) {
|
|
3402
|
+
const selectionRectangle = getRectangleByElements(board, elements, false);
|
|
3403
|
+
const selectionCenterPoint = RectangleClient.getCenterPoint(selectionRectangle);
|
|
3404
|
+
elements.forEach(item => {
|
|
3405
|
+
const originAngle = item.angle;
|
|
3406
|
+
const points = rotatedDataPoints(item.points, selectionCenterPoint, normalizeAngle(angle));
|
|
3407
|
+
const path = PlaitBoard.findPath(board, item);
|
|
3408
|
+
Transforms.setNode(board, { points, angle: normalizeAngle(originAngle + angle) }, path);
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
const normalizeAngle = (angle) => {
|
|
3412
|
+
if (angle < 0) {
|
|
3413
|
+
return angle + 2 * Math.PI;
|
|
3414
|
+
}
|
|
3415
|
+
if (angle >= 2 * Math.PI) {
|
|
3416
|
+
return angle - 2 * Math.PI;
|
|
3417
|
+
}
|
|
3418
|
+
return angle;
|
|
3419
|
+
};
|
|
3420
|
+
const getAngleBetweenPoints = (startPoint, endPoint, centerPoint) => {
|
|
3421
|
+
const startAngle = (5 * Math.PI) / 2 + Math.atan2(startPoint[1] - centerPoint[1], startPoint[0] - centerPoint[0]);
|
|
3422
|
+
const endAngle = (5 * Math.PI) / 2 + Math.atan2(endPoint[1] - centerPoint[1], endPoint[0] - centerPoint[0]);
|
|
3423
|
+
return normalizeAngle(endAngle - startAngle);
|
|
3424
|
+
};
|
|
2286
3425
|
|
|
2287
3426
|
function isSelectionMoving(board) {
|
|
2288
3427
|
return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
|
|
@@ -2319,7 +3458,7 @@ function getTemporaryRef(board) {
|
|
|
2319
3458
|
function deleteTemporaryElements(board) {
|
|
2320
3459
|
BOARD_TO_TEMPORARY_ELEMENTS.delete(board);
|
|
2321
3460
|
}
|
|
2322
|
-
function
|
|
3461
|
+
function drawEntireActiveRectangleG(board) {
|
|
2323
3462
|
const elements = getSelectedElements(board);
|
|
2324
3463
|
const rectangle = getRectangleByElements(board, elements, false);
|
|
2325
3464
|
if (rectangle.width > 0 && rectangle.height > 0 && elements.length > 1) {
|
|
@@ -2329,7 +3468,6 @@ function createSelectionRectangleG(board) {
|
|
|
2329
3468
|
fillStyle: 'solid'
|
|
2330
3469
|
});
|
|
2331
3470
|
selectionRectangleG.classList.add(SELECTION_RECTANGLE_CLASS_NAME);
|
|
2332
|
-
PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
|
|
2333
3471
|
const angle = getSelectionAngle(elements);
|
|
2334
3472
|
if (angle) {
|
|
2335
3473
|
setAngleForG(selectionRectangleG, RectangleClient.getCenterPoint(rectangle), angle);
|
|
@@ -2570,11 +3708,16 @@ const createGroupRectangleG = (board, elements) => {
|
|
|
2570
3708
|
if (item.groupId && isRender) {
|
|
2571
3709
|
const elements = getElementsInGroupByElement(board, item);
|
|
2572
3710
|
const rectangle = getRectangleByElements(board, elements, false);
|
|
2573
|
-
|
|
3711
|
+
const rectangleG = drawRectangle(board, rectangle, {
|
|
2574
3712
|
stroke: SELECTION_BORDER_COLOR,
|
|
2575
3713
|
strokeWidth: ACTIVE_STROKE_WIDTH,
|
|
2576
3714
|
strokeLineDash: [5]
|
|
2577
|
-
})
|
|
3715
|
+
});
|
|
3716
|
+
const angle = getSelectionAngle(elements);
|
|
3717
|
+
if (angle) {
|
|
3718
|
+
setAngleForG(rectangleG, RectangleClient.getCenterPoint(rectangle), angle);
|
|
3719
|
+
}
|
|
3720
|
+
groupRectangleG.append(rectangleG);
|
|
2578
3721
|
}
|
|
2579
3722
|
});
|
|
2580
3723
|
return groupRectangleG;
|
|
@@ -2610,6 +3753,255 @@ const canRemoveGroup = (board, elements) => {
|
|
|
2610
3753
|
const selectedElements = elements || getSelectedElements(board);
|
|
2611
3754
|
return selectedElements.length > 0 && selectedGroups.length > 0;
|
|
2612
3755
|
};
|
|
3756
|
+
const getEditingGroup = (board, element) => {
|
|
3757
|
+
const groups = getGroupByElement(board, element, true);
|
|
3758
|
+
let editingGroup = null;
|
|
3759
|
+
if (groups?.length) {
|
|
3760
|
+
for (let i = 0; i < groups?.length; i++) {
|
|
3761
|
+
if (!isSelectedAllElementsInGroup(board, groups[i])) {
|
|
3762
|
+
editingGroup = groups[i];
|
|
3763
|
+
break;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
return editingGroup;
|
|
3768
|
+
};
|
|
3769
|
+
const moveElementsToNewPathAfterAddGroup = (board, selectedElements, newPath) => {
|
|
3770
|
+
const moveElements = [...selectedElements];
|
|
3771
|
+
sortElements(board, moveElements);
|
|
3772
|
+
moveElements.pop();
|
|
3773
|
+
moveElementsToNewPath(board, moveElements.map(element => {
|
|
3774
|
+
return {
|
|
3775
|
+
element,
|
|
3776
|
+
newPath
|
|
3777
|
+
};
|
|
3778
|
+
}));
|
|
3779
|
+
};
|
|
3780
|
+
|
|
3781
|
+
const deleteFragment = (board) => {
|
|
3782
|
+
const elements = board.getDeletedFragment([]);
|
|
3783
|
+
board.deleteFragment(elements);
|
|
3784
|
+
};
|
|
3785
|
+
const setFragment = (board, type, clipboardData) => {
|
|
3786
|
+
const selectedElements = getSelectedElements(board);
|
|
3787
|
+
const rectangle = getRectangleByElements(board, selectedElements, false);
|
|
3788
|
+
const clipboardContext = board.buildFragment(null, rectangle, type);
|
|
3789
|
+
clipboardContext && setClipboardData(clipboardData, clipboardContext);
|
|
3790
|
+
};
|
|
3791
|
+
const duplicateElements = (board, elements) => {
|
|
3792
|
+
const selectedElements = elements || getSelectedElements(board);
|
|
3793
|
+
const rectangle = getRectangleByElements(board, selectedElements, false);
|
|
3794
|
+
const clipboardContext = board.buildFragment(null, rectangle, 'copy');
|
|
3795
|
+
clipboardContext &&
|
|
3796
|
+
board.insertFragment({
|
|
3797
|
+
...clipboardContext,
|
|
3798
|
+
text: undefined
|
|
3799
|
+
}, [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]);
|
|
3800
|
+
};
|
|
3801
|
+
|
|
3802
|
+
const SNAP_TOLERANCE = 2;
|
|
3803
|
+
const SNAP_SPACING = 24;
|
|
3804
|
+
function getSnapRectangles(board, activeElements) {
|
|
3805
|
+
const elements = findElements(board, {
|
|
3806
|
+
match: element => board.isAlign(element) && !activeElements.some(item => item.id === element.id),
|
|
3807
|
+
recursion: () => true,
|
|
3808
|
+
isReverse: false
|
|
3809
|
+
});
|
|
3810
|
+
return elements.map(item => getRectangleByAngle(board.getRectangle(item), item.angle) || board.getRectangle(item));
|
|
3811
|
+
}
|
|
3812
|
+
function getBarPoint(point, isHorizontal) {
|
|
3813
|
+
return isHorizontal
|
|
3814
|
+
? [
|
|
3815
|
+
[point[0], point[1] - 4],
|
|
3816
|
+
[point[0], point[1] + 4]
|
|
3817
|
+
]
|
|
3818
|
+
: [
|
|
3819
|
+
[point[0] - 4, point[1]],
|
|
3820
|
+
[point[0] + 4, point[1]]
|
|
3821
|
+
];
|
|
3822
|
+
}
|
|
3823
|
+
function getMinPointDelta(pointRectangles, axis, isHorizontal) {
|
|
3824
|
+
let delta = SNAP_TOLERANCE;
|
|
3825
|
+
pointRectangles.forEach(item => {
|
|
3826
|
+
const distance = getNearestDelta(axis, item, isHorizontal);
|
|
3827
|
+
if (Math.abs(distance) < Math.abs(delta)) {
|
|
3828
|
+
delta = distance;
|
|
3829
|
+
}
|
|
3830
|
+
});
|
|
3831
|
+
return delta;
|
|
3832
|
+
}
|
|
3833
|
+
const getNearestDelta = (axis, rectangle, isHorizontal) => {
|
|
3834
|
+
const pointAxis = getTripleAxis(rectangle, isHorizontal);
|
|
3835
|
+
const deltas = pointAxis.map(item => item - axis);
|
|
3836
|
+
const absDeltas = deltas.map(item => Math.abs(item));
|
|
3837
|
+
const index = absDeltas.indexOf(Math.min(...absDeltas));
|
|
3838
|
+
return deltas[index];
|
|
3839
|
+
};
|
|
3840
|
+
const getTripleAxis = (rectangle, isHorizontal) => {
|
|
3841
|
+
const axis = isHorizontal ? 'x' : 'y';
|
|
3842
|
+
const side = isHorizontal ? 'width' : 'height';
|
|
3843
|
+
return [rectangle[axis], rectangle[axis] + rectangle[side] / 2, rectangle[axis] + rectangle[side]];
|
|
3844
|
+
};
|
|
3845
|
+
function getNearestPointRectangle(snapRectangles, activeRectangle) {
|
|
3846
|
+
let minDistance = Infinity;
|
|
3847
|
+
let nearestRectangle = snapRectangles[0];
|
|
3848
|
+
snapRectangles.forEach(item => {
|
|
3849
|
+
const distance = Math.sqrt(Math.pow(activeRectangle.x - item.x, 2) + Math.pow(activeRectangle.y - item.y, 2));
|
|
3850
|
+
if (distance < minDistance) {
|
|
3851
|
+
minDistance = distance;
|
|
3852
|
+
nearestRectangle = item;
|
|
3853
|
+
}
|
|
3854
|
+
});
|
|
3855
|
+
return nearestRectangle;
|
|
3856
|
+
}
|
|
3857
|
+
const isSnapPoint = (axis, rectangle, isHorizontal) => {
|
|
3858
|
+
const pointAxis = getTripleAxis(rectangle, isHorizontal);
|
|
3859
|
+
return pointAxis.includes(axis);
|
|
3860
|
+
};
|
|
3861
|
+
function drawPointSnapLines(board, activeRectangle, snapRectangles, drawHorizontal = true, drawVertical = true, snapMiddle = false) {
|
|
3862
|
+
let pointLinePoints = [];
|
|
3863
|
+
const pointAxisX = getTripleAxis(activeRectangle, true);
|
|
3864
|
+
const pointAxisY = getTripleAxis(activeRectangle, false);
|
|
3865
|
+
const pointLineRefs = [
|
|
3866
|
+
{
|
|
3867
|
+
axis: pointAxisX[0],
|
|
3868
|
+
isHorizontal: true,
|
|
3869
|
+
pointRectangles: []
|
|
3870
|
+
},
|
|
3871
|
+
{
|
|
3872
|
+
axis: pointAxisX[1],
|
|
3873
|
+
isHorizontal: true,
|
|
3874
|
+
pointRectangles: []
|
|
3875
|
+
},
|
|
3876
|
+
{
|
|
3877
|
+
axis: pointAxisX[2],
|
|
3878
|
+
isHorizontal: true,
|
|
3879
|
+
pointRectangles: []
|
|
3880
|
+
},
|
|
3881
|
+
{
|
|
3882
|
+
axis: pointAxisY[0],
|
|
3883
|
+
isHorizontal: false,
|
|
3884
|
+
pointRectangles: []
|
|
3885
|
+
},
|
|
3886
|
+
{
|
|
3887
|
+
axis: pointAxisY[1],
|
|
3888
|
+
isHorizontal: false,
|
|
3889
|
+
pointRectangles: []
|
|
3890
|
+
},
|
|
3891
|
+
{
|
|
3892
|
+
axis: pointAxisY[2],
|
|
3893
|
+
isHorizontal: false,
|
|
3894
|
+
pointRectangles: []
|
|
3895
|
+
}
|
|
3896
|
+
];
|
|
3897
|
+
for (let index = 0; index < snapRectangles.length; index++) {
|
|
3898
|
+
const element = snapRectangles[index];
|
|
3899
|
+
if (isSnapPoint(pointLineRefs[0].axis, element, pointLineRefs[0].isHorizontal)) {
|
|
3900
|
+
pointLineRefs[0].pointRectangles.push(element);
|
|
3901
|
+
}
|
|
3902
|
+
if (isSnapPoint(pointLineRefs[1].axis, element, pointLineRefs[1].isHorizontal)) {
|
|
3903
|
+
pointLineRefs[1].pointRectangles.push(element);
|
|
3904
|
+
}
|
|
3905
|
+
if (isSnapPoint(pointLineRefs[2].axis, element, pointLineRefs[2].isHorizontal)) {
|
|
3906
|
+
pointLineRefs[2].pointRectangles.push(element);
|
|
3907
|
+
}
|
|
3908
|
+
if (isSnapPoint(pointLineRefs[3].axis, element, pointLineRefs[3].isHorizontal)) {
|
|
3909
|
+
pointLineRefs[3].pointRectangles.push(element);
|
|
3910
|
+
}
|
|
3911
|
+
if (isSnapPoint(pointLineRefs[4].axis, element, pointLineRefs[4].isHorizontal)) {
|
|
3912
|
+
pointLineRefs[4].pointRectangles.push(element);
|
|
3913
|
+
}
|
|
3914
|
+
if (isSnapPoint(pointLineRefs[5].axis, element, pointLineRefs[5].isHorizontal)) {
|
|
3915
|
+
pointLineRefs[5].pointRectangles.push(element);
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
const setResizePointSnapLine = (axis, pointRectangle, isHorizontal) => {
|
|
3919
|
+
const boundingRectangle = RectangleClient.inflate(RectangleClient.getBoundingRectangle([activeRectangle, pointRectangle]), SNAP_SPACING);
|
|
3920
|
+
if (isHorizontal) {
|
|
3921
|
+
const pointStart = [axis, boundingRectangle.y];
|
|
3922
|
+
const pointEnd = [axis, boundingRectangle.y + boundingRectangle.height];
|
|
3923
|
+
pointLinePoints.push([pointStart, pointEnd]);
|
|
3924
|
+
}
|
|
3925
|
+
else {
|
|
3926
|
+
const pointStart = [boundingRectangle.x, axis];
|
|
3927
|
+
const pointEnd = [boundingRectangle.x + boundingRectangle.width, axis];
|
|
3928
|
+
pointLinePoints.push([pointStart, pointEnd]);
|
|
3929
|
+
}
|
|
3930
|
+
};
|
|
3931
|
+
if (drawHorizontal && pointLineRefs[0].pointRectangles.length) {
|
|
3932
|
+
const leftRectangle = pointLineRefs[0].pointRectangles.length === 1
|
|
3933
|
+
? pointLineRefs[0].pointRectangles[0]
|
|
3934
|
+
: getNearestPointRectangle(pointLineRefs[0].pointRectangles, activeRectangle);
|
|
3935
|
+
setResizePointSnapLine(pointLineRefs[0].axis, leftRectangle, pointLineRefs[0].isHorizontal);
|
|
3936
|
+
}
|
|
3937
|
+
if (drawHorizontal && snapMiddle && pointLineRefs[1].pointRectangles.length) {
|
|
3938
|
+
const middleRectangle = pointLineRefs[1].pointRectangles.length === 1
|
|
3939
|
+
? pointLineRefs[1].pointRectangles[0]
|
|
3940
|
+
: getNearestPointRectangle(pointLineRefs[1].pointRectangles, activeRectangle);
|
|
3941
|
+
setResizePointSnapLine(pointLineRefs[1].axis, middleRectangle, pointLineRefs[1].isHorizontal);
|
|
3942
|
+
}
|
|
3943
|
+
if (drawHorizontal && pointLineRefs[2].pointRectangles.length) {
|
|
3944
|
+
const rightRectangle = pointLineRefs[2].pointRectangles.length === 1
|
|
3945
|
+
? pointLineRefs[2].pointRectangles[0]
|
|
3946
|
+
: getNearestPointRectangle(pointLineRefs[2].pointRectangles, activeRectangle);
|
|
3947
|
+
setResizePointSnapLine(pointLineRefs[2].axis, rightRectangle, pointLineRefs[2].isHorizontal);
|
|
3948
|
+
}
|
|
3949
|
+
if (drawVertical && pointLineRefs[3].pointRectangles.length) {
|
|
3950
|
+
const topRectangle = pointLineRefs[3].pointRectangles.length === 1
|
|
3951
|
+
? pointLineRefs[3].pointRectangles[0]
|
|
3952
|
+
: getNearestPointRectangle(pointLineRefs[3].pointRectangles, activeRectangle);
|
|
3953
|
+
setResizePointSnapLine(pointLineRefs[3].axis, topRectangle, pointLineRefs[3].isHorizontal);
|
|
3954
|
+
}
|
|
3955
|
+
if (drawVertical && snapMiddle && pointLineRefs[4].pointRectangles.length) {
|
|
3956
|
+
const middleRectangle = pointLineRefs[4].pointRectangles.length === 1
|
|
3957
|
+
? pointLineRefs[4].pointRectangles[0]
|
|
3958
|
+
: getNearestPointRectangle(pointLineRefs[4].pointRectangles, activeRectangle);
|
|
3959
|
+
setResizePointSnapLine(pointLineRefs[4].axis, middleRectangle, pointLineRefs[4].isHorizontal);
|
|
3960
|
+
}
|
|
3961
|
+
if (drawVertical && pointLineRefs[5].pointRectangles.length) {
|
|
3962
|
+
const rightRectangle = pointLineRefs[5].pointRectangles.length === 1
|
|
3963
|
+
? pointLineRefs[5].pointRectangles[0]
|
|
3964
|
+
: getNearestPointRectangle(pointLineRefs[5].pointRectangles, activeRectangle);
|
|
3965
|
+
setResizePointSnapLine(pointLineRefs[5].axis, rightRectangle, pointLineRefs[5].isHorizontal);
|
|
3966
|
+
}
|
|
3967
|
+
return drawDashedLines(board, pointLinePoints);
|
|
3968
|
+
}
|
|
3969
|
+
function drawDashedLines(board, lines) {
|
|
3970
|
+
const g = createG();
|
|
3971
|
+
lines.forEach(points => {
|
|
3972
|
+
if (!points.length)
|
|
3973
|
+
return;
|
|
3974
|
+
const line = PlaitBoard.getRoughSVG(board).line(points[0][0], points[0][1], points[1][0], points[1][1], {
|
|
3975
|
+
stroke: SELECTION_BORDER_COLOR,
|
|
3976
|
+
strokeWidth: 1,
|
|
3977
|
+
strokeLineDash: [4, 4]
|
|
3978
|
+
});
|
|
3979
|
+
g.appendChild(line);
|
|
3980
|
+
});
|
|
3981
|
+
return g;
|
|
3982
|
+
}
|
|
3983
|
+
function drawSolidLines(board, lines) {
|
|
3984
|
+
const g = createG();
|
|
3985
|
+
lines.forEach(points => {
|
|
3986
|
+
if (!points.length)
|
|
3987
|
+
return;
|
|
3988
|
+
let isHorizontal = points[0][1] === points[1][1];
|
|
3989
|
+
const line = PlaitBoard.getRoughSVG(board).line(points[0][0], points[0][1], points[1][0], points[1][1], {
|
|
3990
|
+
stroke: SELECTION_BORDER_COLOR,
|
|
3991
|
+
strokeWidth: 1
|
|
3992
|
+
});
|
|
3993
|
+
g.appendChild(line);
|
|
3994
|
+
points.forEach(point => {
|
|
3995
|
+
const barPoint = getBarPoint(point, isHorizontal);
|
|
3996
|
+
const bar = PlaitBoard.getRoughSVG(board).line(barPoint[0][0], barPoint[0][1], barPoint[1][0], barPoint[1][1], {
|
|
3997
|
+
stroke: SELECTION_BORDER_COLOR,
|
|
3998
|
+
strokeWidth: 1
|
|
3999
|
+
});
|
|
4000
|
+
g.appendChild(bar);
|
|
4001
|
+
});
|
|
4002
|
+
});
|
|
4003
|
+
return g;
|
|
4004
|
+
}
|
|
2613
4005
|
|
|
2614
4006
|
const PlaitElement = {
|
|
2615
4007
|
isRootElement(value) {
|
|
@@ -2623,228 +4015,32 @@ const PlaitElement = {
|
|
|
2623
4015
|
},
|
|
2624
4016
|
getComponent(value) {
|
|
2625
4017
|
return ELEMENT_TO_COMPONENT.get(value);
|
|
2626
|
-
}
|
|
2627
|
-
};
|
|
2628
|
-
|
|
2629
|
-
const Path = {
|
|
2630
|
-
/**
|
|
2631
|
-
* Get a list of ancestor paths for a given path.
|
|
2632
|
-
*
|
|
2633
|
-
* The paths are sorted from shallowest to deepest ancestor. However, if the
|
|
2634
|
-
* `reverse: true` option is passed, they are reversed.
|
|
2635
|
-
*/
|
|
2636
|
-
ancestors(path, options = {}) {
|
|
2637
|
-
const { reverse = false } = options;
|
|
2638
|
-
let paths = Path.levels(path, options);
|
|
2639
|
-
if (reverse) {
|
|
2640
|
-
paths = paths.slice(1);
|
|
2641
|
-
}
|
|
2642
|
-
else {
|
|
2643
|
-
paths = paths.slice(0, -1);
|
|
2644
|
-
}
|
|
2645
|
-
return paths;
|
|
2646
|
-
},
|
|
2647
|
-
/**
|
|
2648
|
-
* Get a list of paths at every level down to a path. Note: this is the same
|
|
2649
|
-
* as `Path.ancestors`, but including the path itself.
|
|
2650
|
-
*
|
|
2651
|
-
* The paths are sorted from shallowest to deepest. However, if the `reverse:
|
|
2652
|
-
* true` option is passed, they are reversed.
|
|
2653
|
-
*/
|
|
2654
|
-
levels(path, options = {}) {
|
|
2655
|
-
const { reverse = false } = options;
|
|
2656
|
-
const list = [];
|
|
2657
|
-
for (let i = 0; i <= path.length; i++) {
|
|
2658
|
-
list.push(path.slice(0, i));
|
|
2659
|
-
}
|
|
2660
|
-
if (reverse) {
|
|
2661
|
-
list.reverse();
|
|
2662
|
-
}
|
|
2663
|
-
return list;
|
|
2664
|
-
},
|
|
2665
|
-
parent(path) {
|
|
2666
|
-
if (path.length === 0) {
|
|
2667
|
-
throw new Error(`Cannot get the parent path of the root path [${path}].`);
|
|
2668
|
-
}
|
|
2669
|
-
return path.slice(0, -1);
|
|
2670
|
-
},
|
|
2671
|
-
next(path) {
|
|
2672
|
-
if (path.length === 0) {
|
|
2673
|
-
throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
|
|
2674
|
-
}
|
|
2675
|
-
const last = path[path.length - 1];
|
|
2676
|
-
return path.slice(0, -1).concat(last + 1);
|
|
2677
|
-
},
|
|
2678
|
-
hasPrevious(path) {
|
|
2679
|
-
return path[path.length - 1] > 0;
|
|
2680
|
-
},
|
|
2681
|
-
previous(path) {
|
|
2682
|
-
if (path.length === 0) {
|
|
2683
|
-
throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
|
|
2684
|
-
}
|
|
2685
|
-
const last = path[path.length - 1];
|
|
2686
|
-
return path.slice(0, -1).concat(last - 1);
|
|
2687
|
-
},
|
|
2688
|
-
/**
|
|
2689
|
-
* Check if a path is an ancestor of another.
|
|
2690
|
-
*/
|
|
2691
|
-
isAncestor(path, another) {
|
|
2692
|
-
return path.length < another.length && Path.compare(path, another) === 0;
|
|
2693
4018
|
},
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
* Note: Two paths of unequal length can still receive a `0` result if one is
|
|
2699
|
-
* directly above or below the other. If you want exact matching, use
|
|
2700
|
-
* [[Path.equals]] instead.
|
|
2701
|
-
*/
|
|
2702
|
-
compare(path, another) {
|
|
2703
|
-
const min = Math.min(path.length, another.length);
|
|
2704
|
-
for (let i = 0; i < min; i++) {
|
|
2705
|
-
if (path[i] < another[i])
|
|
2706
|
-
return -1;
|
|
2707
|
-
if (path[i] > another[i])
|
|
2708
|
-
return 1;
|
|
4019
|
+
getElementG(value) {
|
|
4020
|
+
const g = NODE_TO_G.get(value);
|
|
4021
|
+
if (!g) {
|
|
4022
|
+
throw new Error(`can not resolve element g: ${JSON.stringify(value)}`);
|
|
2709
4023
|
}
|
|
2710
|
-
return
|
|
2711
|
-
},
|
|
2712
|
-
/**
|
|
2713
|
-
* Check if a path is exactly equal to another.
|
|
2714
|
-
*/
|
|
2715
|
-
equals(path, another) {
|
|
2716
|
-
return path.length === another.length && path.every((n, i) => n === another[i]);
|
|
2717
|
-
},
|
|
2718
|
-
/**
|
|
2719
|
-
* Check if a path ends before one of the indexes in another.
|
|
2720
|
-
*/
|
|
2721
|
-
endsBefore(path, another) {
|
|
2722
|
-
const i = path.length - 1;
|
|
2723
|
-
const as = path.slice(0, i);
|
|
2724
|
-
const bs = another.slice(0, i);
|
|
2725
|
-
const av = path[i];
|
|
2726
|
-
const bv = another[i];
|
|
2727
|
-
return Path.equals(as, bs) && av < bv;
|
|
4024
|
+
return g;
|
|
2728
4025
|
},
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
if (path.length !== another.length) {
|
|
2734
|
-
return false;
|
|
4026
|
+
hasMounted(element) {
|
|
4027
|
+
const containerG = PlaitElement.getContainerG(element, { suppressThrow: true });
|
|
4028
|
+
if (containerG) {
|
|
4029
|
+
return true;
|
|
2735
4030
|
}
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
const al = path[path.length - 1];
|
|
2739
|
-
const bl = another[another.length - 1];
|
|
2740
|
-
return al !== bl && Path.equals(as, bs);
|
|
2741
|
-
},
|
|
2742
|
-
transform(path, operation) {
|
|
2743
|
-
return produce(path, p => {
|
|
2744
|
-
// PERF: Exit early if the operation is guaranteed not to have an effect.
|
|
2745
|
-
if (!path || path?.length === 0) {
|
|
2746
|
-
return;
|
|
2747
|
-
}
|
|
2748
|
-
if (p === null) {
|
|
2749
|
-
return null;
|
|
2750
|
-
}
|
|
2751
|
-
switch (operation.type) {
|
|
2752
|
-
case 'insert_node': {
|
|
2753
|
-
const { path: op } = operation;
|
|
2754
|
-
if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
|
|
2755
|
-
p[op.length - 1] += 1;
|
|
2756
|
-
}
|
|
2757
|
-
break;
|
|
2758
|
-
}
|
|
2759
|
-
case 'remove_node': {
|
|
2760
|
-
const { path: op } = operation;
|
|
2761
|
-
if (Path.equals(op, p) || Path.isAncestor(op, p)) {
|
|
2762
|
-
return null;
|
|
2763
|
-
}
|
|
2764
|
-
else if (Path.endsBefore(op, p)) {
|
|
2765
|
-
p[op.length - 1] -= 1;
|
|
2766
|
-
}
|
|
2767
|
-
break;
|
|
2768
|
-
}
|
|
2769
|
-
case 'move_node': {
|
|
2770
|
-
const { path: op, newPath: onp } = operation;
|
|
2771
|
-
// If the old and new path are the same, it's a no-op.
|
|
2772
|
-
if (Path.equals(op, onp)) {
|
|
2773
|
-
return;
|
|
2774
|
-
}
|
|
2775
|
-
if (Path.isAncestor(op, p) || Path.equals(op, p)) {
|
|
2776
|
-
const copy = onp.slice();
|
|
2777
|
-
// op.length <= onp.length is different for slate
|
|
2778
|
-
// resolve drag from [0, 0] to [0, 3] issue
|
|
2779
|
-
if (Path.endsBefore(op, onp) && op.length <= onp.length) {
|
|
2780
|
-
copy[op.length - 1] -= 1;
|
|
2781
|
-
}
|
|
2782
|
-
return copy.concat(p.slice(op.length));
|
|
2783
|
-
}
|
|
2784
|
-
else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
|
|
2785
|
-
if (Path.endsBefore(op, p)) {
|
|
2786
|
-
p[op.length - 1] -= 1;
|
|
2787
|
-
}
|
|
2788
|
-
else {
|
|
2789
|
-
p[op.length - 1] += 1;
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
|
|
2793
|
-
if (Path.endsBefore(op, p)) {
|
|
2794
|
-
p[op.length - 1] -= 1;
|
|
2795
|
-
}
|
|
2796
|
-
p[onp.length - 1] += 1;
|
|
2797
|
-
}
|
|
2798
|
-
else if (Path.endsBefore(op, p)) {
|
|
2799
|
-
if (Path.equals(onp, p)) {
|
|
2800
|
-
p[onp.length - 1] += 1;
|
|
2801
|
-
}
|
|
2802
|
-
p[op.length - 1] -= 1;
|
|
2803
|
-
}
|
|
2804
|
-
break;
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
return p;
|
|
2808
|
-
});
|
|
2809
|
-
}
|
|
2810
|
-
};
|
|
2811
|
-
|
|
2812
|
-
const PlaitNode = {
|
|
2813
|
-
parent: (board, path) => {
|
|
2814
|
-
const parentPath = Path.parent(path);
|
|
2815
|
-
const p = PlaitNode.get(board, parentPath);
|
|
2816
|
-
return p;
|
|
2817
|
-
},
|
|
2818
|
-
/**
|
|
2819
|
-
* Return a generator of all the ancestor nodes above a specific path.
|
|
2820
|
-
*
|
|
2821
|
-
* By default the order is top-down, from highest to lowest ancestor in
|
|
2822
|
-
* the tree, but you can pass the `reverse: true` option to go bottom-up.
|
|
2823
|
-
*/
|
|
2824
|
-
*parents(root, path, options = {}) {
|
|
2825
|
-
for (const p of Path.ancestors(path, options)) {
|
|
2826
|
-
const n = PlaitNode.get(root, p);
|
|
2827
|
-
yield n;
|
|
4031
|
+
else {
|
|
4032
|
+
return false;
|
|
2828
4033
|
}
|
|
2829
4034
|
},
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
throw new Error(`Cannot find a descendant at path [${path}]`);
|
|
4035
|
+
getContainerG(value, options) {
|
|
4036
|
+
const containerG = NODE_TO_CONTAINER_G.get(value) || null;
|
|
4037
|
+
if (!containerG) {
|
|
4038
|
+
if (options.suppressThrow) {
|
|
4039
|
+
return null;
|
|
2836
4040
|
}
|
|
2837
|
-
|
|
2838
|
-
}
|
|
2839
|
-
return node;
|
|
2840
|
-
},
|
|
2841
|
-
last(board, path) {
|
|
2842
|
-
let n = PlaitNode.get(board, path);
|
|
2843
|
-
while (n && n.children && n.children.length > 0) {
|
|
2844
|
-
const i = n.children.length - 1;
|
|
2845
|
-
n = n.children[i];
|
|
4041
|
+
throw new Error('can not resolve container g');
|
|
2846
4042
|
}
|
|
2847
|
-
return
|
|
4043
|
+
return containerG;
|
|
2848
4044
|
}
|
|
2849
4045
|
};
|
|
2850
4046
|
|
|
@@ -2962,12 +4158,6 @@ const Point = {
|
|
|
2962
4158
|
}
|
|
2963
4159
|
};
|
|
2964
4160
|
|
|
2965
|
-
const Viewport = {
|
|
2966
|
-
isViewport: (value) => {
|
|
2967
|
-
return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
|
|
2968
|
-
}
|
|
2969
|
-
};
|
|
2970
|
-
|
|
2971
4161
|
const SAVING = new WeakMap();
|
|
2972
4162
|
const MERGING = new WeakMap();
|
|
2973
4163
|
|
|
@@ -3029,445 +4219,177 @@ var Direction;
|
|
|
3029
4219
|
|
|
3030
4220
|
const PlaitGroupElement = {
|
|
3031
4221
|
isGroup: (value) => {
|
|
3032
|
-
return value.type === 'group';
|
|
3033
|
-
}
|
|
3034
|
-
};
|
|
3035
|
-
|
|
3036
|
-
function getRectangleByElements(board, elements, recursion) {
|
|
3037
|
-
const rectanglesCornerPoints = [];
|
|
3038
|
-
const callback = (node) => {
|
|
3039
|
-
const nodeRectangle = board.getRectangle(node);
|
|
3040
|
-
if (nodeRectangle) {
|
|
3041
|
-
const cornerPoints = RectangleClient.getCornerPoints(nodeRectangle);
|
|
3042
|
-
const rotatedCornerPoints =
|
|
3043
|
-
rectanglesCornerPoints.push(rotatedCornerPoints);
|
|
3044
|
-
}
|
|
3045
|
-
else {
|
|
3046
|
-
console.error(`can not get rectangle of element:`, node);
|
|
3047
|
-
}
|
|
3048
|
-
};
|
|
3049
|
-
elements.forEach(element => {
|
|
3050
|
-
if (recursion) {
|
|
3051
|
-
depthFirstRecursion(element, node => callback(node), node => board.isRecursion(node));
|
|
3052
|
-
}
|
|
3053
|
-
else {
|
|
3054
|
-
callback(element);
|
|
3055
|
-
}
|
|
3056
|
-
});
|
|
3057
|
-
if (rectanglesCornerPoints.length > 0) {
|
|
3058
|
-
if (hasSameAngle(elements)) {
|
|
3059
|
-
const angle = getSelectionAngle(elements);
|
|
3060
|
-
return getRotatedBoundingRectangle(rectanglesCornerPoints, angle);
|
|
3061
|
-
}
|
|
3062
|
-
else {
|
|
3063
|
-
const flatCornerPoints = rectanglesCornerPoints.reduce((acc, val) => {
|
|
3064
|
-
return acc.concat(val);
|
|
3065
|
-
}, []);
|
|
3066
|
-
return RectangleClient.getRectangleByPoints(flatCornerPoints);
|
|
3067
|
-
}
|
|
3068
|
-
}
|
|
3069
|
-
else {
|
|
3070
|
-
return {
|
|
3071
|
-
x: 0,
|
|
3072
|
-
y: 0,
|
|
3073
|
-
width: 0,
|
|
3074
|
-
height: 0
|
|
3075
|
-
};
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
function getBoardRectangle(board) {
|
|
3079
|
-
return getRectangleByElements(board, board.children, true);
|
|
3080
|
-
}
|
|
3081
|
-
function getElementById(board, id, dataSource) {
|
|
3082
|
-
if (!dataSource) {
|
|
3083
|
-
dataSource = findElements(board, { match: element => true, recursion: element => true });
|
|
3084
|
-
}
|
|
3085
|
-
let element = dataSource.find(element => element.id === id);
|
|
3086
|
-
return element;
|
|
3087
|
-
}
|
|
3088
|
-
function findElements(board, options) {
|
|
3089
|
-
let elements = [];
|
|
3090
|
-
const isReverse = options.isReverse ?? true;
|
|
3091
|
-
depthFirstRecursion(board, node => {
|
|
3092
|
-
if (!PlaitBoard.isBoard(node) && options.match(node)) {
|
|
3093
|
-
elements.push(node);
|
|
3094
|
-
}
|
|
3095
|
-
}, (value) => {
|
|
3096
|
-
if (PlaitBoard.isBoard(value)) {
|
|
3097
|
-
return true;
|
|
3098
|
-
}
|
|
3099
|
-
else {
|
|
3100
|
-
return getIsRecursionFunc(board)(value) && options.recursion(value);
|
|
3101
|
-
}
|
|
3102
|
-
}, isReverse);
|
|
3103
|
-
return elements;
|
|
3104
|
-
}
|
|
3105
|
-
|
|
3106
|
-
const PlaitBoard = {
|
|
3107
|
-
isBoard(value) {
|
|
3108
|
-
const cachedIsBoard = IS_BOARD_CACHE.get(value);
|
|
3109
|
-
if (cachedIsBoard !== undefined) {
|
|
3110
|
-
return cachedIsBoard;
|
|
3111
|
-
}
|
|
3112
|
-
const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
|
|
3113
|
-
IS_BOARD_CACHE.set(value, isBoard);
|
|
3114
|
-
return isBoard;
|
|
3115
|
-
},
|
|
3116
|
-
findPath(board, node) {
|
|
3117
|
-
const path = [];
|
|
3118
|
-
let child = node;
|
|
3119
|
-
while (true) {
|
|
3120
|
-
const parent = NODE_TO_PARENT.get(child);
|
|
3121
|
-
if (parent == null) {
|
|
3122
|
-
if (PlaitBoard.isBoard(child)) {
|
|
3123
|
-
return path;
|
|
3124
|
-
}
|
|
3125
|
-
else {
|
|
3126
|
-
break;
|
|
3127
|
-
}
|
|
3128
|
-
}
|
|
3129
|
-
const i = NODE_TO_INDEX.get(child);
|
|
3130
|
-
if (i == null) {
|
|
3131
|
-
break;
|
|
3132
|
-
}
|
|
3133
|
-
path.unshift(i);
|
|
3134
|
-
child = parent;
|
|
3135
|
-
}
|
|
3136
|
-
throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
|
|
3137
|
-
},
|
|
3138
|
-
getHost(board) {
|
|
3139
|
-
return BOARD_TO_HOST.get(board);
|
|
3140
|
-
},
|
|
3141
|
-
getElementHost(board) {
|
|
3142
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.host;
|
|
3143
|
-
},
|
|
3144
|
-
getElementUpperHost(board) {
|
|
3145
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
|
|
3146
|
-
},
|
|
3147
|
-
getElementActiveHost(board) {
|
|
3148
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
|
|
3149
|
-
},
|
|
3150
|
-
getRoughSVG(board) {
|
|
3151
|
-
return BOARD_TO_ROUGH_SVG.get(board);
|
|
3152
|
-
},
|
|
3153
|
-
getComponent(board) {
|
|
3154
|
-
return BOARD_TO_COMPONENT.get(board);
|
|
3155
|
-
},
|
|
3156
|
-
getBoardContainer(board) {
|
|
3157
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.container;
|
|
3158
|
-
},
|
|
3159
|
-
getRectangle(board) {
|
|
3160
|
-
return getRectangleByElements(board, board.children, true);
|
|
3161
|
-
},
|
|
3162
|
-
getViewportContainer(board) {
|
|
3163
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.viewportContainer;
|
|
3164
|
-
},
|
|
3165
|
-
isFocus(board) {
|
|
3166
|
-
return !!board.selection;
|
|
3167
|
-
},
|
|
3168
|
-
isReadonly(board) {
|
|
3169
|
-
return board.options.readonly;
|
|
3170
|
-
},
|
|
3171
|
-
hasBeenTextEditing(board) {
|
|
3172
|
-
return !!IS_TEXT_EDITABLE.get(board);
|
|
3173
|
-
},
|
|
3174
|
-
getPointer(board) {
|
|
3175
|
-
return board.pointer;
|
|
3176
|
-
},
|
|
3177
|
-
isPointer(board, pointer) {
|
|
3178
|
-
return board.pointer === pointer;
|
|
3179
|
-
},
|
|
3180
|
-
isInPointer(board, pointers) {
|
|
3181
|
-
const point = board.pointer;
|
|
3182
|
-
return pointers.includes(point);
|
|
3183
|
-
},
|
|
3184
|
-
getMovingPointInBoard(board) {
|
|
3185
|
-
return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
|
|
3186
|
-
},
|
|
3187
|
-
isMovingPointInBoard(board) {
|
|
3188
|
-
const point = BOARD_TO_MOVING_POINT.get(board);
|
|
3189
|
-
const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
3190
|
-
if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
|
|
3191
|
-
return true;
|
|
3192
|
-
}
|
|
3193
|
-
return false;
|
|
3194
|
-
},
|
|
3195
|
-
getThemeColors(board) {
|
|
3196
|
-
return (board.options.themeColors || ThemeColors);
|
|
3197
|
-
}
|
|
3198
|
-
};
|
|
3199
|
-
|
|
3200
|
-
const applyToDraft = (board, selection, viewport, theme, op) => {
|
|
3201
|
-
switch (op.type) {
|
|
3202
|
-
case 'insert_node': {
|
|
3203
|
-
const { path, node } = op;
|
|
3204
|
-
const parent = PlaitNode.parent(board, path);
|
|
3205
|
-
const index = path[path.length - 1];
|
|
3206
|
-
if (!parent.children || index > parent.children.length) {
|
|
3207
|
-
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
3208
|
-
}
|
|
3209
|
-
parent.children.splice(index, 0, node);
|
|
3210
|
-
break;
|
|
3211
|
-
}
|
|
3212
|
-
case 'remove_node': {
|
|
3213
|
-
const { path } = op;
|
|
3214
|
-
const parent = PlaitNode.parent(board, path);
|
|
3215
|
-
const index = path[path.length - 1];
|
|
3216
|
-
if (!parent.children || index > parent.children.length) {
|
|
3217
|
-
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
3218
|
-
}
|
|
3219
|
-
parent.children.splice(index, 1);
|
|
3220
|
-
break;
|
|
3221
|
-
}
|
|
3222
|
-
case 'move_node': {
|
|
3223
|
-
const { path, newPath } = op;
|
|
3224
|
-
if (Path.isAncestor(path, newPath)) {
|
|
3225
|
-
throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
|
|
3226
|
-
}
|
|
3227
|
-
const node = PlaitNode.get(board, path);
|
|
3228
|
-
const parent = PlaitNode.parent(board, path);
|
|
3229
|
-
const index = path[path.length - 1];
|
|
3230
|
-
// This is tricky, but since the `path` and `newPath` both refer to
|
|
3231
|
-
// the same snapshot in time, there's a mismatch. After either
|
|
3232
|
-
// removing the original position, the second step's path can be out
|
|
3233
|
-
// of date. So instead of using the `op.newPath` directly, we
|
|
3234
|
-
// transform `op.path` to ascertain what the `newPath` would be after
|
|
3235
|
-
// the operation was applied.
|
|
3236
|
-
parent.children?.splice(index, 1);
|
|
3237
|
-
const truePath = Path.transform(path, op);
|
|
3238
|
-
const newParent = PlaitNode.get(board, Path.parent(truePath));
|
|
3239
|
-
const newIndex = truePath[truePath.length - 1];
|
|
3240
|
-
newParent.children?.splice(newIndex, 0, node);
|
|
3241
|
-
break;
|
|
3242
|
-
}
|
|
3243
|
-
case 'set_node': {
|
|
3244
|
-
const { path, properties, newProperties } = op;
|
|
3245
|
-
if (path.length === 0) {
|
|
3246
|
-
throw new Error(`Cannot set properties on the root node!`);
|
|
3247
|
-
}
|
|
3248
|
-
const node = PlaitNode.get(board, path);
|
|
3249
|
-
for (const key in newProperties) {
|
|
3250
|
-
const value = newProperties[key];
|
|
3251
|
-
if (value == null) {
|
|
3252
|
-
delete node[key];
|
|
3253
|
-
}
|
|
3254
|
-
else {
|
|
3255
|
-
node[key] = value;
|
|
3256
|
-
}
|
|
3257
|
-
}
|
|
3258
|
-
// properties that were previously defined, but are now missing, must be deleted
|
|
3259
|
-
for (const key in properties) {
|
|
3260
|
-
if (!newProperties.hasOwnProperty(key)) {
|
|
3261
|
-
delete node[key];
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
break;
|
|
4222
|
+
return value.type === 'group';
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
|
|
4226
|
+
function getRectangleByElements(board, elements, recursion) {
|
|
4227
|
+
const rectanglesCornerPoints = [];
|
|
4228
|
+
const callback = (node) => {
|
|
4229
|
+
const nodeRectangle = board.getRectangle(node);
|
|
4230
|
+
if (nodeRectangle) {
|
|
4231
|
+
const cornerPoints = RectangleClient.getCornerPoints(nodeRectangle);
|
|
4232
|
+
const rotatedCornerPoints = rotatePointsByElement(cornerPoints, node) || cornerPoints;
|
|
4233
|
+
rectanglesCornerPoints.push(rotatedCornerPoints);
|
|
3265
4234
|
}
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
if (newProperties == null) {
|
|
3269
|
-
viewport = newProperties;
|
|
3270
|
-
}
|
|
3271
|
-
else {
|
|
3272
|
-
if (viewport == null) {
|
|
3273
|
-
if (!Viewport.isViewport(newProperties)) {
|
|
3274
|
-
throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
|
|
3275
|
-
}
|
|
3276
|
-
viewport = { ...newProperties };
|
|
3277
|
-
}
|
|
3278
|
-
for (const key in newProperties) {
|
|
3279
|
-
const value = newProperties[key];
|
|
3280
|
-
if (value == null) {
|
|
3281
|
-
delete viewport[key];
|
|
3282
|
-
}
|
|
3283
|
-
else {
|
|
3284
|
-
viewport[key] = value;
|
|
3285
|
-
}
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
break;
|
|
4235
|
+
else {
|
|
4236
|
+
console.error(`can not get rectangle of element:`, node);
|
|
3289
4237
|
}
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
}
|
|
3295
|
-
else {
|
|
3296
|
-
if (selection === null) {
|
|
3297
|
-
selection = op.newProperties;
|
|
3298
|
-
}
|
|
3299
|
-
else {
|
|
3300
|
-
selection = newProperties;
|
|
3301
|
-
}
|
|
3302
|
-
}
|
|
3303
|
-
break;
|
|
4238
|
+
};
|
|
4239
|
+
elements.forEach(element => {
|
|
4240
|
+
if (recursion) {
|
|
4241
|
+
depthFirstRecursion(element, node => callback(node), node => board.isRecursion(node));
|
|
3304
4242
|
}
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
theme = newProperties;
|
|
3308
|
-
break;
|
|
4243
|
+
else {
|
|
4244
|
+
callback(element);
|
|
3309
4245
|
}
|
|
3310
|
-
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
const
|
|
3314
|
-
|
|
3315
|
-
* Transform the board by an operation.
|
|
3316
|
-
*/
|
|
3317
|
-
transform(board, op) {
|
|
3318
|
-
board.children = createDraft(board.children);
|
|
3319
|
-
let viewport = board.viewport && createDraft(board.viewport);
|
|
3320
|
-
let selection = board.selection && createDraft(board.selection);
|
|
3321
|
-
let theme = board.theme && createDraft(board.theme);
|
|
3322
|
-
try {
|
|
3323
|
-
const state = applyToDraft(board, selection, viewport, theme, op);
|
|
3324
|
-
viewport = state.viewport;
|
|
3325
|
-
selection = state.selection;
|
|
3326
|
-
theme = state.theme;
|
|
4246
|
+
});
|
|
4247
|
+
if (rectanglesCornerPoints.length > 0) {
|
|
4248
|
+
if (hasSameAngle(elements)) {
|
|
4249
|
+
const angle = getSelectionAngle(elements);
|
|
4250
|
+
return getRotatedBoundingRectangle(rectanglesCornerPoints, angle);
|
|
3327
4251
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
else {
|
|
3334
|
-
board.selection = null;
|
|
3335
|
-
}
|
|
3336
|
-
board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
|
|
3337
|
-
board.theme = isDraft(theme) ? finishDraft(theme) : theme;
|
|
4252
|
+
else {
|
|
4253
|
+
const flatCornerPoints = rectanglesCornerPoints.reduce((acc, val) => {
|
|
4254
|
+
return acc.concat(val);
|
|
4255
|
+
}, []);
|
|
4256
|
+
return RectangleClient.getRectangleByPoints(flatCornerPoints);
|
|
3338
4257
|
}
|
|
3339
4258
|
}
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
const properties = {};
|
|
3348
|
-
const newProperties = {};
|
|
3349
|
-
const node = PlaitNode.get(board, path);
|
|
3350
|
-
for (const k in props) {
|
|
3351
|
-
if (node[k] !== props[k]) {
|
|
3352
|
-
if (node.hasOwnProperty(k)) {
|
|
3353
|
-
properties[k] = node[k];
|
|
3354
|
-
}
|
|
3355
|
-
if (props[k] != null)
|
|
3356
|
-
newProperties[k] = props[k];
|
|
3357
|
-
}
|
|
4259
|
+
else {
|
|
4260
|
+
return {
|
|
4261
|
+
x: 0,
|
|
4262
|
+
y: 0,
|
|
4263
|
+
width: 0,
|
|
4264
|
+
height: 0
|
|
4265
|
+
};
|
|
3358
4266
|
}
|
|
3359
|
-
const operation = { type: 'set_node', properties, newProperties, path };
|
|
3360
|
-
board.apply(operation);
|
|
3361
|
-
}
|
|
3362
|
-
function removeNode(board, path) {
|
|
3363
|
-
const node = PlaitNode.get(board, path);
|
|
3364
|
-
const operation = { type: 'remove_node', path, node };
|
|
3365
|
-
board.apply(operation);
|
|
3366
|
-
}
|
|
3367
|
-
function moveNode(board, path, newPath) {
|
|
3368
|
-
const operation = { type: 'move_node', path, newPath };
|
|
3369
|
-
board.apply(operation);
|
|
3370
4267
|
}
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
setNode,
|
|
3374
|
-
removeNode,
|
|
3375
|
-
moveNode
|
|
3376
|
-
};
|
|
3377
|
-
|
|
3378
|
-
function setSelection(board, selection) {
|
|
3379
|
-
const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
|
|
3380
|
-
board.apply(operation);
|
|
4268
|
+
function getBoardRectangle(board) {
|
|
4269
|
+
return getRectangleByElements(board, board.children, true);
|
|
3381
4270
|
}
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
};
|
|
3386
|
-
function addSelectionWithTemporaryElements(board, elements) {
|
|
3387
|
-
const timeoutId = setTimeout(() => {
|
|
3388
|
-
setSelection(board, { anchor: [0, 0], focus: [0, 0] });
|
|
3389
|
-
}, 0);
|
|
3390
|
-
let ref = getTemporaryRef(board);
|
|
3391
|
-
if (ref) {
|
|
3392
|
-
clearTimeout(ref.timeoutId);
|
|
3393
|
-
const currentElements = ref.elements;
|
|
3394
|
-
ref.elements.push(...elements.filter(element => !currentElements.includes(element)));
|
|
3395
|
-
ref.timeoutId = timeoutId;
|
|
3396
|
-
}
|
|
3397
|
-
else {
|
|
3398
|
-
BOARD_TO_TEMPORARY_ELEMENTS.set(board, { timeoutId, elements });
|
|
4271
|
+
function getElementById(board, id, dataSource) {
|
|
4272
|
+
if (!dataSource) {
|
|
4273
|
+
dataSource = findElements(board, { match: element => true, recursion: element => true });
|
|
3399
4274
|
}
|
|
4275
|
+
let element = dataSource.find(element => element.id === id);
|
|
4276
|
+
return element;
|
|
3400
4277
|
}
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
};
|
|
3412
|
-
})
|
|
3413
|
-
.forEach(action => {
|
|
3414
|
-
action();
|
|
3415
|
-
});
|
|
3416
|
-
};
|
|
3417
|
-
const CoreTransforms = {
|
|
3418
|
-
removeElements
|
|
3419
|
-
};
|
|
3420
|
-
|
|
3421
|
-
const addGroup = (board, elements) => {
|
|
3422
|
-
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3423
|
-
const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
|
|
3424
|
-
const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
|
|
3425
|
-
const group = createGroup();
|
|
3426
|
-
if (canAddGroup(board)) {
|
|
3427
|
-
highestSelectedElements.forEach(item => {
|
|
3428
|
-
const path = PlaitBoard.findPath(board, item);
|
|
3429
|
-
NodeTransforms.setNode(board, { groupId: group.id }, path);
|
|
3430
|
-
});
|
|
3431
|
-
if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
|
|
3432
|
-
const newGroupId = selectedIsolatedElements[0].groupId;
|
|
3433
|
-
NodeTransforms.insertNode(board, {
|
|
3434
|
-
...group,
|
|
3435
|
-
groupId: newGroupId
|
|
3436
|
-
}, [board.children.length]);
|
|
4278
|
+
function findElements(board, options) {
|
|
4279
|
+
let elements = [];
|
|
4280
|
+
const isReverse = options.isReverse ?? true;
|
|
4281
|
+
depthFirstRecursion(board, node => {
|
|
4282
|
+
if (!PlaitBoard.isBoard(node) && options.match(node)) {
|
|
4283
|
+
elements.push(node);
|
|
4284
|
+
}
|
|
4285
|
+
}, (value) => {
|
|
4286
|
+
if (PlaitBoard.isBoard(value)) {
|
|
4287
|
+
return true;
|
|
3437
4288
|
}
|
|
3438
4289
|
else {
|
|
3439
|
-
|
|
4290
|
+
return getIsRecursionFunc(board)(value) && options.recursion(value);
|
|
4291
|
+
}
|
|
4292
|
+
}, isReverse);
|
|
4293
|
+
return elements;
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
const PlaitBoard = {
|
|
4297
|
+
isBoard(value) {
|
|
4298
|
+
const cachedIsBoard = IS_BOARD_CACHE.get(value);
|
|
4299
|
+
if (cachedIsBoard !== undefined) {
|
|
4300
|
+
return cachedIsBoard;
|
|
4301
|
+
}
|
|
4302
|
+
const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
|
|
4303
|
+
IS_BOARD_CACHE.set(value, isBoard);
|
|
4304
|
+
return isBoard;
|
|
4305
|
+
},
|
|
4306
|
+
isAlive(board) {
|
|
4307
|
+
const isAlive = IS_BOARD_ALIVE.get(board);
|
|
4308
|
+
return !!isAlive;
|
|
4309
|
+
},
|
|
4310
|
+
findPath(board, node) {
|
|
4311
|
+
const path = [];
|
|
4312
|
+
let child = node;
|
|
4313
|
+
while (true) {
|
|
4314
|
+
const parent = NODE_TO_PARENT.get(child);
|
|
4315
|
+
if (parent == null) {
|
|
4316
|
+
if (PlaitBoard.isBoard(child)) {
|
|
4317
|
+
return path;
|
|
4318
|
+
}
|
|
4319
|
+
else {
|
|
4320
|
+
break;
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
const i = NODE_TO_INDEX.get(child);
|
|
4324
|
+
if (i == null) {
|
|
4325
|
+
break;
|
|
4326
|
+
}
|
|
4327
|
+
path.unshift(i);
|
|
4328
|
+
child = parent;
|
|
4329
|
+
}
|
|
4330
|
+
throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
|
|
4331
|
+
},
|
|
4332
|
+
getHost(board) {
|
|
4333
|
+
return BOARD_TO_HOST.get(board);
|
|
4334
|
+
},
|
|
4335
|
+
getElementHost(board) {
|
|
4336
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.host;
|
|
4337
|
+
},
|
|
4338
|
+
getElementUpperHost(board) {
|
|
4339
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
|
|
4340
|
+
},
|
|
4341
|
+
getElementActiveHost(board) {
|
|
4342
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
|
|
4343
|
+
},
|
|
4344
|
+
getRoughSVG(board) {
|
|
4345
|
+
return BOARD_TO_ROUGH_SVG.get(board);
|
|
4346
|
+
},
|
|
4347
|
+
getComponent(board) {
|
|
4348
|
+
return BOARD_TO_COMPONENT.get(board);
|
|
4349
|
+
},
|
|
4350
|
+
getBoardContainer(board) {
|
|
4351
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.container;
|
|
4352
|
+
},
|
|
4353
|
+
getRectangle(board) {
|
|
4354
|
+
return getRectangleByElements(board, board.children, true);
|
|
4355
|
+
},
|
|
4356
|
+
getViewportContainer(board) {
|
|
4357
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.viewportContainer;
|
|
4358
|
+
},
|
|
4359
|
+
isFocus(board) {
|
|
4360
|
+
return !!board.selection;
|
|
4361
|
+
},
|
|
4362
|
+
isReadonly(board) {
|
|
4363
|
+
return board.options.readonly;
|
|
4364
|
+
},
|
|
4365
|
+
hasBeenTextEditing(board) {
|
|
4366
|
+
return !!IS_TEXT_EDITABLE.get(board);
|
|
4367
|
+
},
|
|
4368
|
+
getPointer(board) {
|
|
4369
|
+
return board.pointer;
|
|
4370
|
+
},
|
|
4371
|
+
isPointer(board, pointer) {
|
|
4372
|
+
return board.pointer === pointer;
|
|
4373
|
+
},
|
|
4374
|
+
isInPointer(board, pointers) {
|
|
4375
|
+
const point = board.pointer;
|
|
4376
|
+
return pointers.includes(point);
|
|
4377
|
+
},
|
|
4378
|
+
getMovingPointInBoard(board) {
|
|
4379
|
+
return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
|
|
4380
|
+
},
|
|
4381
|
+
isMovingPointInBoard(board) {
|
|
4382
|
+
const point = BOARD_TO_MOVING_POINT.get(board);
|
|
4383
|
+
const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
4384
|
+
if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
|
|
4385
|
+
return true;
|
|
3440
4386
|
}
|
|
4387
|
+
return false;
|
|
4388
|
+
},
|
|
4389
|
+
getThemeColors(board) {
|
|
4390
|
+
return (board.options.themeColors || ThemeColors);
|
|
3441
4391
|
}
|
|
3442
4392
|
};
|
|
3443
|
-
const removeGroup = (board, elements) => {
|
|
3444
|
-
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3445
|
-
if (canRemoveGroup(board)) {
|
|
3446
|
-
selectedGroups.map(group => {
|
|
3447
|
-
const elementsInGroup = findElements(board, {
|
|
3448
|
-
match: item => item.groupId === group.id,
|
|
3449
|
-
recursion: () => false
|
|
3450
|
-
});
|
|
3451
|
-
elementsInGroup.forEach(item => {
|
|
3452
|
-
const path = PlaitBoard.findPath(board, item);
|
|
3453
|
-
NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
|
|
3454
|
-
});
|
|
3455
|
-
const groupPath = PlaitBoard.findPath(board, group);
|
|
3456
|
-
NodeTransforms.removeNode(board, groupPath);
|
|
3457
|
-
});
|
|
3458
|
-
}
|
|
3459
|
-
};
|
|
3460
|
-
const GroupTransforms = {
|
|
3461
|
-
addGroup,
|
|
3462
|
-
removeGroup
|
|
3463
|
-
};
|
|
3464
|
-
|
|
3465
|
-
const Transforms = {
|
|
3466
|
-
...GeneralTransforms,
|
|
3467
|
-
...ViewportTransforms$1,
|
|
3468
|
-
...SelectionTransforms,
|
|
3469
|
-
...NodeTransforms
|
|
3470
|
-
};
|
|
3471
4393
|
|
|
3472
4394
|
const PathRef = {
|
|
3473
4395
|
transform(ref, op) {
|
|
@@ -3547,6 +4469,9 @@ function createBoard(children, options) {
|
|
|
3547
4469
|
},
|
|
3548
4470
|
onChange: () => { },
|
|
3549
4471
|
afterChange: () => { },
|
|
4472
|
+
drawActiveRectangle: () => {
|
|
4473
|
+
return drawEntireActiveRectangleG(board);
|
|
4474
|
+
},
|
|
3550
4475
|
mousedown: (event) => { },
|
|
3551
4476
|
mousemove: (event) => { },
|
|
3552
4477
|
mouseleave: (event) => { },
|
|
@@ -3557,19 +4482,16 @@ function createBoard(children, options) {
|
|
|
3557
4482
|
globalKeyDown: (event) => { },
|
|
3558
4483
|
keyUp: (event) => { },
|
|
3559
4484
|
dblClick: (event) => { },
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
insertFragment: (data) => { },
|
|
3564
|
-
deleteFragment: (data) => {
|
|
3565
|
-
const elements = board.getDeletedFragment([]);
|
|
4485
|
+
buildFragment: (clipboardContext) => clipboardContext,
|
|
4486
|
+
insertFragment: () => { },
|
|
4487
|
+
deleteFragment: (elements) => {
|
|
3566
4488
|
CoreTransforms.removeElements(board, elements);
|
|
3567
4489
|
},
|
|
3568
4490
|
getDeletedFragment: (data) => data,
|
|
3569
|
-
getRelatedFragment: (data) => data,
|
|
3570
|
-
drawElement: (context) =>
|
|
3571
|
-
|
|
3572
|
-
|
|
4491
|
+
getRelatedFragment: (data, originData) => data,
|
|
4492
|
+
drawElement: (context) => {
|
|
4493
|
+
throw new Error(`can not resolve plugin element component type: ${context.element.type}`);
|
|
4494
|
+
},
|
|
3573
4495
|
isWithinSelection: element => false,
|
|
3574
4496
|
isRectangleHit: element => false,
|
|
3575
4497
|
isHit: element => false,
|
|
@@ -3588,7 +4510,9 @@ function createBoard(children, options) {
|
|
|
3588
4510
|
globalPointerMove: pointer => { },
|
|
3589
4511
|
globalPointerUp: pointer => { },
|
|
3590
4512
|
isImageBindingAllowed: (element) => false,
|
|
3591
|
-
canAddToGroup: (element) => true
|
|
4513
|
+
canAddToGroup: (element) => true,
|
|
4514
|
+
canSetZIndex: (element) => true,
|
|
4515
|
+
isExpanded: (element) => true
|
|
3592
4516
|
};
|
|
3593
4517
|
return board;
|
|
3594
4518
|
}
|
|
@@ -3751,7 +4675,7 @@ function withHandPointer(board) {
|
|
|
3751
4675
|
}
|
|
3752
4676
|
|
|
3753
4677
|
function withSelection(board) {
|
|
3754
|
-
const { pointerDown, pointerUp, pointerMove, globalPointerUp, onChange, afterChange } = board;
|
|
4678
|
+
const { pointerDown, pointerUp, pointerMove, globalPointerUp, onChange, afterChange, drawActiveRectangle } = board;
|
|
3755
4679
|
let start = null;
|
|
3756
4680
|
let end = null;
|
|
3757
4681
|
let selectionMovingG;
|
|
@@ -3912,7 +4836,8 @@ function withSelection(board) {
|
|
|
3912
4836
|
if (!isSelectionMoving(board)) {
|
|
3913
4837
|
selectionRectangleG?.remove();
|
|
3914
4838
|
if (newElements.length > 1) {
|
|
3915
|
-
selectionRectangleG =
|
|
4839
|
+
selectionRectangleG = board.drawActiveRectangle();
|
|
4840
|
+
PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
|
|
3916
4841
|
}
|
|
3917
4842
|
}
|
|
3918
4843
|
}
|
|
@@ -3931,7 +4856,8 @@ function withSelection(board) {
|
|
|
3931
4856
|
(currentSelectedElements.length !== previousSelectedElements.length ||
|
|
3932
4857
|
currentSelectedElements.some((c, index) => c !== previousSelectedElements[index]))) {
|
|
3933
4858
|
selectionRectangleG?.remove();
|
|
3934
|
-
selectionRectangleG =
|
|
4859
|
+
selectionRectangleG = board.drawActiveRectangle();
|
|
4860
|
+
PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
|
|
3935
4861
|
previousSelectedElements = currentSelectedElements;
|
|
3936
4862
|
}
|
|
3937
4863
|
}
|
|
@@ -3980,354 +4906,194 @@ function withViewport(board) {
|
|
|
3980
4906
|
return board;
|
|
3981
4907
|
}
|
|
3982
4908
|
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4909
|
+
function getSnapMovingRef(board, activeRectangle, activeElements) {
|
|
4910
|
+
const snapRectangles = getSnapRectangles(board, activeElements);
|
|
4911
|
+
const snapG = createG();
|
|
4912
|
+
let snapDelta = getPointLineDelta(activeRectangle, snapRectangles);
|
|
4913
|
+
const pointLinesG = drawMovingPointSnapLines(board, snapDelta, activeRectangle, snapRectangles);
|
|
4914
|
+
snapG.append(pointLinesG);
|
|
4915
|
+
const result = getGapSnapLinesAndDelta(board, snapDelta, activeRectangle, snapRectangles);
|
|
4916
|
+
snapDelta = result.snapDelta;
|
|
4917
|
+
snapG.append(result.snapG);
|
|
4918
|
+
return { ...snapDelta, snapG };
|
|
4919
|
+
}
|
|
4920
|
+
function getPointLineDeltas(activeRectangle, snapRectangles, isHorizontal) {
|
|
4921
|
+
const axis = getTripleAxis(activeRectangle, isHorizontal);
|
|
4922
|
+
const deltaStart = getMinPointDelta(snapRectangles, axis[0], isHorizontal);
|
|
4923
|
+
const deltaMiddle = getMinPointDelta(snapRectangles, axis[1], isHorizontal);
|
|
4924
|
+
const deltaEnd = getMinPointDelta(snapRectangles, axis[2], isHorizontal);
|
|
4925
|
+
return [deltaStart, deltaMiddle, deltaEnd];
|
|
4926
|
+
}
|
|
4927
|
+
function getPointLineDelta(activeRectangle, snapRectangles) {
|
|
4928
|
+
let snapDelta = {
|
|
4929
|
+
deltaX: 0,
|
|
4930
|
+
deltaY: 0
|
|
4931
|
+
};
|
|
4932
|
+
function getDelta(isHorizontal) {
|
|
4933
|
+
let delta = 0;
|
|
4934
|
+
const deltas = getPointLineDeltas(activeRectangle, snapRectangles, isHorizontal);
|
|
4935
|
+
for (let i = 0; i < deltas.length; i++) {
|
|
4936
|
+
if (Math.abs(deltas[i]) < SNAP_TOLERANCE) {
|
|
4937
|
+
delta = deltas[i];
|
|
4938
|
+
break;
|
|
4005
4939
|
}
|
|
4006
|
-
}
|
|
4007
|
-
return
|
|
4940
|
+
}
|
|
4941
|
+
return delta;
|
|
4008
4942
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4943
|
+
snapDelta.deltaX = getDelta(true);
|
|
4944
|
+
snapDelta.deltaY = getDelta(false);
|
|
4945
|
+
return snapDelta;
|
|
4946
|
+
}
|
|
4947
|
+
function updateActiveRectangle(snapDelta, activeRectangle) {
|
|
4948
|
+
const { deltaX, deltaY } = snapDelta;
|
|
4949
|
+
const { x, y, width, height } = activeRectangle;
|
|
4950
|
+
return {
|
|
4951
|
+
x: x + deltaX,
|
|
4952
|
+
y: y + deltaY,
|
|
4953
|
+
width,
|
|
4954
|
+
height
|
|
4955
|
+
};
|
|
4956
|
+
}
|
|
4957
|
+
function drawMovingPointSnapLines(board, snapDelta, activeRectangle, snapRectangles) {
|
|
4958
|
+
const newActiveRectangle = updateActiveRectangle(snapDelta, activeRectangle);
|
|
4959
|
+
return drawPointSnapLines(board, newActiveRectangle, snapRectangles, true, true, true);
|
|
4960
|
+
}
|
|
4961
|
+
function getGapSnapLinesAndDelta(board, snapDelta, activeRectangle, snapRectangles) {
|
|
4962
|
+
let deltaX = snapDelta.deltaX;
|
|
4963
|
+
let deltaY = snapDelta.deltaY;
|
|
4964
|
+
const gapHorizontalResult = getGapLinesAndDelta(activeRectangle, snapRectangles, true);
|
|
4965
|
+
const gapVerticalResult = getGapLinesAndDelta(activeRectangle, snapRectangles, false);
|
|
4966
|
+
const gapSnapLines = [...gapHorizontalResult.lines, ...gapVerticalResult.lines];
|
|
4967
|
+
if (gapHorizontalResult.delta) {
|
|
4968
|
+
deltaX = gapHorizontalResult.delta;
|
|
4969
|
+
}
|
|
4970
|
+
if (gapVerticalResult.delta) {
|
|
4971
|
+
deltaY = gapVerticalResult.delta;
|
|
4972
|
+
}
|
|
4973
|
+
return {
|
|
4974
|
+
snapDelta: { deltaX, deltaY },
|
|
4975
|
+
snapG: drawSolidLines(board, gapSnapLines)
|
|
4976
|
+
};
|
|
4977
|
+
}
|
|
4978
|
+
function getGapLinesAndDelta(activeRectangle, snapRectangles, isHorizontal) {
|
|
4979
|
+
let lines = [];
|
|
4980
|
+
let delta = 0;
|
|
4981
|
+
let rectangles = [];
|
|
4982
|
+
const axis = isHorizontal ? 'x' : 'y';
|
|
4983
|
+
const side = isHorizontal ? 'width' : 'height';
|
|
4984
|
+
const activeRectangleCenter = activeRectangle[axis] + activeRectangle[side] / 2;
|
|
4985
|
+
snapRectangles.forEach(rec => {
|
|
4986
|
+
const isCross = isHorizontal ? isHorizontalCross(rec, activeRectangle) : isVerticalCross(rec, activeRectangle);
|
|
4987
|
+
if (isCross && !RectangleClient.isHit(rec, activeRectangle)) {
|
|
4988
|
+
rectangles.push(rec);
|
|
4989
|
+
}
|
|
4990
|
+
});
|
|
4991
|
+
rectangles = [...rectangles, activeRectangle].sort((a, b) => a[axis] - b[axis]);
|
|
4992
|
+
const refArray = [];
|
|
4993
|
+
let gapDistance = 0;
|
|
4994
|
+
let beforeIndex = undefined;
|
|
4995
|
+
let afterIndex = undefined;
|
|
4996
|
+
for (let i = 0; i < rectangles.length; i++) {
|
|
4997
|
+
for (let j = i + 1; j < rectangles.length; j++) {
|
|
4998
|
+
const before = rectangles[i];
|
|
4999
|
+
const after = rectangles[j];
|
|
5000
|
+
const distance = after[axis] - (before[axis] + before[side]);
|
|
5001
|
+
let dif = Infinity;
|
|
5002
|
+
if (refArray[i]?.after) {
|
|
5003
|
+
refArray[i].after.push({ distance, index: j });
|
|
4029
5004
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
alignRectangle.y,
|
|
4033
|
-
alignRectangle.y + alignRectangle.height,
|
|
4034
|
-
this.activeRectangle.y,
|
|
4035
|
-
this.activeRectangle.y + this.activeRectangle.height
|
|
4036
|
-
];
|
|
4037
|
-
const lineTopY = Math.min(...verticalY) - offset;
|
|
4038
|
-
const lineBottomY = Math.max(...verticalY) + offset;
|
|
4039
|
-
const leftLine = [this.activeRectangle.x, lineTopY, this.activeRectangle.x, lineBottomY];
|
|
4040
|
-
const middleLine = [
|
|
4041
|
-
this.activeRectangle.x + this.activeRectangle.width / 2,
|
|
4042
|
-
lineTopY,
|
|
4043
|
-
this.activeRectangle.x + this.activeRectangle.width / 2,
|
|
4044
|
-
lineBottomY
|
|
4045
|
-
];
|
|
4046
|
-
const rightLine = [
|
|
4047
|
-
this.activeRectangle.x + this.activeRectangle.width,
|
|
4048
|
-
lineTopY,
|
|
4049
|
-
this.activeRectangle.x + this.activeRectangle.width,
|
|
4050
|
-
lineBottomY
|
|
4051
|
-
];
|
|
4052
|
-
const shouldDrawLeftLine = closestDistances.indexX === 0 ||
|
|
4053
|
-
closestDistances.indexX === 1 ||
|
|
4054
|
-
(closestDistances.indexX === 2 && this.activeRectangle.width === alignRectangle.width);
|
|
4055
|
-
if (shouldDrawLeftLine && !alignLines[0]) {
|
|
4056
|
-
alignLines[0] = leftLine;
|
|
4057
|
-
}
|
|
4058
|
-
const shouldDrawRightLine = closestDistances.indexX === 2 ||
|
|
4059
|
-
closestDistances.indexX === 3 ||
|
|
4060
|
-
(closestDistances.indexX === 0 && this.activeRectangle.width === alignRectangle.width);
|
|
4061
|
-
if (shouldDrawRightLine && !alignLines[2]) {
|
|
4062
|
-
alignLines[2] = rightLine;
|
|
4063
|
-
}
|
|
4064
|
-
const shouldDrawMiddleLine = closestDistances.indexX === 4 || (!shouldDrawLeftLine && !shouldDrawRightLine);
|
|
4065
|
-
if (shouldDrawMiddleLine && !alignLines[1]) {
|
|
4066
|
-
alignLines[1] = middleLine;
|
|
4067
|
-
}
|
|
4068
|
-
isCorrectX = true;
|
|
5005
|
+
else {
|
|
5006
|
+
refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
|
|
4069
5007
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
deltaY = closestDistances.yDistance;
|
|
4073
|
-
this.activeRectangle.y -= deltaY;
|
|
4074
|
-
isCorrectY = true;
|
|
4075
|
-
canDrawVertical = true;
|
|
5008
|
+
if (refArray[j]?.before) {
|
|
5009
|
+
refArray[j].before.push({ distance, index: i });
|
|
4076
5010
|
}
|
|
4077
|
-
|
|
4078
|
-
|
|
5011
|
+
else {
|
|
5012
|
+
refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
|
|
4079
5013
|
}
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
const lineRightX = Math.max(...horizontalX) + offset;
|
|
4089
|
-
const topLine = [lineLeftX, this.activeRectangle.y, lineRightX, this.activeRectangle.y];
|
|
4090
|
-
const horizontalMiddleLine = [
|
|
4091
|
-
lineLeftX,
|
|
4092
|
-
this.activeRectangle.y + this.activeRectangle.height / 2,
|
|
4093
|
-
lineRightX,
|
|
4094
|
-
this.activeRectangle.y + this.activeRectangle.height / 2
|
|
4095
|
-
];
|
|
4096
|
-
const bottomLine = [
|
|
4097
|
-
lineLeftX,
|
|
4098
|
-
this.activeRectangle.y + this.activeRectangle.height,
|
|
4099
|
-
lineRightX,
|
|
4100
|
-
this.activeRectangle.y + this.activeRectangle.height
|
|
4101
|
-
];
|
|
4102
|
-
const shouldDrawTopLine = closestDistances.indexY === 0 ||
|
|
4103
|
-
closestDistances.indexY === 1 ||
|
|
4104
|
-
(closestDistances.indexY === 2 && this.activeRectangle.height === alignRectangle.height);
|
|
4105
|
-
if (shouldDrawTopLine && !alignLines[3]) {
|
|
4106
|
-
alignLines[3] = topLine;
|
|
4107
|
-
}
|
|
4108
|
-
const shouldDrawBottomLine = closestDistances.indexY === 2 ||
|
|
4109
|
-
closestDistances.indexY === 3 ||
|
|
4110
|
-
(closestDistances.indexY === 0 && this.activeRectangle.width === alignRectangle.width);
|
|
4111
|
-
if (shouldDrawBottomLine && !alignLines[5]) {
|
|
4112
|
-
alignLines[5] = bottomLine;
|
|
4113
|
-
}
|
|
4114
|
-
const shouldDrawMiddleLine = closestDistances.indexY === 4 || (!shouldDrawTopLine && !shouldDrawBottomLine);
|
|
4115
|
-
if (shouldDrawMiddleLine && !alignLines[4]) {
|
|
4116
|
-
alignLines[4] = horizontalMiddleLine;
|
|
4117
|
-
}
|
|
5014
|
+
//middle
|
|
5015
|
+
let _center = (before[axis] + before[side] + after[axis]) / 2;
|
|
5016
|
+
dif = Math.abs(_center - activeRectangleCenter);
|
|
5017
|
+
if (dif < SNAP_TOLERANCE) {
|
|
5018
|
+
gapDistance = (after[axis] - (before[axis] + before[side]) - activeRectangle[side]) / 2;
|
|
5019
|
+
delta = _center - activeRectangleCenter;
|
|
5020
|
+
beforeIndex = i;
|
|
5021
|
+
afterIndex = j;
|
|
4118
5022
|
}
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
if (distributeHorizontalResult.delta) {
|
|
4128
|
-
deltaX = distributeHorizontalResult.delta;
|
|
4129
|
-
if (alignDeltaX !== deltaX) {
|
|
4130
|
-
alignLines[0] = [];
|
|
4131
|
-
alignLines[1] = [];
|
|
4132
|
-
alignLines[2] = [];
|
|
5023
|
+
//after
|
|
5024
|
+
const distanceRight = after[axis] - (before[axis] + before[side]);
|
|
5025
|
+
_center = after[axis] + after[side] + distanceRight + activeRectangle[side] / 2;
|
|
5026
|
+
dif = Math.abs(_center - activeRectangleCenter);
|
|
5027
|
+
if (!gapDistance && dif < SNAP_TOLERANCE) {
|
|
5028
|
+
gapDistance = distanceRight;
|
|
5029
|
+
beforeIndex = j;
|
|
5030
|
+
delta = _center - activeRectangleCenter;
|
|
4133
5031
|
}
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
5032
|
+
//before
|
|
5033
|
+
const distanceBefore = after[axis] - (before[axis] + before[side]);
|
|
5034
|
+
_center = before[axis] - distanceBefore - activeRectangle[side] / 2;
|
|
5035
|
+
dif = Math.abs(_center - activeRectangleCenter);
|
|
5036
|
+
if (!gapDistance && dif < SNAP_TOLERANCE) {
|
|
5037
|
+
gapDistance = distanceBefore;
|
|
5038
|
+
afterIndex = i;
|
|
5039
|
+
delta = _center - activeRectangleCenter;
|
|
4141
5040
|
}
|
|
4142
5041
|
}
|
|
4143
|
-
if (alignLines.length) {
|
|
4144
|
-
this.drawAlignLines(alignLines, g);
|
|
4145
|
-
}
|
|
4146
|
-
if (distributeLines.length) {
|
|
4147
|
-
this.drawDistributeLines(distributeLines, g);
|
|
4148
|
-
}
|
|
4149
|
-
return { deltaX, deltaY, g };
|
|
4150
|
-
}
|
|
4151
|
-
calculateClosestDistances(activeRectangle, alignRectangle) {
|
|
4152
|
-
const activeRectangleCenter = [activeRectangle.x + activeRectangle.width / 2, activeRectangle.y + activeRectangle.height / 2];
|
|
4153
|
-
const alignRectangleCenter = [alignRectangle.x + alignRectangle.width / 2, alignRectangle.y + alignRectangle.height / 2];
|
|
4154
|
-
const centerXDistance = activeRectangleCenter[0] - alignRectangleCenter[0];
|
|
4155
|
-
const centerYDistance = activeRectangleCenter[1] - alignRectangleCenter[1];
|
|
4156
|
-
const leftToLeft = activeRectangle.x - alignRectangle.x;
|
|
4157
|
-
const leftToRight = activeRectangle.x - (alignRectangle.x + alignRectangle.width);
|
|
4158
|
-
const rightToRight = activeRectangle.x + activeRectangle.width - (alignRectangle.x + alignRectangle.width);
|
|
4159
|
-
const rightToLeft = activeRectangle.x + activeRectangle.width - alignRectangle.x;
|
|
4160
|
-
const topToTop = activeRectangle.y - alignRectangle.y;
|
|
4161
|
-
const topToBottom = activeRectangle.y - (alignRectangle.y + alignRectangle.height);
|
|
4162
|
-
const bottomToTop = activeRectangle.y + activeRectangle.height - alignRectangle.y;
|
|
4163
|
-
const bottomToBottom = activeRectangle.y + activeRectangle.height - (alignRectangle.y + alignRectangle.height);
|
|
4164
|
-
const xDistances = [leftToLeft, leftToRight, rightToRight, rightToLeft, centerXDistance];
|
|
4165
|
-
const yDistances = [topToTop, topToBottom, bottomToBottom, bottomToTop, centerYDistance];
|
|
4166
|
-
const xDistancesAbs = xDistances.map(distance => Math.abs(distance));
|
|
4167
|
-
const yDistancesAbs = yDistances.map(distance => Math.abs(distance));
|
|
4168
|
-
const indexX = xDistancesAbs.indexOf(Math.min(...xDistancesAbs));
|
|
4169
|
-
const indexY = yDistancesAbs.indexOf(Math.min(...yDistancesAbs));
|
|
4170
|
-
return {
|
|
4171
|
-
absXDistance: xDistancesAbs[indexX],
|
|
4172
|
-
xDistance: xDistances[indexX],
|
|
4173
|
-
absYDistance: yDistancesAbs[indexY],
|
|
4174
|
-
yDistance: yDistances[indexY],
|
|
4175
|
-
indexX,
|
|
4176
|
-
indexY
|
|
4177
|
-
};
|
|
4178
5042
|
}
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
let beforeIndex = undefined;
|
|
4196
|
-
let afterIndex = undefined;
|
|
4197
|
-
for (let i = 0; i < rectangles.length; i++) {
|
|
4198
|
-
for (let j = i + 1; j < rectangles.length; j++) {
|
|
4199
|
-
const before = rectangles[i];
|
|
4200
|
-
const after = rectangles[j];
|
|
4201
|
-
const distance = after[axis] - (before[axis] + before[side]);
|
|
4202
|
-
let dif = Infinity;
|
|
4203
|
-
if (refArray[i]?.after) {
|
|
4204
|
-
refArray[i].after.push({ distance, index: j });
|
|
4205
|
-
}
|
|
4206
|
-
else {
|
|
4207
|
-
refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
|
|
4208
|
-
}
|
|
4209
|
-
if (refArray[j]?.before) {
|
|
4210
|
-
refArray[j].before.push({ distance, index: i });
|
|
4211
|
-
}
|
|
4212
|
-
else {
|
|
4213
|
-
refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
|
|
4214
|
-
}
|
|
4215
|
-
//middle
|
|
4216
|
-
let _center = (before[axis] + before[side] + after[axis]) / 2;
|
|
4217
|
-
dif = Math.abs(activeRectangleCenter - _center);
|
|
4218
|
-
if (dif < ALIGN_TOLERANCE) {
|
|
4219
|
-
distributeDistance = (after[axis] - (before[axis] + before[side]) - this.activeRectangle[side]) / 2;
|
|
4220
|
-
delta = activeRectangleCenter - _center;
|
|
4221
|
-
beforeIndex = i;
|
|
4222
|
-
afterIndex = j;
|
|
4223
|
-
}
|
|
4224
|
-
//after
|
|
4225
|
-
const distanceRight = after[axis] - (before[axis] + before[side]);
|
|
4226
|
-
_center = after[axis] + after[side] + distanceRight + this.activeRectangle[side] / 2;
|
|
4227
|
-
dif = Math.abs(activeRectangleCenter - _center);
|
|
4228
|
-
if (!distributeDistance && dif < ALIGN_TOLERANCE) {
|
|
4229
|
-
distributeDistance = distanceRight;
|
|
4230
|
-
beforeIndex = j;
|
|
4231
|
-
delta = activeRectangleCenter - _center;
|
|
4232
|
-
}
|
|
4233
|
-
//before
|
|
4234
|
-
const distanceBefore = after[axis] - (before[axis] + before[side]);
|
|
4235
|
-
_center = before[axis] - distanceBefore - this.activeRectangle[side] / 2;
|
|
4236
|
-
dif = Math.abs(activeRectangleCenter - _center);
|
|
4237
|
-
if (!distributeDistance && dif < ALIGN_TOLERANCE) {
|
|
4238
|
-
distributeDistance = distanceBefore;
|
|
4239
|
-
afterIndex = i;
|
|
4240
|
-
delta = activeRectangleCenter - _center;
|
|
4241
|
-
}
|
|
4242
|
-
}
|
|
4243
|
-
}
|
|
4244
|
-
const activeIndex = rectangles.indexOf(this.activeRectangle);
|
|
4245
|
-
let beforeIndexes = [];
|
|
4246
|
-
let afterIndexes = [];
|
|
4247
|
-
if (beforeIndex !== undefined) {
|
|
4248
|
-
beforeIndexes.push(beforeIndex);
|
|
4249
|
-
findRectangle(distributeDistance, refArray[beforeIndex], 'before', beforeIndexes);
|
|
4250
|
-
}
|
|
4251
|
-
if (afterIndex !== undefined) {
|
|
4252
|
-
afterIndexes.push(afterIndex);
|
|
4253
|
-
findRectangle(distributeDistance, refArray[afterIndex], 'after', afterIndexes);
|
|
4254
|
-
}
|
|
4255
|
-
if (beforeIndexes.length || afterIndexes.length) {
|
|
4256
|
-
const indexArr = [...beforeIndexes.reverse(), activeIndex, ...afterIndexes];
|
|
4257
|
-
this.activeRectangle[axis] -= delta;
|
|
4258
|
-
for (let i = 1; i < indexArr.length; i++) {
|
|
4259
|
-
distributeLines.push(getLinePoints(rectangles[indexArr[i - 1]], rectangles[indexArr[i]]));
|
|
4260
|
-
}
|
|
5043
|
+
const activeIndex = rectangles.indexOf(activeRectangle);
|
|
5044
|
+
let beforeIndexes = [];
|
|
5045
|
+
let afterIndexes = [];
|
|
5046
|
+
if (beforeIndex !== undefined) {
|
|
5047
|
+
beforeIndexes.push(beforeIndex);
|
|
5048
|
+
findRectangle(gapDistance, refArray[beforeIndex], 'before', beforeIndexes);
|
|
5049
|
+
}
|
|
5050
|
+
if (afterIndex !== undefined) {
|
|
5051
|
+
afterIndexes.push(afterIndex);
|
|
5052
|
+
findRectangle(gapDistance, refArray[afterIndex], 'after', afterIndexes);
|
|
5053
|
+
}
|
|
5054
|
+
if (beforeIndexes.length || afterIndexes.length) {
|
|
5055
|
+
const indexArr = [...beforeIndexes.reverse(), activeIndex, ...afterIndexes];
|
|
5056
|
+
activeRectangle[axis] += delta;
|
|
5057
|
+
for (let i = 1; i < indexArr.length; i++) {
|
|
5058
|
+
lines.push(getLinePoints(rectangles[indexArr[i - 1]], rectangles[indexArr[i]]));
|
|
4261
5059
|
}
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
5060
|
+
}
|
|
5061
|
+
function findRectangle(distance, ref, direction, rectangleIndexes) {
|
|
5062
|
+
const arr = ref[direction];
|
|
5063
|
+
const index = refArray.indexOf(ref);
|
|
5064
|
+
if ((index === 0 && direction === 'before') || (index === refArray.length - 1 && direction === 'after'))
|
|
5065
|
+
return;
|
|
5066
|
+
for (let i = 0; i < arr.length; i++) {
|
|
5067
|
+
if (Math.abs(arr[i].distance - distance) < 0.1) {
|
|
5068
|
+
rectangleIndexes.push(arr[i].index);
|
|
5069
|
+
findRectangle(distance, refArray[arr[i].index], direction, rectangleIndexes);
|
|
4266
5070
|
return;
|
|
4267
|
-
for (let i = 0; i < arr.length; i++) {
|
|
4268
|
-
if (Math.abs(arr[i].distance - distance) < 0.1) {
|
|
4269
|
-
rectangleIndexes.push(arr[i].index);
|
|
4270
|
-
findRectangle(distance, refArray[arr[i].index], direction, rectangleIndexes);
|
|
4271
|
-
return;
|
|
4272
|
-
}
|
|
4273
5071
|
}
|
|
4274
5072
|
}
|
|
4275
|
-
function getLinePoints(beforeRectangle, afterRectangle) {
|
|
4276
|
-
const oppositeAxis = axis === 'x' ? 'y' : 'x';
|
|
4277
|
-
const oppositeSide = side === 'width' ? 'height' : 'width';
|
|
4278
|
-
const align = [
|
|
4279
|
-
beforeRectangle[oppositeAxis],
|
|
4280
|
-
beforeRectangle[oppositeAxis] + beforeRectangle[oppositeSide],
|
|
4281
|
-
afterRectangle[oppositeAxis],
|
|
4282
|
-
afterRectangle[oppositeAxis] + afterRectangle[oppositeSide]
|
|
4283
|
-
];
|
|
4284
|
-
const sortArr = align.sort((a, b) => a - b);
|
|
4285
|
-
const average = (sortArr[1] + sortArr[2]) / 2;
|
|
4286
|
-
const offset = 3;
|
|
4287
|
-
return isHorizontal
|
|
4288
|
-
? [
|
|
4289
|
-
[beforeRectangle.x + beforeRectangle.width + offset, average],
|
|
4290
|
-
[afterRectangle.x - offset, average]
|
|
4291
|
-
]
|
|
4292
|
-
: [
|
|
4293
|
-
[average, beforeRectangle.y + beforeRectangle.height + offset],
|
|
4294
|
-
[average, afterRectangle.y - offset]
|
|
4295
|
-
];
|
|
4296
|
-
}
|
|
4297
|
-
return { delta, distributeLines };
|
|
4298
|
-
}
|
|
4299
|
-
drawAlignLines(lines, g) {
|
|
4300
|
-
lines.forEach(points => {
|
|
4301
|
-
if (!points.length)
|
|
4302
|
-
return;
|
|
4303
|
-
const xAlign = PlaitBoard.getRoughSVG(this.board).line(points[0], points[1], points[2], points[3], {
|
|
4304
|
-
stroke: SELECTION_BORDER_COLOR,
|
|
4305
|
-
strokeWidth: 1,
|
|
4306
|
-
strokeLineDash: [4, 4]
|
|
4307
|
-
});
|
|
4308
|
-
g.appendChild(xAlign);
|
|
4309
|
-
});
|
|
4310
5073
|
}
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
5074
|
+
function getLinePoints(beforeRectangle, afterRectangle) {
|
|
5075
|
+
const oppositeAxis = axis === 'x' ? 'y' : 'x';
|
|
5076
|
+
const oppositeSide = side === 'width' ? 'height' : 'width';
|
|
5077
|
+
const snap = [
|
|
5078
|
+
beforeRectangle[oppositeAxis],
|
|
5079
|
+
beforeRectangle[oppositeAxis] + beforeRectangle[oppositeSide],
|
|
5080
|
+
afterRectangle[oppositeAxis],
|
|
5081
|
+
afterRectangle[oppositeAxis] + afterRectangle[oppositeSide]
|
|
5082
|
+
];
|
|
5083
|
+
const sortArr = snap.sort((a, b) => a - b);
|
|
5084
|
+
const average = (sortArr[1] + sortArr[2]) / 2;
|
|
5085
|
+
const offset = 3;
|
|
5086
|
+
return isHorizontal
|
|
5087
|
+
? [
|
|
5088
|
+
[beforeRectangle.x + beforeRectangle.width + offset, average],
|
|
5089
|
+
[afterRectangle.x - offset, average]
|
|
5090
|
+
]
|
|
5091
|
+
: [
|
|
5092
|
+
[average, beforeRectangle.y + beforeRectangle.height + offset],
|
|
5093
|
+
[average, afterRectangle.y - offset]
|
|
5094
|
+
];
|
|
4330
5095
|
}
|
|
5096
|
+
return { delta, lines };
|
|
4331
5097
|
}
|
|
4332
5098
|
function isHorizontalCross(rectangle, other) {
|
|
4333
5099
|
return !(rectangle.y + rectangle.height < other.y || rectangle.y > other.y + other.height);
|
|
@@ -4335,17 +5101,6 @@ function isHorizontalCross(rectangle, other) {
|
|
|
4335
5101
|
function isVerticalCross(rectangle, other) {
|
|
4336
5102
|
return !(rectangle.x + rectangle.width < other.x || rectangle.x > other.x + other.width);
|
|
4337
5103
|
}
|
|
4338
|
-
function getBarPoint(point, isHorizontal) {
|
|
4339
|
-
return isHorizontal
|
|
4340
|
-
? [
|
|
4341
|
-
[point[0], point[1] - 4],
|
|
4342
|
-
[point[0], point[1] + 4]
|
|
4343
|
-
]
|
|
4344
|
-
: [
|
|
4345
|
-
[point[0] - 4, point[1]],
|
|
4346
|
-
[point[0] + 4, point[1]]
|
|
4347
|
-
];
|
|
4348
|
-
}
|
|
4349
5104
|
|
|
4350
5105
|
function withMoving(board) {
|
|
4351
5106
|
const { pointerDown, pointerMove, globalPointerUp, globalPointerMove } = board;
|
|
@@ -4354,7 +5109,7 @@ function withMoving(board) {
|
|
|
4354
5109
|
let isPreventDefault = false;
|
|
4355
5110
|
let startPoint;
|
|
4356
5111
|
let activeElements = [];
|
|
4357
|
-
let
|
|
5112
|
+
let snapG = null;
|
|
4358
5113
|
let activeElementsRectangle = null;
|
|
4359
5114
|
let selectedTargetElements = null;
|
|
4360
5115
|
let hitTargetElement = undefined;
|
|
@@ -4379,7 +5134,8 @@ function withMoving(board) {
|
|
|
4379
5134
|
}
|
|
4380
5135
|
else if (hitTargetElement) {
|
|
4381
5136
|
startPoint = point;
|
|
4382
|
-
|
|
5137
|
+
const relatedElements = board.getRelatedFragment([], [hitTargetElement]);
|
|
5138
|
+
activeElements = [...getElementsInGroupByElement(board, hitTargetElement), ...relatedElements];
|
|
4383
5139
|
activeElementsRectangle = getRectangleByElements(board, activeElements, true);
|
|
4384
5140
|
preventTouchMove(board, event, true);
|
|
4385
5141
|
}
|
|
@@ -4402,7 +5158,7 @@ function withMoving(board) {
|
|
|
4402
5158
|
if (!isPreventDefault) {
|
|
4403
5159
|
isPreventDefault = true;
|
|
4404
5160
|
}
|
|
4405
|
-
|
|
5161
|
+
snapG?.remove();
|
|
4406
5162
|
const endPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
|
|
4407
5163
|
offsetX = endPoint[0] - startPoint[0];
|
|
4408
5164
|
offsetY = endPoint[1] - startPoint[1];
|
|
@@ -4423,13 +5179,13 @@ function withMoving(board) {
|
|
|
4423
5179
|
x: activeElementsRectangle.x + offsetX,
|
|
4424
5180
|
y: activeElementsRectangle.y + offsetY
|
|
4425
5181
|
};
|
|
4426
|
-
const
|
|
4427
|
-
const ref =
|
|
4428
|
-
offsetX
|
|
4429
|
-
offsetY
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
PlaitBoard.getElementActiveHost(board).append(
|
|
5182
|
+
const activeRectangle = getRectangleByAngle(newRectangle, getSelectionAngle(activeElements)) || newRectangle;
|
|
5183
|
+
const ref = getSnapMovingRef(board, activeRectangle, activeElements);
|
|
5184
|
+
offsetX += ref.deltaX;
|
|
5185
|
+
offsetY += ref.deltaY;
|
|
5186
|
+
snapG = ref.snapG;
|
|
5187
|
+
snapG.classList.add(ACTIVE_MOVING_CLASS_NAME);
|
|
5188
|
+
PlaitBoard.getElementActiveHost(board).append(snapG);
|
|
4433
5189
|
handleTouchTarget(board);
|
|
4434
5190
|
const currentElements = updatePoints(board, activeElements, offsetX, offsetY);
|
|
4435
5191
|
PlaitBoard.getBoardContainer(board).classList.add('element-moving');
|
|
@@ -4464,7 +5220,7 @@ function withMoving(board) {
|
|
|
4464
5220
|
globalPointerUp(event);
|
|
4465
5221
|
};
|
|
4466
5222
|
function cancelMove(board) {
|
|
4467
|
-
|
|
5223
|
+
snapG?.remove();
|
|
4468
5224
|
startPoint = null;
|
|
4469
5225
|
activeElementsRectangle = null;
|
|
4470
5226
|
offsetX = 0;
|
|
@@ -4648,12 +5404,41 @@ const withHotkey = (board) => {
|
|
|
4648
5404
|
Transforms.addSelectionWithTemporaryElements(board, elements);
|
|
4649
5405
|
return;
|
|
4650
5406
|
}
|
|
5407
|
+
if (!PlaitBoard.isReadonly(board)) {
|
|
5408
|
+
if (isKeyHotkey('mod+]', event)) {
|
|
5409
|
+
event.preventDefault();
|
|
5410
|
+
Transforms.moveUp(board);
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5413
|
+
if (isKeyHotkey('mod+[', event)) {
|
|
5414
|
+
event.preventDefault();
|
|
5415
|
+
Transforms.moveDown(board);
|
|
5416
|
+
return;
|
|
5417
|
+
}
|
|
5418
|
+
if (isKeyHotkey('mod+option+‘', event)) {
|
|
5419
|
+
event.preventDefault();
|
|
5420
|
+
Transforms.moveToTop(board);
|
|
5421
|
+
return;
|
|
5422
|
+
}
|
|
5423
|
+
if (isKeyHotkey('mod+option+“', event)) {
|
|
5424
|
+
event.preventDefault();
|
|
5425
|
+
Transforms.moveToBottom(board);
|
|
5426
|
+
return;
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
4651
5429
|
const selectedElements = getSelectedElements(board);
|
|
5430
|
+
if (!PlaitBoard.isReadonly(board) && selectedElements.length > 0) {
|
|
5431
|
+
if (isKeyHotkey('mod+d', event)) {
|
|
5432
|
+
event.preventDefault();
|
|
5433
|
+
duplicateElements(board, selectedElements);
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
4652
5437
|
if (!PlaitBoard.isReadonly(board) &&
|
|
4653
5438
|
selectedElements.length > 0 &&
|
|
4654
5439
|
(hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
|
|
4655
5440
|
event.preventDefault();
|
|
4656
|
-
|
|
5441
|
+
deleteFragment(board);
|
|
4657
5442
|
}
|
|
4658
5443
|
keyDown(event);
|
|
4659
5444
|
};
|
|
@@ -4708,162 +5493,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
|
|
|
4708
5493
|
type: Injectable
|
|
4709
5494
|
}] });
|
|
4710
5495
|
|
|
4711
|
-
class PlaitElementComponent {
|
|
4712
|
-
constructor(renderer2, viewContainerRef) {
|
|
4713
|
-
this.renderer2 = renderer2;
|
|
4714
|
-
this.viewContainerRef = viewContainerRef;
|
|
4715
|
-
this.initialized = false;
|
|
4716
|
-
}
|
|
4717
|
-
ngOnInit() {
|
|
4718
|
-
this.initialize();
|
|
4719
|
-
this.drawElement();
|
|
4720
|
-
}
|
|
4721
|
-
initialize() {
|
|
4722
|
-
NODE_TO_INDEX.set(this.element, this.index);
|
|
4723
|
-
NODE_TO_PARENT.set(this.element, this.parent);
|
|
4724
|
-
this.initialized = true;
|
|
4725
|
-
}
|
|
4726
|
-
drawElement() {
|
|
4727
|
-
const context = this.getContext();
|
|
4728
|
-
const result = this.board.drawElement(context);
|
|
4729
|
-
if (Array.isArray(result)) {
|
|
4730
|
-
}
|
|
4731
|
-
else {
|
|
4732
|
-
const componentRef = this.viewContainerRef.createComponent(result);
|
|
4733
|
-
const instance = componentRef.instance;
|
|
4734
|
-
instance.context = context;
|
|
4735
|
-
this.insertG(instance.rootG ? instance.rootG : instance.g);
|
|
4736
|
-
this.instance = instance;
|
|
4737
|
-
}
|
|
4738
|
-
}
|
|
4739
|
-
insertG(g) {
|
|
4740
|
-
if (PlaitBoard.isBoard(this.parent)) {
|
|
4741
|
-
this.parentG.append(g);
|
|
4742
|
-
}
|
|
4743
|
-
else {
|
|
4744
|
-
let siblingG = PlaitElement.getComponent(this.parent).g;
|
|
4745
|
-
if (this.index > 0) {
|
|
4746
|
-
const brotherElement = this.parent.children[this.index - 1];
|
|
4747
|
-
const lastElement = PlaitNode.last(this.board, PlaitBoard.findPath(this.board, brotherElement));
|
|
4748
|
-
let component = PlaitElement.getComponent(lastElement) || PlaitElement.getComponent(brotherElement);
|
|
4749
|
-
siblingG = component.g;
|
|
4750
|
-
}
|
|
4751
|
-
this.parentG.insertBefore(g, siblingG);
|
|
4752
|
-
}
|
|
4753
|
-
}
|
|
4754
|
-
ngOnChanges(simpleChanges) {
|
|
4755
|
-
if (this.initialized) {
|
|
4756
|
-
NODE_TO_INDEX.set(this.element, this.index);
|
|
4757
|
-
NODE_TO_PARENT.set(this.element, this.parent);
|
|
4758
|
-
const elementChanged = simpleChanges['element'];
|
|
4759
|
-
const context = this.getContext();
|
|
4760
|
-
if (elementChanged && isSelectedElement(this.board, elementChanged.previousValue)) {
|
|
4761
|
-
context.selected = true;
|
|
4762
|
-
removeSelectedElement(this.board, elementChanged.previousValue);
|
|
4763
|
-
addSelectedElement(this.board, this.element);
|
|
4764
|
-
}
|
|
4765
|
-
if (this.instance) {
|
|
4766
|
-
this.instance.context = context;
|
|
4767
|
-
}
|
|
4768
|
-
}
|
|
4769
|
-
}
|
|
4770
|
-
getContext() {
|
|
4771
|
-
const isSelected = isSelectedElement(this.board, this.element);
|
|
4772
|
-
const context = {
|
|
4773
|
-
element: this.element,
|
|
4774
|
-
parent: this.parent,
|
|
4775
|
-
board: this.board,
|
|
4776
|
-
selected: isSelected,
|
|
4777
|
-
effect: this.effect
|
|
4778
|
-
};
|
|
4779
|
-
return context;
|
|
4780
|
-
}
|
|
4781
|
-
ngOnDestroy() {
|
|
4782
|
-
this.board.destroyElement(this.getContext());
|
|
4783
|
-
}
|
|
4784
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitElementComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4785
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: PlaitElementComponent, isStandalone: true, selector: "plait-element", inputs: { index: "index", element: "element", parent: "parent", board: "board", effect: "effect", parentG: "parentG" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4786
|
-
}
|
|
4787
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitElementComponent, decorators: [{
|
|
4788
|
-
type: Component,
|
|
4789
|
-
args: [{
|
|
4790
|
-
selector: 'plait-element',
|
|
4791
|
-
template: '',
|
|
4792
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4793
|
-
standalone: true
|
|
4794
|
-
}]
|
|
4795
|
-
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }], propDecorators: { index: [{
|
|
4796
|
-
type: Input
|
|
4797
|
-
}], element: [{
|
|
4798
|
-
type: Input
|
|
4799
|
-
}], parent: [{
|
|
4800
|
-
type: Input
|
|
4801
|
-
}], board: [{
|
|
4802
|
-
type: Input
|
|
4803
|
-
}], effect: [{
|
|
4804
|
-
type: Input
|
|
4805
|
-
}], parentG: [{
|
|
4806
|
-
type: Input
|
|
4807
|
-
}] } });
|
|
4808
|
-
|
|
4809
|
-
class PlaitChildrenElementComponent {
|
|
4810
|
-
constructor() {
|
|
4811
|
-
this.trackBy = (index, element) => {
|
|
4812
|
-
return element.id;
|
|
4813
|
-
};
|
|
4814
|
-
}
|
|
4815
|
-
ngOnInit() {
|
|
4816
|
-
if (!this.parent) {
|
|
4817
|
-
this.parent = this.board;
|
|
4818
|
-
}
|
|
4819
|
-
if (!this.parentG) {
|
|
4820
|
-
this.parentG = PlaitBoard.getElementHost(this.board);
|
|
4821
|
-
}
|
|
4822
|
-
}
|
|
4823
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4824
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: PlaitChildrenElementComponent, isStandalone: true, selector: "plait-children", inputs: { board: "board", parent: "parent", effect: "effect", parentG: "parentG" }, ngImport: i0, template: `
|
|
4825
|
-
<plait-element
|
|
4826
|
-
*ngFor="let item of parent.children; let index = index; trackBy: trackBy"
|
|
4827
|
-
[index]="index"
|
|
4828
|
-
[element]="item"
|
|
4829
|
-
[parent]="parent"
|
|
4830
|
-
[board]="board"
|
|
4831
|
-
[effect]="effect"
|
|
4832
|
-
[parentG]="parentG"
|
|
4833
|
-
></plait-element>
|
|
4834
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: PlaitElementComponent, selector: "plait-element", inputs: ["index", "element", "parent", "board", "effect", "parentG"] }] }); }
|
|
4835
|
-
}
|
|
4836
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, decorators: [{
|
|
4837
|
-
type: Component,
|
|
4838
|
-
args: [{
|
|
4839
|
-
selector: 'plait-children',
|
|
4840
|
-
template: `
|
|
4841
|
-
<plait-element
|
|
4842
|
-
*ngFor="let item of parent.children; let index = index; trackBy: trackBy"
|
|
4843
|
-
[index]="index"
|
|
4844
|
-
[element]="item"
|
|
4845
|
-
[parent]="parent"
|
|
4846
|
-
[board]="board"
|
|
4847
|
-
[effect]="effect"
|
|
4848
|
-
[parentG]="parentG"
|
|
4849
|
-
></plait-element>
|
|
4850
|
-
`,
|
|
4851
|
-
standalone: true,
|
|
4852
|
-
imports: [NgFor, PlaitElementComponent]
|
|
4853
|
-
}]
|
|
4854
|
-
}], ctorParameters: () => [], propDecorators: { board: [{
|
|
4855
|
-
type: Input
|
|
4856
|
-
}], parent: [{
|
|
4857
|
-
type: Input
|
|
4858
|
-
}], effect: [{
|
|
4859
|
-
type: Input
|
|
4860
|
-
}], parentG: [{
|
|
4861
|
-
type: Input
|
|
4862
|
-
}] } });
|
|
4863
|
-
|
|
4864
5496
|
function withRelatedFragment(board) {
|
|
4865
|
-
const {
|
|
4866
|
-
board.
|
|
5497
|
+
const { buildFragment } = board;
|
|
5498
|
+
board.buildFragment = (clipboardContext, rectangle, type) => {
|
|
4867
5499
|
const relatedFragment = board.getRelatedFragment([]);
|
|
4868
5500
|
if (!clipboardContext) {
|
|
4869
5501
|
clipboardContext = createClipboardContext(WritableClipboardType.elements, relatedFragment, '');
|
|
@@ -4872,10 +5504,10 @@ function withRelatedFragment(board) {
|
|
|
4872
5504
|
clipboardContext = addClipboardContext(clipboardContext, {
|
|
4873
5505
|
text: '',
|
|
4874
5506
|
type: WritableClipboardType.elements,
|
|
4875
|
-
|
|
5507
|
+
elements: relatedFragment
|
|
4876
5508
|
});
|
|
4877
5509
|
}
|
|
4878
|
-
|
|
5510
|
+
return buildFragment(clipboardContext, rectangle, type);
|
|
4879
5511
|
};
|
|
4880
5512
|
return board;
|
|
4881
5513
|
}
|
|
@@ -4920,7 +5552,6 @@ class PlaitBoardComponent {
|
|
|
4920
5552
|
this.elementRef = elementRef;
|
|
4921
5553
|
this.ngZone = ngZone;
|
|
4922
5554
|
this.hasInitialized = false;
|
|
4923
|
-
this.effect = {};
|
|
4924
5555
|
this.destroy$ = new Subject();
|
|
4925
5556
|
this.plaitValue = [];
|
|
4926
5557
|
this.plaitPlugins = [];
|
|
@@ -4953,6 +5584,7 @@ class PlaitBoardComponent {
|
|
|
4953
5584
|
BOARD_TO_COMPONENT.set(this.board, this);
|
|
4954
5585
|
BOARD_TO_ROUGH_SVG.set(this.board, roughSVG);
|
|
4955
5586
|
BOARD_TO_HOST.set(this.board, this.host);
|
|
5587
|
+
IS_BOARD_ALIVE.set(this.board, true);
|
|
4956
5588
|
BOARD_TO_ELEMENT_HOST.set(this.board, {
|
|
4957
5589
|
host: elementHost,
|
|
4958
5590
|
upperHost: elementUpperHost,
|
|
@@ -4962,7 +5594,7 @@ class PlaitBoardComponent {
|
|
|
4962
5594
|
});
|
|
4963
5595
|
BOARD_TO_ON_CHANGE.set(this.board, () => {
|
|
4964
5596
|
this.ngZone.run(() => {
|
|
4965
|
-
this.
|
|
5597
|
+
this.updateListRender();
|
|
4966
5598
|
});
|
|
4967
5599
|
});
|
|
4968
5600
|
BOARD_TO_AFTER_CHANGE.set(this.board, () => {
|
|
@@ -4978,15 +5610,12 @@ class PlaitBoardComponent {
|
|
|
4978
5610
|
this.plaitChange.emit(changeEvent);
|
|
4979
5611
|
});
|
|
4980
5612
|
});
|
|
5613
|
+
this.initializeListRender();
|
|
4981
5614
|
this.hasInitialized = true;
|
|
4982
5615
|
}
|
|
4983
5616
|
ngAfterContentInit() {
|
|
4984
5617
|
this.initializeIslands();
|
|
4985
5618
|
}
|
|
4986
|
-
update() {
|
|
4987
|
-
this.effect = {};
|
|
4988
|
-
this.cdr.detectChanges();
|
|
4989
|
-
}
|
|
4990
5619
|
ngOnChanges(changes) {
|
|
4991
5620
|
if (this.hasInitialized) {
|
|
4992
5621
|
const valueChange = changes['plaitValue'];
|
|
@@ -5104,10 +5733,8 @@ class PlaitBoardComponent {
|
|
|
5104
5733
|
fromEvent(document, 'copy')
|
|
5105
5734
|
.pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board)))
|
|
5106
5735
|
.subscribe((event) => {
|
|
5107
|
-
const selectedElements = getSelectedElements(this.board);
|
|
5108
5736
|
event.preventDefault();
|
|
5109
|
-
|
|
5110
|
-
this.board.setFragment(event.clipboardData, null, rectangle, 'copy');
|
|
5737
|
+
setFragment(this.board, 'copy', event.clipboardData);
|
|
5111
5738
|
});
|
|
5112
5739
|
fromEvent(document, 'paste')
|
|
5113
5740
|
.pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
|
|
@@ -5116,19 +5743,31 @@ class PlaitBoardComponent {
|
|
|
5116
5743
|
if (mousePoint) {
|
|
5117
5744
|
const targetPoint = toViewBoxPoint(this.board, toHostPoint(this.board, mousePoint[0], mousePoint[1]));
|
|
5118
5745
|
const clipboardData = await getClipboardData(clipboardEvent.clipboardData);
|
|
5119
|
-
this.board.insertFragment(
|
|
5746
|
+
this.board.insertFragment(clipboardData, targetPoint);
|
|
5120
5747
|
}
|
|
5121
5748
|
});
|
|
5122
5749
|
fromEvent(document, 'cut')
|
|
5123
5750
|
.pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
|
|
5124
5751
|
.subscribe((event) => {
|
|
5125
|
-
const selectedElements = getSelectedElements(this.board);
|
|
5126
5752
|
event.preventDefault();
|
|
5127
|
-
|
|
5128
|
-
this.board
|
|
5129
|
-
this.board.deleteFragment(event.clipboardData);
|
|
5753
|
+
setFragment(this.board, 'cut', event.clipboardData);
|
|
5754
|
+
deleteFragment(this.board);
|
|
5130
5755
|
});
|
|
5131
5756
|
}
|
|
5757
|
+
initializeListRender() {
|
|
5758
|
+
this.listRender = new ListRender(this.board, this.viewContainerRef);
|
|
5759
|
+
this.listRender.initialize(this.board.children, this.initializeChildrenContext());
|
|
5760
|
+
}
|
|
5761
|
+
updateListRender() {
|
|
5762
|
+
this.listRender.update(this.board.children, this.initializeChildrenContext());
|
|
5763
|
+
}
|
|
5764
|
+
initializeChildrenContext() {
|
|
5765
|
+
return {
|
|
5766
|
+
board: this.board,
|
|
5767
|
+
parent: this.board,
|
|
5768
|
+
parentG: PlaitBoard.getElementHost(this.board)
|
|
5769
|
+
};
|
|
5770
|
+
}
|
|
5132
5771
|
viewportScrollListener() {
|
|
5133
5772
|
fromEvent(this.viewportContainer.nativeElement, 'scroll')
|
|
5134
5773
|
.pipe(takeUntil(this.destroy$), filter(() => {
|
|
@@ -5208,6 +5847,7 @@ class PlaitBoardComponent {
|
|
|
5208
5847
|
BOARD_TO_ROUGH_SVG.delete(this.board);
|
|
5209
5848
|
BOARD_TO_HOST.delete(this.board);
|
|
5210
5849
|
BOARD_TO_ELEMENT_HOST.delete(this.board);
|
|
5850
|
+
IS_BOARD_ALIVE.set(this.board, false);
|
|
5211
5851
|
BOARD_TO_ON_CHANGE.delete(this.board);
|
|
5212
5852
|
BOARD_TO_AFTER_CHANGE.set(this.board, () => { });
|
|
5213
5853
|
}
|
|
@@ -5225,10 +5865,9 @@ class PlaitBoardComponent {
|
|
|
5225
5865
|
<g class="element-upper-host"></g>
|
|
5226
5866
|
<g class="element-active-host"></g>
|
|
5227
5867
|
</svg>
|
|
5228
|
-
<plait-children [board]="board" [effect]="effect"></plait-children>
|
|
5229
5868
|
</div>
|
|
5230
5869
|
<ng-content></ng-content>
|
|
5231
|
-
`, isInline: true,
|
|
5870
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5232
5871
|
}
|
|
5233
5872
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitBoardComponent, decorators: [{
|
|
5234
5873
|
type: Component,
|
|
@@ -5241,14 +5880,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
|
|
|
5241
5880
|
<g class="element-upper-host"></g>
|
|
5242
5881
|
<g class="element-active-host"></g>
|
|
5243
5882
|
</svg>
|
|
5244
|
-
<plait-children [board]="board" [effect]="effect"></plait-children>
|
|
5245
5883
|
</div>
|
|
5246
5884
|
<ng-content></ng-content>
|
|
5247
5885
|
`,
|
|
5248
5886
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5249
5887
|
providers: [PlaitContextService],
|
|
5250
|
-
standalone: true
|
|
5251
|
-
imports: [PlaitChildrenElementComponent]
|
|
5888
|
+
standalone: true
|
|
5252
5889
|
}]
|
|
5253
5890
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ViewContainerRef }, { type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { plaitValue: [{
|
|
5254
5891
|
type: Input
|
|
@@ -5474,12 +6111,25 @@ class DebugGenerator {
|
|
|
5474
6111
|
return;
|
|
5475
6112
|
}
|
|
5476
6113
|
const polygonG = PlaitBoard.getRoughSVG(board).polygon(points, options || { stroke: 'red' });
|
|
6114
|
+
polygonG.classList.add(this.debugKey);
|
|
5477
6115
|
PlaitBoard.getElementActiveHost(board).append(polygonG);
|
|
5478
6116
|
const gArray = getTemporaryGArray(this.debugKey);
|
|
5479
6117
|
gArray.push(polygonG);
|
|
5480
6118
|
setTemporaryGArray(this.debugKey, gArray);
|
|
5481
6119
|
return polygonG;
|
|
5482
6120
|
}
|
|
6121
|
+
drawLine(board, points, options) {
|
|
6122
|
+
if (!isDebug(this.debugKey)) {
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
6125
|
+
const lineG = PlaitBoard.getRoughSVG(board).linearPath(points, options || { stroke: 'red' });
|
|
6126
|
+
lineG.classList.add(this.debugKey);
|
|
6127
|
+
PlaitBoard.getElementActiveHost(board).append(lineG);
|
|
6128
|
+
const gArray = getTemporaryGArray(this.debugKey);
|
|
6129
|
+
gArray.push(lineG);
|
|
6130
|
+
setTemporaryGArray(this.debugKey, gArray);
|
|
6131
|
+
return lineG;
|
|
6132
|
+
}
|
|
5483
6133
|
drawRectangle(board, data, options) {
|
|
5484
6134
|
if (!isDebug(this.debugKey)) {
|
|
5485
6135
|
return;
|
|
@@ -5492,6 +6142,7 @@ class DebugGenerator {
|
|
|
5492
6142
|
rectangle = data;
|
|
5493
6143
|
}
|
|
5494
6144
|
const rectangleG = PlaitBoard.getRoughSVG(board).rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options || { stroke: 'red' });
|
|
6145
|
+
rectangleG.classList.add(this.debugKey);
|
|
5495
6146
|
PlaitBoard.getElementActiveHost(board).append(rectangleG);
|
|
5496
6147
|
const gArray = getTemporaryGArray(this.debugKey);
|
|
5497
6148
|
gArray.push(rectangleG);
|
|
@@ -5505,6 +6156,7 @@ class DebugGenerator {
|
|
|
5505
6156
|
const result = [];
|
|
5506
6157
|
points.forEach((p, i) => {
|
|
5507
6158
|
const circle = PlaitBoard.getRoughSVG(board).circle(p[0], p[1], isCumulativeDiameter ? diameter * (i + 1) : diameter, Object.assign({}, { stroke: 'red', fill: 'red', fillStyle: 'solid' }, options || {}));
|
|
6159
|
+
circle.classList.add(this.debugKey);
|
|
5508
6160
|
PlaitBoard.getElementActiveHost(board).append(circle);
|
|
5509
6161
|
const gArray = getTemporaryGArray(this.debugKey);
|
|
5510
6162
|
gArray.push(circle);
|
|
@@ -5530,5 +6182,5 @@ const isDebug = (key) => {
|
|
|
5530
6182
|
* Generated bundle index. Do not edit.
|
|
5531
6183
|
*/
|
|
5532
6184
|
|
|
5533
|
-
export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DebugGenerator, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G,
|
|
6185
|
+
export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DebugGenerator, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, H, HIT_DISTANCE_BUFFER, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_ALIVE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_CONTAINER_G, NODE_TO_G, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitContextService, PlaitElement, PlaitGroupElement, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RESIZE_CURSORS, RESIZE_HANDLE_CLASS_NAME, RIGHT_ARROW, ROTATE_HANDLE_CLASS_NAME, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SNAPPING_STROKE_WIDTH, SNAP_TOLERANCE, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addSelectedElement, approximately, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, cacheSelectedElementsWithGroup, cacheSelectedElementsWithGroupOnShift, calcNewViewBox, canAddGroup, canRemoveGroup, canSetZIndex, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createDebugGenerator, createFakeEvent, createForeignObject, createG, createGroup, createGroupRectangleG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createTestingBoard, createText, createTouchEvent, debounce, degreesToRadians, deleteFragment, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawDashedLines, drawEntireActiveRectangleG, drawLine, drawLinearPath, drawPointSnapLines, drawRectangle, drawRoundRectangle, drawSolidLines, duplicateElements, fakeNodeWeakMap, filterSelectedGroups, findElements, findIndex, findLastIndex, getAllElementsInGroup, getAllMoveOptions, getAngleBetweenPoints, getBarPoint, getBoardRectangle, getClipboardData, getClipboardFromHtml, getCrossingPointsBetweenEllipseAndSegment, getDataTransferClipboard, getDataTransferClipboardText, getEditingGroup, getElementById, getElementHostBBox, getElementsInGroup, getElementsInGroupByElement, getElementsIndices, getEllipseTangentSlope, getGroupByElement, getHighestGroup, getHighestIndexOfElement, getHighestSelectedElements, getHighestSelectedGroup, getHighestSelectedGroups, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMinPointDelta, getMovingElements, getNearestDelta, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getNearestPointRectangle, getOffsetAfterRotate, getOneMoveOptions, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByAngle, getRectangleByElements, getRectangleByGroup, getRotatedBoundingRectangle, getSelectedElements, getSelectedGroups, getSelectedIsolatedElements, getSelectedIsolatedElementsCanAddToGroup, getSelectedTargetElements, getSelectionAngle, getSnapRectangles, getTemporaryElements, getTemporaryRef, getTripleAxis, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hasSameAngle, hasSelectedElementsInSameGroup, hasValidAngle, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isAxisChangedByAngle, isContextmenu, isDOMElement, isDOMNode, isDebug, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isIndicesContinuous, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedAllElementsInGroup, isSelectedElement, isSelectedElementOrGroup, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, isSnapPoint, moveElementsToNewPath, moveElementsToNewPathAfterAddGroup, nonGroupInHighestSelectedElements, normalizeAngle, normalizePoint, preventTouchMove, radiansToDegrees, removeMovingElements, removeSelectedElement, rotate, rotateAntiPointsByElement, rotateElements, rotatePoints, rotatePointsByElement, rotatedDataPoints, scrollToRectangle, setAngleForG, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setFragment, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectedElementsWithGroup, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, sortElements, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, uniqueById, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
|
|
5534
6186
|
//# sourceMappingURL=plait-core.mjs.map
|