@plait/core 0.54.0 → 0.55.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/board/board.component.d.ts +8 -3
- package/constants/index.d.ts +2 -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/core/element/context.mjs +1 -1
- package/esm2022/core/element/plugin-element.mjs +79 -12
- package/esm2022/core/list-render.mjs +210 -0
- package/esm2022/interfaces/board.mjs +3 -3
- package/esm2022/interfaces/element.mjs +28 -2
- package/esm2022/interfaces/node.mjs +18 -1
- package/esm2022/interfaces/path.mjs +56 -57
- package/esm2022/plugins/create-board.mjs +10 -10
- package/esm2022/plugins/with-hotkey.mjs +32 -3
- package/esm2022/plugins/with-moving.mjs +12 -12
- package/esm2022/plugins/with-related-fragment.mjs +5 -5
- package/esm2022/public-api.mjs +2 -4
- package/esm2022/services/context.service.mjs +30 -0
- 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 +17 -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 -1
- package/esm2022/utils/fragment.mjs +22 -1
- package/esm2022/utils/group.mjs +33 -4
- package/esm2022/utils/helper.mjs +37 -1
- package/esm2022/utils/index.mjs +4 -1
- package/esm2022/utils/math.mjs +37 -1
- package/esm2022/utils/position.mjs +3 -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 +3 -1
- package/esm2022/utils/z-index.mjs +166 -0
- package/fesm2022/plait-core.mjs +1667 -1075
- package/fesm2022/plait-core.mjs.map +1 -1
- package/interfaces/board.d.ts +5 -5
- package/interfaces/element.d.ts +5 -0
- package/interfaces/node.d.ts +1 -0
- package/package.json +1 -1
- package/public-api.d.ts +1 -3
- package/services/{image-context.service.d.ts → context.service.d.ts} +3 -0
- 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 +2 -0
- 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/fragment.d.ts +3 -1
- package/utils/group.d.ts +3 -1
- package/utils/helper.d.ts +4 -1
- package/utils/index.d.ts +3 -0
- package/utils/math.d.ts +1 -0
- package/utils/position.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 +2 -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/services/image-context.service.mjs +0 -22
- package/esm2022/utils/moving-snap.mjs +0 -372
- package/utils/moving-snap.d.ts +0 -41
package/fesm2022/plait-core.mjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
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();
|
|
@@ -77,11 +78,11 @@ const Selection = {
|
|
|
77
78
|
}
|
|
78
79
|
};
|
|
79
80
|
|
|
80
|
-
const sortElements = (board, elements) => {
|
|
81
|
+
const sortElements = (board, elements, ascendingOrder = true) => {
|
|
81
82
|
return [...elements].sort((a, b) => {
|
|
82
83
|
const pathA = PlaitBoard.findPath(board, a);
|
|
83
84
|
const pathB = PlaitBoard.findPath(board, b);
|
|
84
|
-
return pathA[0] - pathB[0];
|
|
85
|
+
return ascendingOrder ? pathA[0] - pathB[0] : pathB[0] - pathA[0];
|
|
85
86
|
});
|
|
86
87
|
};
|
|
87
88
|
|
|
@@ -506,6 +507,8 @@ const SELECTION_RECTANGLE_CLASS_NAME = 'selection-rectangle';
|
|
|
506
507
|
|
|
507
508
|
const HOST_CLASS_NAME = 'plait-board-container';
|
|
508
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';
|
|
509
512
|
const SCROLL_BAR_WIDTH = 20;
|
|
510
513
|
const MAX_RADIUS = 16;
|
|
511
514
|
const POINTER_BUTTON = {
|
|
@@ -621,7 +624,216 @@ function hasOnContextChanged(value) {
|
|
|
621
624
|
return false;
|
|
622
625
|
}
|
|
623
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
|
+
// item might has been changed, so need to compare the id
|
|
685
|
+
if (record.item === this.children[0] || record.item.id === this.children[0]?.id) {
|
|
686
|
+
currentIndexForFirstElement = record.currentIndex;
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
diffResult.forEachOperation(record => {
|
|
690
|
+
// removed
|
|
691
|
+
if (record.currentIndex === null) {
|
|
692
|
+
const componentRef = this.componentRefs[record.previousIndex];
|
|
693
|
+
componentRef?.destroy();
|
|
694
|
+
}
|
|
695
|
+
// moved
|
|
696
|
+
if (record.previousIndex !== null && record.currentIndex !== null) {
|
|
697
|
+
mountOnItemMove(record.item, record.currentIndex, childrenContext, currentIndexForFirstElement);
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
this.componentRefs = newComponentRefs;
|
|
701
|
+
this.contexts = newContexts;
|
|
702
|
+
this.children = children;
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
const newContexts = [];
|
|
706
|
+
this.children.forEach((element, index) => {
|
|
707
|
+
NODE_TO_INDEX.set(element, index);
|
|
708
|
+
NODE_TO_PARENT.set(element, childrenContext.parent);
|
|
709
|
+
const previousContext = this.contexts[index];
|
|
710
|
+
const previousComponentRef = this.componentRefs[index];
|
|
711
|
+
const context = getContext(board, element, index, parent, previousContext);
|
|
712
|
+
previousComponentRef.instance.context = context;
|
|
713
|
+
newContexts.push(context);
|
|
714
|
+
});
|
|
715
|
+
this.contexts = newContexts;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
destroy() {
|
|
719
|
+
this.children.forEach((element, index) => {
|
|
720
|
+
if (this.componentRefs[index]) {
|
|
721
|
+
this.componentRefs[index].destroy();
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
this.componentRefs = [];
|
|
725
|
+
this.children = [];
|
|
726
|
+
this.contexts = [];
|
|
727
|
+
this.initialized = false;
|
|
728
|
+
this.differ = null;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const trackBy = (index, element) => {
|
|
732
|
+
return element.id;
|
|
733
|
+
};
|
|
734
|
+
const createPluginComponent = (componentType, context, viewContainerRef, childrenContext) => {
|
|
735
|
+
const componentRef = viewContainerRef.createComponent(componentType, { injector: viewContainerRef.injector });
|
|
736
|
+
const instance = componentRef.instance;
|
|
737
|
+
instance.context = context;
|
|
738
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
739
|
+
const g = componentRef.instance.getContainerG();
|
|
740
|
+
mountElementG(context.index, g, childrenContext);
|
|
741
|
+
componentRef.instance.initializeListRender();
|
|
742
|
+
return componentRef;
|
|
743
|
+
};
|
|
744
|
+
const getComponentType = (board, context) => {
|
|
745
|
+
const result = board.drawElement(context);
|
|
746
|
+
return result;
|
|
747
|
+
};
|
|
748
|
+
const getContext = (board, element, index, parent, previousContext) => {
|
|
749
|
+
let isSelected = isSelectedElement(board, element);
|
|
750
|
+
const previousElement = previousContext && previousContext.element;
|
|
751
|
+
if (previousElement && previousElement !== element && isSelectedElement(board, previousElement)) {
|
|
752
|
+
isSelected = true;
|
|
753
|
+
removeSelectedElement(board, previousElement);
|
|
754
|
+
addSelectedElement(board, element);
|
|
755
|
+
}
|
|
756
|
+
const context = {
|
|
757
|
+
element: element,
|
|
758
|
+
parent: parent,
|
|
759
|
+
board: board,
|
|
760
|
+
selected: isSelected,
|
|
761
|
+
index
|
|
762
|
+
};
|
|
763
|
+
return context;
|
|
764
|
+
};
|
|
765
|
+
// the g depth of root element:[1]-[2]-[3]-[4]
|
|
766
|
+
// the g depth of root element and children element(the [2] element has children):
|
|
767
|
+
// [1]-
|
|
768
|
+
// [2]([2-1-1][2-1-2][2-1][2-2][2-3-1][2-3-2][2-3][2])-
|
|
769
|
+
// [3]-
|
|
770
|
+
// [4]
|
|
771
|
+
const mountElementG = (index, g, childrenContext,
|
|
772
|
+
// for moving scene: the current index for first element before moving
|
|
773
|
+
currentIndexForFirstElement = null) => {
|
|
774
|
+
const { parent, parentG } = childrenContext;
|
|
775
|
+
if (PlaitBoard.isBoard(parent)) {
|
|
776
|
+
if (index > 0) {
|
|
777
|
+
const previousElement = parent.children[index - 1];
|
|
778
|
+
const previousContainerG = PlaitElement.getContainerG(previousElement, { suppressThrow: false });
|
|
779
|
+
previousContainerG.insertAdjacentElement('afterend', g);
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
if (currentIndexForFirstElement !== null) {
|
|
783
|
+
const firstElement = parent.children[currentIndexForFirstElement];
|
|
784
|
+
const firstContainerG = firstElement && PlaitElement.getContainerG(firstElement, { suppressThrow: true });
|
|
785
|
+
if (firstElement && firstContainerG) {
|
|
786
|
+
parentG.insertBefore(g, firstContainerG);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
throw new Error('fail to mount container on moving');
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
parentG.append(g);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
if (index > 0) {
|
|
799
|
+
const previousElement = parent.children[index - 1];
|
|
800
|
+
const previousElementG = PlaitElement.getElementG(previousElement);
|
|
801
|
+
previousElementG.insertAdjacentElement('afterend', g);
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
if (currentIndexForFirstElement) {
|
|
805
|
+
const nextElement = parent.children[currentIndexForFirstElement];
|
|
806
|
+
const nextPath = nextElement && PlaitBoard.findPath(childrenContext.board, nextElement);
|
|
807
|
+
const first = nextPath && PlaitNode.first(childrenContext.board, nextPath);
|
|
808
|
+
const firstContainerG = first && PlaitElement.getContainerG(first, { suppressThrow: false });
|
|
809
|
+
if (firstContainerG) {
|
|
810
|
+
parentG.insertBefore(g, firstContainerG);
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
throw new Error('fail to mount container on moving');
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
let parentElementG = PlaitElement.getElementG(parent);
|
|
818
|
+
parentG.insertBefore(g, parentElementG);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
const mountOnItemMove = (element, index, childrenContext, currentIndexForFirstElement) => {
|
|
824
|
+
const containerG = PlaitElement.getContainerG(element, { suppressThrow: false });
|
|
825
|
+
mountElementG(index, containerG, childrenContext, currentIndexForFirstElement);
|
|
826
|
+
if (element.children && !PlaitElement.isRootElement(element)) {
|
|
827
|
+
element.children.forEach((child, index) => {
|
|
828
|
+
mountOnItemMove(child, index, { ...childrenContext, parent: element }, null);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
|
|
624
833
|
class PlaitPluginElementComponent {
|
|
834
|
+
get hasChildren() {
|
|
835
|
+
return !!this.element.children;
|
|
836
|
+
}
|
|
625
837
|
set context(value) {
|
|
626
838
|
if (hasBeforeContextChange(this)) {
|
|
627
839
|
this.beforeContextChange(value);
|
|
@@ -632,20 +844,28 @@ class PlaitPluginElementComponent {
|
|
|
632
844
|
ELEMENT_TO_COMPONENT.set(this.element, this);
|
|
633
845
|
}
|
|
634
846
|
if (this.initialized) {
|
|
847
|
+
const elementG = this.getElementG();
|
|
848
|
+
const containerG = this.getContainerG();
|
|
849
|
+
NODE_TO_G.set(this.element, elementG);
|
|
850
|
+
NODE_TO_CONTAINER_G.set(this.element, containerG);
|
|
851
|
+
this.updateListRender();
|
|
635
852
|
this.cdr.markForCheck();
|
|
636
853
|
if (hasOnContextChanged(this)) {
|
|
637
854
|
this.onContextChanged(value, previousContext);
|
|
638
855
|
}
|
|
639
856
|
}
|
|
640
857
|
else {
|
|
641
|
-
if (PlaitElement.isRootElement(this.element) && this.
|
|
642
|
-
this.
|
|
643
|
-
this.
|
|
644
|
-
this.
|
|
858
|
+
if (PlaitElement.isRootElement(this.element) && this.hasChildren) {
|
|
859
|
+
this._g = createG();
|
|
860
|
+
this._containerG = createG();
|
|
861
|
+
this._containerG.append(this._g);
|
|
645
862
|
}
|
|
646
863
|
else {
|
|
647
|
-
this.
|
|
864
|
+
this._g = createG();
|
|
865
|
+
this._containerG = this._g;
|
|
648
866
|
}
|
|
867
|
+
NODE_TO_G.set(this.element, this._g);
|
|
868
|
+
NODE_TO_CONTAINER_G.set(this.element, this._containerG);
|
|
649
869
|
}
|
|
650
870
|
}
|
|
651
871
|
get context() {
|
|
@@ -660,25 +880,79 @@ class PlaitPluginElementComponent {
|
|
|
660
880
|
get selected() {
|
|
661
881
|
return this.context && this.context.selected;
|
|
662
882
|
}
|
|
663
|
-
|
|
664
|
-
return this.
|
|
883
|
+
getContainerG() {
|
|
884
|
+
return this._containerG;
|
|
885
|
+
}
|
|
886
|
+
getElementG() {
|
|
887
|
+
return this._g;
|
|
665
888
|
}
|
|
666
889
|
constructor(cdr) {
|
|
667
890
|
this.cdr = cdr;
|
|
891
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
668
892
|
this.initialized = false;
|
|
669
893
|
}
|
|
670
894
|
ngOnInit() {
|
|
671
895
|
if (this.element.type) {
|
|
672
|
-
|
|
896
|
+
this.getContainerG().setAttribute(`plait-${this.element.type}`, 'true');
|
|
897
|
+
}
|
|
898
|
+
if (this.hasChildren) {
|
|
899
|
+
if (PlaitElement.isRootElement(this.element)) {
|
|
900
|
+
this._rootContainerG = this._containerG;
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
const path = PlaitBoard.findPath(this.board, this.element);
|
|
904
|
+
const rootNode = PlaitNode.get(this.board, path.slice(0, 1));
|
|
905
|
+
this._rootContainerG = PlaitElement.getContainerG(rootNode, { suppressThrow: false });
|
|
906
|
+
}
|
|
673
907
|
}
|
|
908
|
+
this.getContainerG().setAttribute('plait-data-id', this.element.id);
|
|
674
909
|
this.initialized = true;
|
|
675
910
|
}
|
|
911
|
+
initializeListRender() {
|
|
912
|
+
if (this.hasChildren) {
|
|
913
|
+
this.listRender = new ListRender(this.board, this.viewContainerRef);
|
|
914
|
+
if (this.board.isExpanded(this.element)) {
|
|
915
|
+
this.listRender.initialize(this.element.children, this.initializeChildrenContext());
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
updateListRender() {
|
|
920
|
+
if (this.hasChildren) {
|
|
921
|
+
if (!this.listRender) {
|
|
922
|
+
throw new Error('incorrectly initialize list render');
|
|
923
|
+
}
|
|
924
|
+
if (this.board.isExpanded(this.element)) {
|
|
925
|
+
this.listRender.update(this.element.children, this.initializeChildrenContext());
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
if (this.listRender.initialized) {
|
|
929
|
+
this.listRender.destroy();
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
initializeChildrenContext() {
|
|
935
|
+
if (!this._rootContainerG) {
|
|
936
|
+
throw new Error('can not resolve root container g');
|
|
937
|
+
}
|
|
938
|
+
return {
|
|
939
|
+
board: this.board,
|
|
940
|
+
parent: this.element,
|
|
941
|
+
parentG: this._rootContainerG
|
|
942
|
+
};
|
|
943
|
+
}
|
|
676
944
|
ngOnDestroy() {
|
|
677
945
|
if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
|
|
678
946
|
ELEMENT_TO_COMPONENT.delete(this.element);
|
|
679
947
|
}
|
|
948
|
+
if (NODE_TO_G.get(this.element) === this._g) {
|
|
949
|
+
NODE_TO_G.delete(this.element);
|
|
950
|
+
}
|
|
951
|
+
if (NODE_TO_CONTAINER_G.get(this.element) === this._containerG) {
|
|
952
|
+
NODE_TO_CONTAINER_G.delete(this.element);
|
|
953
|
+
}
|
|
680
954
|
removeSelectedElement(this.board, this.element);
|
|
681
|
-
|
|
955
|
+
this.getContainerG().remove();
|
|
682
956
|
}
|
|
683
957
|
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 }); }
|
|
684
958
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.4", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
|
|
@@ -968,6 +1242,42 @@ function toFixed(v) {
|
|
|
968
1242
|
function approximately(a, b, precision = 0.000001) {
|
|
969
1243
|
return Math.abs(a - b) <= precision;
|
|
970
1244
|
}
|
|
1245
|
+
// https://medium.com/@steveruiz/find-the-points-where-a-line-segment-intercepts-an-angled-ellipse-in-javascript-typescript-e451524beece
|
|
1246
|
+
function getCrossingPointsBetweenEllipseAndSegment(startPoint, endPoint, cx, cy, rx, ry, segment_only = true) {
|
|
1247
|
+
// If the ellipse or line segment are empty, return no tValues.
|
|
1248
|
+
if (rx === 0 || ry === 0 || (startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1])) {
|
|
1249
|
+
return [];
|
|
1250
|
+
}
|
|
1251
|
+
rx = rx < 0 ? rx : -rx;
|
|
1252
|
+
ry = ry < 0 ? ry : -ry;
|
|
1253
|
+
startPoint[0] -= cx;
|
|
1254
|
+
startPoint[1] -= cy;
|
|
1255
|
+
endPoint[0] -= cx;
|
|
1256
|
+
endPoint[1] -= cy;
|
|
1257
|
+
// Calculate the quadratic parameters.
|
|
1258
|
+
var A = ((endPoint[0] - startPoint[0]) * (endPoint[0] - startPoint[0])) / rx / rx +
|
|
1259
|
+
((endPoint[1] - startPoint[1]) * (endPoint[1] - startPoint[1])) / ry / ry;
|
|
1260
|
+
var B = (2 * startPoint[0] * (endPoint[0] - startPoint[0])) / rx / rx + (2 * startPoint[1] * (endPoint[1] - startPoint[1])) / ry / ry;
|
|
1261
|
+
var C = (startPoint[0] * startPoint[0]) / rx / rx + (startPoint[1] * startPoint[1]) / ry / ry - 1;
|
|
1262
|
+
// Make a list of t values (normalized points on the line where intersections occur).
|
|
1263
|
+
var tValues = [];
|
|
1264
|
+
// Calculate the discriminant.
|
|
1265
|
+
var discriminant = B * B - 4 * A * C;
|
|
1266
|
+
if (discriminant === 0) {
|
|
1267
|
+
// One real solution.
|
|
1268
|
+
tValues.push(-B / 2 / A);
|
|
1269
|
+
}
|
|
1270
|
+
else if (discriminant > 0) {
|
|
1271
|
+
// Two real solutions.
|
|
1272
|
+
tValues.push((-B + Math.sqrt(discriminant)) / 2 / A);
|
|
1273
|
+
tValues.push((-B - Math.sqrt(discriminant)) / 2 / A);
|
|
1274
|
+
}
|
|
1275
|
+
return (tValues
|
|
1276
|
+
// Filter to only points that are on the segment.
|
|
1277
|
+
.filter(t => !segment_only || (t >= 0 && t <= 1))
|
|
1278
|
+
// Solve for points.
|
|
1279
|
+
.map(t => [startPoint[0] + (endPoint[0] - startPoint[0]) * t + cx, startPoint[1] + (endPoint[1] - startPoint[1]) * t + cy]));
|
|
1280
|
+
}
|
|
971
1281
|
|
|
972
1282
|
function isInPlaitBoard(board, x, y) {
|
|
973
1283
|
const plaitBoardElement = PlaitBoard.getBoardContainer(board);
|
|
@@ -1055,6 +1365,42 @@ function uniqueById(elements) {
|
|
|
1055
1365
|
});
|
|
1056
1366
|
return Array.from(uniqueMap.values());
|
|
1057
1367
|
}
|
|
1368
|
+
const findLastIndex = (array, cb, fromIndex = array.length - 1) => {
|
|
1369
|
+
if (fromIndex < 0) {
|
|
1370
|
+
fromIndex = array.length + fromIndex;
|
|
1371
|
+
}
|
|
1372
|
+
fromIndex = Math.min(array.length - 1, Math.max(fromIndex, 0));
|
|
1373
|
+
let index = fromIndex + 1;
|
|
1374
|
+
while (--index > -1) {
|
|
1375
|
+
if (cb(array[index], index, array)) {
|
|
1376
|
+
return index;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return -1;
|
|
1380
|
+
};
|
|
1381
|
+
const findIndex = (array, cb, fromIndex = 0) => {
|
|
1382
|
+
// fromIndex = 2
|
|
1383
|
+
if (fromIndex < 0) {
|
|
1384
|
+
fromIndex = array.length + fromIndex;
|
|
1385
|
+
}
|
|
1386
|
+
fromIndex = Math.min(array.length, Math.max(fromIndex, 0));
|
|
1387
|
+
let index = fromIndex - 1;
|
|
1388
|
+
while (++index < array.length) {
|
|
1389
|
+
if (cb(array[index], index, array)) {
|
|
1390
|
+
return index;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return -1;
|
|
1394
|
+
};
|
|
1395
|
+
const isIndicesContinuous = (indexes) => {
|
|
1396
|
+
indexes.sort((a, b) => a - b);
|
|
1397
|
+
for (let i = 1; i < indexes.length; i++) {
|
|
1398
|
+
if (indexes[i] !== indexes[i - 1] + 1) {
|
|
1399
|
+
return false;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return true;
|
|
1403
|
+
};
|
|
1058
1404
|
|
|
1059
1405
|
/**
|
|
1060
1406
|
* Check whether to merge an operation into the previous operation.
|
|
@@ -1694,157 +2040,458 @@ const setIsFromViewportChange = (board, state) => {
|
|
|
1694
2040
|
};
|
|
1695
2041
|
function scrollToRectangle(board, client) { }
|
|
1696
2042
|
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
}
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
value[key] = null;
|
|
1710
|
-
BOARD_TO_RAF.set(board, value);
|
|
1711
|
-
PlaitBoard.isAlive(board) && fn();
|
|
1712
|
-
});
|
|
1713
|
-
const state = getRAFState(board);
|
|
1714
|
-
state[key] = timerId;
|
|
1715
|
-
BOARD_TO_RAF.set(board, state);
|
|
1716
|
-
};
|
|
1717
|
-
let timerId = getTimerId(board, key);
|
|
1718
|
-
if (timerId !== null) {
|
|
1719
|
-
cancelAnimationFrame(timerId);
|
|
1720
|
-
}
|
|
1721
|
-
scheduleFunc();
|
|
1722
|
-
};
|
|
1723
|
-
const debounce = (func, wait, options) => {
|
|
1724
|
-
let timerSubscription = null;
|
|
1725
|
-
return () => {
|
|
1726
|
-
if (timerSubscription && !timerSubscription.closed) {
|
|
1727
|
-
timerSubscription.unsubscribe();
|
|
1728
|
-
timerSubscription = timer(wait).subscribe(() => {
|
|
1729
|
-
func();
|
|
1730
|
-
});
|
|
2043
|
+
const Path = {
|
|
2044
|
+
/**
|
|
2045
|
+
* Get a list of ancestor paths for a given path.
|
|
2046
|
+
*
|
|
2047
|
+
* The paths are sorted from shallowest to deepest ancestor. However, if the
|
|
2048
|
+
* `reverse: true` option is passed, they are reversed.
|
|
2049
|
+
*/
|
|
2050
|
+
ancestors(path, options = {}) {
|
|
2051
|
+
const { reverse = false } = options;
|
|
2052
|
+
let paths = Path.levels(path, options);
|
|
2053
|
+
if (reverse) {
|
|
2054
|
+
paths = paths.slice(1);
|
|
1731
2055
|
}
|
|
1732
2056
|
else {
|
|
1733
|
-
|
|
1734
|
-
timer(0).subscribe(() => {
|
|
1735
|
-
func();
|
|
1736
|
-
});
|
|
1737
|
-
}
|
|
1738
|
-
timerSubscription = timer(wait).subscribe();
|
|
2057
|
+
paths = paths.slice(0, -1);
|
|
1739
2058
|
}
|
|
1740
|
-
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
const
|
|
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
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2059
|
+
return paths;
|
|
2060
|
+
},
|
|
2061
|
+
/**
|
|
2062
|
+
* Get a list of paths at every level down to a path. Note: this is the same
|
|
2063
|
+
* as `Path.ancestors`, but including the path itself.
|
|
2064
|
+
*
|
|
2065
|
+
* The paths are sorted from shallowest to deepest. However, if the `reverse:
|
|
2066
|
+
* true` option is passed, they are reversed.
|
|
2067
|
+
*/
|
|
2068
|
+
levels(path, options = {}) {
|
|
2069
|
+
const { reverse = false } = options;
|
|
2070
|
+
const list = [];
|
|
2071
|
+
for (let i = 0; i <= path.length; i++) {
|
|
2072
|
+
list.push(path.slice(0, i));
|
|
2073
|
+
}
|
|
2074
|
+
if (reverse) {
|
|
2075
|
+
list.reverse();
|
|
2076
|
+
}
|
|
2077
|
+
return list;
|
|
2078
|
+
},
|
|
2079
|
+
parent(path) {
|
|
2080
|
+
if (path.length === 0) {
|
|
2081
|
+
throw new Error(`Cannot get the parent path of the root path [${path}].`);
|
|
2082
|
+
}
|
|
2083
|
+
return path.slice(0, -1);
|
|
2084
|
+
},
|
|
2085
|
+
next(path) {
|
|
2086
|
+
if (path.length === 0) {
|
|
2087
|
+
throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
|
|
2088
|
+
}
|
|
2089
|
+
const last = path[path.length - 1];
|
|
2090
|
+
return path.slice(0, -1).concat(last + 1);
|
|
2091
|
+
},
|
|
2092
|
+
hasPrevious(path) {
|
|
2093
|
+
return path[path.length - 1] > 0;
|
|
2094
|
+
},
|
|
2095
|
+
previous(path) {
|
|
2096
|
+
if (path.length === 0) {
|
|
2097
|
+
throw new Error(`Cannot get the previous path of a root path [${path}], because it has no previous index.`);
|
|
2098
|
+
}
|
|
2099
|
+
const last = path[path.length - 1];
|
|
2100
|
+
if (last <= 0) {
|
|
2101
|
+
throw new Error(`Cannot get the previous path of a first child path [${path}] because it would result in a negative index.`);
|
|
2102
|
+
}
|
|
2103
|
+
return path.slice(0, -1).concat(last - 1);
|
|
2104
|
+
},
|
|
2105
|
+
/**
|
|
2106
|
+
* Check if a path is an ancestor of another.
|
|
2107
|
+
*/
|
|
2108
|
+
isAncestor(path, another) {
|
|
2109
|
+
return path.length < another.length && Path.compare(path, another) === 0;
|
|
2110
|
+
},
|
|
2111
|
+
/**
|
|
2112
|
+
* Compare a path to another, returning an integer indicating whether the path
|
|
2113
|
+
* was before, at, or after the other.
|
|
2114
|
+
*
|
|
2115
|
+
* Note: Two paths of unequal length can still receive a `0` result if one is
|
|
2116
|
+
* directly above or below the other. If you want exact matching, use
|
|
2117
|
+
* [[Path.equals]] instead.
|
|
2118
|
+
*/
|
|
2119
|
+
compare(path, another) {
|
|
2120
|
+
const min = Math.min(path.length, another.length);
|
|
2121
|
+
for (let i = 0; i < min; i++) {
|
|
2122
|
+
if (path[i] < another[i])
|
|
2123
|
+
return -1;
|
|
2124
|
+
if (path[i] > another[i])
|
|
2125
|
+
return 1;
|
|
2126
|
+
}
|
|
2127
|
+
return 0;
|
|
2128
|
+
},
|
|
2129
|
+
/**
|
|
2130
|
+
* Check if a path is exactly equal to another.
|
|
2131
|
+
*/
|
|
2132
|
+
equals(path, another) {
|
|
2133
|
+
return path.length === another.length && path.every((n, i) => n === another[i]);
|
|
2134
|
+
},
|
|
2135
|
+
/**
|
|
2136
|
+
* Check if a path ends before one of the indexes in another.
|
|
2137
|
+
*/
|
|
2138
|
+
endsBefore(path, another) {
|
|
2139
|
+
const i = path.length - 1;
|
|
2140
|
+
const as = path.slice(0, i);
|
|
2141
|
+
const bs = another.slice(0, i);
|
|
2142
|
+
const av = path[i];
|
|
2143
|
+
const bv = another[i];
|
|
2144
|
+
return Path.equals(as, bs) && av < bv;
|
|
2145
|
+
},
|
|
2146
|
+
/**
|
|
2147
|
+
* Check if a path is a sibling of another.
|
|
2148
|
+
*/
|
|
2149
|
+
isSibling(path, another) {
|
|
2150
|
+
if (path.length !== another.length) {
|
|
2151
|
+
return false;
|
|
2152
|
+
}
|
|
2153
|
+
const as = path.slice(0, -1);
|
|
2154
|
+
const bs = another.slice(0, -1);
|
|
2155
|
+
const al = path[path.length - 1];
|
|
2156
|
+
const bl = another[another.length - 1];
|
|
2157
|
+
return al !== bl && Path.equals(as, bs);
|
|
2158
|
+
},
|
|
2159
|
+
transform(path, operation) {
|
|
2160
|
+
if (!path)
|
|
2161
|
+
return null;
|
|
2162
|
+
// PERF: use destructing instead of immer
|
|
2163
|
+
const p = [...path];
|
|
2164
|
+
// PERF: Exit early if the operation is guaranteed not to have an effect.
|
|
2165
|
+
if (path.length === 0) {
|
|
2166
|
+
return p;
|
|
2167
|
+
}
|
|
2168
|
+
switch (operation.type) {
|
|
2169
|
+
case 'insert_node': {
|
|
2170
|
+
const { path: op } = operation;
|
|
2171
|
+
if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
|
|
2172
|
+
p[op.length - 1] += 1;
|
|
2173
|
+
}
|
|
2174
|
+
break;
|
|
2175
|
+
}
|
|
2176
|
+
case 'remove_node': {
|
|
2177
|
+
const { path: op } = operation;
|
|
2178
|
+
if (Path.equals(op, p) || Path.isAncestor(op, p)) {
|
|
2179
|
+
return null;
|
|
2180
|
+
}
|
|
2181
|
+
else if (Path.endsBefore(op, p)) {
|
|
2182
|
+
p[op.length - 1] -= 1;
|
|
2183
|
+
}
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
case 'move_node': {
|
|
2187
|
+
const { path: op, newPath: onp } = operation;
|
|
2188
|
+
// If the old and new path are the same, it's a no-op.
|
|
2189
|
+
if (Path.equals(op, onp)) {
|
|
2190
|
+
return p;
|
|
2191
|
+
}
|
|
2192
|
+
if (Path.isAncestor(op, p) || Path.equals(op, p)) {
|
|
2193
|
+
const copy = onp.slice();
|
|
2194
|
+
if (Path.endsBefore(op, onp) && op.length < onp.length) {
|
|
2195
|
+
copy[op.length - 1] -= 1;
|
|
2196
|
+
}
|
|
2197
|
+
return copy.concat(p.slice(op.length));
|
|
2198
|
+
}
|
|
2199
|
+
else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
|
|
2200
|
+
if (Path.endsBefore(op, p)) {
|
|
2201
|
+
p[op.length - 1] -= 1;
|
|
2202
|
+
}
|
|
2203
|
+
else {
|
|
2204
|
+
p[op.length - 1] += 1;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
|
|
2208
|
+
if (Path.endsBefore(op, p)) {
|
|
2209
|
+
p[op.length - 1] -= 1;
|
|
2210
|
+
}
|
|
2211
|
+
p[onp.length - 1] += 1;
|
|
2212
|
+
}
|
|
2213
|
+
else if (Path.endsBefore(op, p)) {
|
|
2214
|
+
if (Path.equals(onp, p)) {
|
|
2215
|
+
p[onp.length - 1] += 1;
|
|
2216
|
+
}
|
|
2217
|
+
p[op.length - 1] -= 1;
|
|
2218
|
+
}
|
|
2219
|
+
break;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
return p;
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
|
|
2226
|
+
const PlaitNode = {
|
|
2227
|
+
parent: (board, path) => {
|
|
2228
|
+
const parentPath = Path.parent(path);
|
|
2229
|
+
const p = PlaitNode.get(board, parentPath);
|
|
2230
|
+
return p;
|
|
2231
|
+
},
|
|
2232
|
+
/**
|
|
2233
|
+
* Return a generator of all the ancestor nodes above a specific path.
|
|
2234
|
+
*
|
|
2235
|
+
* By default the order is top-down, from highest to lowest ancestor in
|
|
2236
|
+
* the tree, but you can pass the `reverse: true` option to go bottom-up.
|
|
2237
|
+
*/
|
|
2238
|
+
*parents(root, path, options = {}) {
|
|
2239
|
+
for (const p of Path.ancestors(path, options)) {
|
|
2240
|
+
const n = PlaitNode.get(root, p);
|
|
2241
|
+
yield n;
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2244
|
+
get(root, path) {
|
|
2245
|
+
let node = root;
|
|
2246
|
+
for (let i = 0; i < path.length; i++) {
|
|
2247
|
+
const p = path[i];
|
|
2248
|
+
if (!node || !node.children || !node.children[p]) {
|
|
2249
|
+
throw new Error(`Cannot find a descendant at path [${path}]`);
|
|
2250
|
+
}
|
|
2251
|
+
node = node.children[p];
|
|
2252
|
+
}
|
|
2253
|
+
return node;
|
|
2254
|
+
},
|
|
2255
|
+
last(board, path) {
|
|
2256
|
+
let n = PlaitNode.get(board, path);
|
|
2257
|
+
while (n && n.children && n.children.length > 0) {
|
|
2258
|
+
const i = n.children.length - 1;
|
|
2259
|
+
n = n.children[i];
|
|
2260
|
+
}
|
|
2261
|
+
return n;
|
|
2262
|
+
},
|
|
2263
|
+
first(board, path) {
|
|
2264
|
+
const p = path.slice();
|
|
2265
|
+
let n = PlaitNode.get(board, p);
|
|
2266
|
+
if (!n.children) {
|
|
2267
|
+
return n;
|
|
2268
|
+
}
|
|
2269
|
+
while (n) {
|
|
2270
|
+
if (n.children.length === 0) {
|
|
2271
|
+
break;
|
|
2272
|
+
}
|
|
2273
|
+
else {
|
|
2274
|
+
n = n.children[0];
|
|
2275
|
+
p.push(0);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
return n;
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
|
|
2282
|
+
function insertNode(board, node, path) {
|
|
2283
|
+
const operation = { type: 'insert_node', node, path };
|
|
2284
|
+
board.apply(operation);
|
|
2285
|
+
}
|
|
2286
|
+
function setNode(board, props, path) {
|
|
2287
|
+
const properties = {};
|
|
2288
|
+
const newProperties = {};
|
|
2289
|
+
const node = PlaitNode.get(board, path);
|
|
2290
|
+
for (const k in props) {
|
|
2291
|
+
if (node[k] !== props[k]) {
|
|
2292
|
+
if (node.hasOwnProperty(k)) {
|
|
2293
|
+
properties[k] = node[k];
|
|
2294
|
+
}
|
|
2295
|
+
if (props[k] != null)
|
|
2296
|
+
newProperties[k] = props[k];
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
const operation = { type: 'set_node', properties, newProperties, path };
|
|
2300
|
+
board.apply(operation);
|
|
2301
|
+
}
|
|
2302
|
+
function removeNode(board, path) {
|
|
2303
|
+
const node = PlaitNode.get(board, path);
|
|
2304
|
+
const operation = { type: 'remove_node', path, node };
|
|
2305
|
+
board.apply(operation);
|
|
2306
|
+
}
|
|
2307
|
+
function moveNode(board, path, newPath) {
|
|
2308
|
+
const operation = { type: 'move_node', path, newPath };
|
|
2309
|
+
board.apply(operation);
|
|
2310
|
+
}
|
|
2311
|
+
const NodeTransforms = {
|
|
2312
|
+
insertNode,
|
|
2313
|
+
setNode,
|
|
2314
|
+
removeNode,
|
|
2315
|
+
moveNode
|
|
2316
|
+
};
|
|
2317
|
+
|
|
2318
|
+
const BOARD_TO_RAF = new WeakMap();
|
|
2319
|
+
const getTimerId = (board, key) => {
|
|
2320
|
+
const state = getRAFState(board);
|
|
2321
|
+
return state[key] || null;
|
|
2322
|
+
};
|
|
2323
|
+
const getRAFState = (board) => {
|
|
2324
|
+
return BOARD_TO_RAF.get(board) || {};
|
|
2325
|
+
};
|
|
2326
|
+
const throttleRAF = (board, key, fn) => {
|
|
2327
|
+
const scheduleFunc = () => {
|
|
2328
|
+
let timerId = requestAnimationFrame(() => {
|
|
2329
|
+
const value = BOARD_TO_RAF.get(board) || {};
|
|
2330
|
+
value[key] = null;
|
|
2331
|
+
BOARD_TO_RAF.set(board, value);
|
|
2332
|
+
PlaitBoard.isAlive(board) && fn();
|
|
2333
|
+
});
|
|
2334
|
+
const state = getRAFState(board);
|
|
2335
|
+
state[key] = timerId;
|
|
2336
|
+
BOARD_TO_RAF.set(board, state);
|
|
2337
|
+
};
|
|
2338
|
+
let timerId = getTimerId(board, key);
|
|
2339
|
+
if (timerId !== null) {
|
|
2340
|
+
cancelAnimationFrame(timerId);
|
|
2341
|
+
}
|
|
2342
|
+
scheduleFunc();
|
|
2343
|
+
};
|
|
2344
|
+
const debounce = (func, wait, options) => {
|
|
2345
|
+
let timerSubscription = null;
|
|
2346
|
+
return () => {
|
|
2347
|
+
if (timerSubscription && !timerSubscription.closed) {
|
|
2348
|
+
timerSubscription.unsubscribe();
|
|
2349
|
+
timerSubscription = timer(wait).subscribe(() => {
|
|
2350
|
+
func();
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
else {
|
|
2354
|
+
if (options?.leading) {
|
|
2355
|
+
timer(0).subscribe(() => {
|
|
2356
|
+
func();
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
timerSubscription = timer(wait).subscribe();
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
2362
|
+
};
|
|
2363
|
+
const getElementsIndices = (board, elements) => {
|
|
2364
|
+
sortElements(board, elements);
|
|
2365
|
+
return elements
|
|
2366
|
+
.map(item => {
|
|
2367
|
+
return board.children.map(item => item.id).indexOf(item.id);
|
|
2368
|
+
})
|
|
2369
|
+
.filter(item => item >= 0);
|
|
2370
|
+
};
|
|
2371
|
+
const getHighestIndexOfElement = (board, elements) => {
|
|
2372
|
+
const indices = getElementsIndices(board, elements);
|
|
2373
|
+
return indices[indices.length - 1];
|
|
2374
|
+
};
|
|
2375
|
+
const moveElementsToNewPath = (board, moveOptions) => {
|
|
2376
|
+
moveOptions
|
|
2377
|
+
.map(item => {
|
|
2378
|
+
const path = PlaitBoard.findPath(board, item.element);
|
|
2379
|
+
const ref = board.pathRef(path);
|
|
2380
|
+
return () => {
|
|
2381
|
+
ref.current && NodeTransforms.moveNode(board, ref.current, item.newPath);
|
|
2382
|
+
ref.unref();
|
|
2383
|
+
};
|
|
2384
|
+
})
|
|
2385
|
+
.forEach(action => {
|
|
2386
|
+
action();
|
|
2387
|
+
});
|
|
2388
|
+
};
|
|
2389
|
+
|
|
2390
|
+
const IS_DRAGGING = new WeakMap();
|
|
2391
|
+
const isDragging = (board) => {
|
|
2392
|
+
return !!IS_DRAGGING.get(board);
|
|
2393
|
+
};
|
|
2394
|
+
const setDragging = (board, state) => {
|
|
2395
|
+
IS_DRAGGING.set(board, state);
|
|
2396
|
+
};
|
|
2397
|
+
|
|
2398
|
+
const getMovingElements = (board) => {
|
|
2399
|
+
return BOARD_TO_MOVING_ELEMENT.get(board) || [];
|
|
2400
|
+
};
|
|
2401
|
+
const isMovingElements = (board) => {
|
|
2402
|
+
return (BOARD_TO_MOVING_ELEMENT.get(board) || []).length > 0;
|
|
2403
|
+
};
|
|
2404
|
+
const removeMovingElements = (board) => {
|
|
2405
|
+
BOARD_TO_MOVING_ELEMENT.delete(board);
|
|
2406
|
+
setDragging(board, false);
|
|
2407
|
+
};
|
|
2408
|
+
const cacheMovingElements = (board, elements) => {
|
|
2409
|
+
BOARD_TO_MOVING_ELEMENT.set(board, elements);
|
|
2410
|
+
setDragging(board, true);
|
|
2411
|
+
};
|
|
2412
|
+
|
|
2413
|
+
const IMAGE_CONTAINER = 'plait-image-container';
|
|
2414
|
+
/**
|
|
2415
|
+
* Is element node
|
|
2416
|
+
* @param node
|
|
2417
|
+
* @returns
|
|
2418
|
+
*/
|
|
2419
|
+
function isElementNode(node) {
|
|
2420
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* load image resources
|
|
2424
|
+
* @param url image url
|
|
2425
|
+
* @returns image element
|
|
2426
|
+
*/
|
|
2427
|
+
function loadImage(src) {
|
|
2428
|
+
return new Promise((resolve, reject) => {
|
|
2429
|
+
const img = new Image();
|
|
2430
|
+
img.crossOrigin = 'Anonymous';
|
|
2431
|
+
img.onload = () => resolve(img);
|
|
2432
|
+
img.onerror = () => reject(new Error('Failed to load image'));
|
|
2433
|
+
img.src = src;
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* create and return canvas and context
|
|
2438
|
+
* @param width canvas width
|
|
2439
|
+
* @param height canvas height
|
|
2440
|
+
* @param fillStyle fill style
|
|
2441
|
+
* @returns canvas and context
|
|
2442
|
+
*/
|
|
2443
|
+
function createCanvas(width, height, fillStyle = 'transparent') {
|
|
2444
|
+
const canvas = document.createElement('canvas');
|
|
2445
|
+
const ctx = canvas.getContext('2d');
|
|
2446
|
+
canvas.width = width;
|
|
2447
|
+
canvas.height = height;
|
|
2448
|
+
canvas.style.width = `${width}px`;
|
|
2449
|
+
canvas.style.height = `${height}px`;
|
|
2450
|
+
ctx.strokeStyle = '#ffffff';
|
|
2451
|
+
ctx.fillStyle = fillStyle;
|
|
2452
|
+
ctx.fillRect(0, 0, width, height);
|
|
2453
|
+
return {
|
|
2454
|
+
canvas,
|
|
2455
|
+
ctx
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* convert image to base64
|
|
2460
|
+
* @param url image url
|
|
2461
|
+
* @returns image base64
|
|
2462
|
+
*/
|
|
2463
|
+
function convertImageToBase64(url) {
|
|
2464
|
+
return loadImage(url).then(img => {
|
|
2465
|
+
const { canvas, ctx } = createCanvas(img.width, img.height);
|
|
2466
|
+
ctx?.drawImage(img, 0, 0);
|
|
2467
|
+
return canvas.toDataURL('image/png');
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* clone node style
|
|
2472
|
+
* @param nativeNode source node
|
|
2473
|
+
* @param clonedNode clone node
|
|
2474
|
+
*/
|
|
2475
|
+
function cloneCSSStyle(nativeNode, clonedNode) {
|
|
2476
|
+
const targetStyle = clonedNode?.style;
|
|
2477
|
+
if (!targetStyle) {
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
2481
|
+
if (sourceStyle.cssText) {
|
|
2482
|
+
targetStyle.cssText = sourceStyle.cssText;
|
|
2483
|
+
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
2484
|
+
}
|
|
2485
|
+
else {
|
|
2486
|
+
Array.from(sourceStyle).forEach(name => {
|
|
2487
|
+
let value = sourceStyle.getPropertyValue(name);
|
|
2488
|
+
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* batch clone target styles
|
|
2494
|
+
* @param sourceNode
|
|
1848
2495
|
* @param cloneNode
|
|
1849
2496
|
* @param inlineStyleClassNames
|
|
1850
2497
|
*/
|
|
@@ -1899,7 +2546,7 @@ async function cloneSvg(board, elements, rectangle, options) {
|
|
|
1899
2546
|
const { width, height, x, y } = rectangle;
|
|
1900
2547
|
const { padding = 4, inlineStyleClassNames } = options;
|
|
1901
2548
|
const sourceSvg = PlaitBoard.getHost(board);
|
|
1902
|
-
const selectedGElements = elements.map(value => PlaitElement.
|
|
2549
|
+
const selectedGElements = elements.map(value => PlaitElement.getElementG(value));
|
|
1903
2550
|
const cloneSvgElement = sourceSvg.cloneNode();
|
|
1904
2551
|
const newHostElement = PlaitBoard.getElementHost(board).cloneNode();
|
|
1905
2552
|
cloneSvgElement.style.width = `${width}px`;
|
|
@@ -2013,19 +2660,19 @@ const getProbablySupportsClipboardWriteText = () => {
|
|
|
2013
2660
|
const getProbablySupportsClipboardRead = () => {
|
|
2014
2661
|
return 'clipboard' in navigator && 'read' in navigator.clipboard;
|
|
2015
2662
|
};
|
|
2016
|
-
const createClipboardContext = (type,
|
|
2663
|
+
const createClipboardContext = (type, elements, text) => {
|
|
2017
2664
|
return {
|
|
2018
2665
|
type,
|
|
2019
|
-
|
|
2666
|
+
elements,
|
|
2020
2667
|
text
|
|
2021
2668
|
};
|
|
2022
2669
|
};
|
|
2023
2670
|
const addClipboardContext = (clipboardContext, addition) => {
|
|
2024
|
-
const { type,
|
|
2671
|
+
const { type, elements, text } = clipboardContext;
|
|
2025
2672
|
if (type === addition.type) {
|
|
2026
2673
|
return {
|
|
2027
2674
|
type,
|
|
2028
|
-
|
|
2675
|
+
elements: elements.concat(addition.elements),
|
|
2029
2676
|
text: text + ' ' + addition.text
|
|
2030
2677
|
};
|
|
2031
2678
|
}
|
|
@@ -2150,297 +2797,75 @@ const getClipboardData = async (dataTransfer) => {
|
|
|
2150
2797
|
return await getNavigatorClipboard();
|
|
2151
2798
|
}
|
|
2152
2799
|
return clipboardData;
|
|
2153
|
-
};
|
|
2154
|
-
const setClipboardData = async (dataTransfer, clipboardContext) => {
|
|
2155
|
-
if (!clipboardContext) {
|
|
2156
|
-
return;
|
|
2157
|
-
}
|
|
2158
|
-
const { type,
|
|
2159
|
-
if (getProbablySupportsClipboardWrite()) {
|
|
2160
|
-
return await setNavigatorClipboard(type,
|
|
2161
|
-
}
|
|
2162
|
-
if (dataTransfer) {
|
|
2163
|
-
setDataTransferClipboard(dataTransfer, type,
|
|
2164
|
-
setDataTransferClipboardText(dataTransfer, text);
|
|
2165
|
-
return;
|
|
2166
|
-
}
|
|
2167
|
-
// Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
|
|
2168
|
-
// Such as contextmenu copy in Firefox.
|
|
2169
|
-
if (getProbablySupportsClipboardWriteText()) {
|
|
2170
|
-
return await navigator.clipboard.writeText(buildPlaitHtml(type,
|
|
2171
|
-
}
|
|
2172
|
-
};
|
|
2173
|
-
|
|
2174
|
-
const BOARD_TO_TOUCH_REF = new WeakMap();
|
|
2175
|
-
const isPreventTouchMove = (board) => {
|
|
2176
|
-
return !!BOARD_TO_TOUCH_REF.get(board);
|
|
2177
|
-
};
|
|
2178
|
-
const preventTouchMove = (board, event, state) => {
|
|
2179
|
-
const hostElement = PlaitBoard.getElementHost(board);
|
|
2180
|
-
const activeHostElement = PlaitBoard.getElementActiveHost(board);
|
|
2181
|
-
if (state) {
|
|
2182
|
-
if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) &&
|
|
2183
|
-
(hostElement.contains(event.target) || activeHostElement.contains(event.target))) {
|
|
2184
|
-
BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
|
|
2185
|
-
}
|
|
2186
|
-
else {
|
|
2187
|
-
BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
else {
|
|
2191
|
-
const ref = BOARD_TO_TOUCH_REF.get(board);
|
|
2192
|
-
if (ref) {
|
|
2193
|
-
BOARD_TO_TOUCH_REF.delete(board);
|
|
2194
|
-
ref.host?.remove();
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
};
|
|
2198
|
-
/**
|
|
2199
|
-
* some intersection maybe cause target is removed from current browser window,
|
|
2200
|
-
* after it was removed touch move event will not be fired
|
|
2201
|
-
* so scroll behavior will can not be prevented in mobile browser device
|
|
2202
|
-
* this function will prevent target element being remove.
|
|
2203
|
-
*/
|
|
2204
|
-
const handleTouchTarget = (board) => {
|
|
2205
|
-
const touchRef = BOARD_TO_TOUCH_REF.get(board);
|
|
2206
|
-
if (touchRef &&
|
|
2207
|
-
touchRef.target &&
|
|
2208
|
-
!PlaitBoard.getElementHost(board).contains(touchRef.target) &&
|
|
2209
|
-
!PlaitBoard.getElementActiveHost(board).contains(touchRef.target)) {
|
|
2210
|
-
touchRef.target.style.opacity = '0';
|
|
2211
|
-
const host = createG();
|
|
2212
|
-
host.appendChild(touchRef.target);
|
|
2213
|
-
touchRef.host = host;
|
|
2214
|
-
host.classList.add('touch-target');
|
|
2215
|
-
PlaitBoard.getElementActiveHost(board).append(host);
|
|
2216
|
-
}
|
|
2217
|
-
};
|
|
2218
|
-
|
|
2219
|
-
const Viewport = {
|
|
2220
|
-
isViewport: (value) => {
|
|
2221
|
-
return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
|
|
2222
|
-
}
|
|
2223
|
-
};
|
|
2224
|
-
|
|
2225
|
-
const Path = {
|
|
2226
|
-
/**
|
|
2227
|
-
* Get a list of ancestor paths for a given path.
|
|
2228
|
-
*
|
|
2229
|
-
* The paths are sorted from shallowest to deepest ancestor. However, if the
|
|
2230
|
-
* `reverse: true` option is passed, they are reversed.
|
|
2231
|
-
*/
|
|
2232
|
-
ancestors(path, options = {}) {
|
|
2233
|
-
const { reverse = false } = options;
|
|
2234
|
-
let paths = Path.levels(path, options);
|
|
2235
|
-
if (reverse) {
|
|
2236
|
-
paths = paths.slice(1);
|
|
2237
|
-
}
|
|
2238
|
-
else {
|
|
2239
|
-
paths = paths.slice(0, -1);
|
|
2240
|
-
}
|
|
2241
|
-
return paths;
|
|
2242
|
-
},
|
|
2243
|
-
/**
|
|
2244
|
-
* Get a list of paths at every level down to a path. Note: this is the same
|
|
2245
|
-
* as `Path.ancestors`, but including the path itself.
|
|
2246
|
-
*
|
|
2247
|
-
* The paths are sorted from shallowest to deepest. However, if the `reverse:
|
|
2248
|
-
* true` option is passed, they are reversed.
|
|
2249
|
-
*/
|
|
2250
|
-
levels(path, options = {}) {
|
|
2251
|
-
const { reverse = false } = options;
|
|
2252
|
-
const list = [];
|
|
2253
|
-
for (let i = 0; i <= path.length; i++) {
|
|
2254
|
-
list.push(path.slice(0, i));
|
|
2255
|
-
}
|
|
2256
|
-
if (reverse) {
|
|
2257
|
-
list.reverse();
|
|
2258
|
-
}
|
|
2259
|
-
return list;
|
|
2260
|
-
},
|
|
2261
|
-
parent(path) {
|
|
2262
|
-
if (path.length === 0) {
|
|
2263
|
-
throw new Error(`Cannot get the parent path of the root path [${path}].`);
|
|
2264
|
-
}
|
|
2265
|
-
return path.slice(0, -1);
|
|
2266
|
-
},
|
|
2267
|
-
next(path) {
|
|
2268
|
-
if (path.length === 0) {
|
|
2269
|
-
throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
|
|
2270
|
-
}
|
|
2271
|
-
const last = path[path.length - 1];
|
|
2272
|
-
return path.slice(0, -1).concat(last + 1);
|
|
2273
|
-
},
|
|
2274
|
-
hasPrevious(path) {
|
|
2275
|
-
return path[path.length - 1] > 0;
|
|
2276
|
-
},
|
|
2277
|
-
previous(path) {
|
|
2278
|
-
if (path.length === 0) {
|
|
2279
|
-
throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
|
|
2280
|
-
}
|
|
2281
|
-
const last = path[path.length - 1];
|
|
2282
|
-
return path.slice(0, -1).concat(last - 1);
|
|
2283
|
-
},
|
|
2284
|
-
/**
|
|
2285
|
-
* Check if a path is an ancestor of another.
|
|
2286
|
-
*/
|
|
2287
|
-
isAncestor(path, another) {
|
|
2288
|
-
return path.length < another.length && Path.compare(path, another) === 0;
|
|
2289
|
-
},
|
|
2290
|
-
/**
|
|
2291
|
-
* Compare a path to another, returning an integer indicating whether the path
|
|
2292
|
-
* was before, at, or after the other.
|
|
2293
|
-
*
|
|
2294
|
-
* Note: Two paths of unequal length can still receive a `0` result if one is
|
|
2295
|
-
* directly above or below the other. If you want exact matching, use
|
|
2296
|
-
* [[Path.equals]] instead.
|
|
2297
|
-
*/
|
|
2298
|
-
compare(path, another) {
|
|
2299
|
-
const min = Math.min(path.length, another.length);
|
|
2300
|
-
for (let i = 0; i < min; i++) {
|
|
2301
|
-
if (path[i] < another[i])
|
|
2302
|
-
return -1;
|
|
2303
|
-
if (path[i] > another[i])
|
|
2304
|
-
return 1;
|
|
2305
|
-
}
|
|
2306
|
-
return 0;
|
|
2307
|
-
},
|
|
2308
|
-
/**
|
|
2309
|
-
* Check if a path is exactly equal to another.
|
|
2310
|
-
*/
|
|
2311
|
-
equals(path, another) {
|
|
2312
|
-
return path.length === another.length && path.every((n, i) => n === another[i]);
|
|
2313
|
-
},
|
|
2314
|
-
/**
|
|
2315
|
-
* Check if a path ends before one of the indexes in another.
|
|
2316
|
-
*/
|
|
2317
|
-
endsBefore(path, another) {
|
|
2318
|
-
const i = path.length - 1;
|
|
2319
|
-
const as = path.slice(0, i);
|
|
2320
|
-
const bs = another.slice(0, i);
|
|
2321
|
-
const av = path[i];
|
|
2322
|
-
const bv = another[i];
|
|
2323
|
-
return Path.equals(as, bs) && av < bv;
|
|
2324
|
-
},
|
|
2325
|
-
/**
|
|
2326
|
-
* Check if a path is a sibling of another.
|
|
2327
|
-
*/
|
|
2328
|
-
isSibling(path, another) {
|
|
2329
|
-
if (path.length !== another.length) {
|
|
2330
|
-
return false;
|
|
2331
|
-
}
|
|
2332
|
-
const as = path.slice(0, -1);
|
|
2333
|
-
const bs = another.slice(0, -1);
|
|
2334
|
-
const al = path[path.length - 1];
|
|
2335
|
-
const bl = another[another.length - 1];
|
|
2336
|
-
return al !== bl && Path.equals(as, bs);
|
|
2337
|
-
},
|
|
2338
|
-
transform(path, operation) {
|
|
2339
|
-
return produce(path, p => {
|
|
2340
|
-
// PERF: Exit early if the operation is guaranteed not to have an effect.
|
|
2341
|
-
if (!path || path?.length === 0) {
|
|
2342
|
-
return;
|
|
2343
|
-
}
|
|
2344
|
-
if (p === null) {
|
|
2345
|
-
return null;
|
|
2346
|
-
}
|
|
2347
|
-
switch (operation.type) {
|
|
2348
|
-
case 'insert_node': {
|
|
2349
|
-
const { path: op } = operation;
|
|
2350
|
-
if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
|
|
2351
|
-
p[op.length - 1] += 1;
|
|
2352
|
-
}
|
|
2353
|
-
break;
|
|
2354
|
-
}
|
|
2355
|
-
case 'remove_node': {
|
|
2356
|
-
const { path: op } = operation;
|
|
2357
|
-
if (Path.equals(op, p) || Path.isAncestor(op, p)) {
|
|
2358
|
-
return null;
|
|
2359
|
-
}
|
|
2360
|
-
else if (Path.endsBefore(op, p)) {
|
|
2361
|
-
p[op.length - 1] -= 1;
|
|
2362
|
-
}
|
|
2363
|
-
break;
|
|
2364
|
-
}
|
|
2365
|
-
case 'move_node': {
|
|
2366
|
-
const { path: op, newPath: onp } = operation;
|
|
2367
|
-
// If the old and new path are the same, it's a no-op.
|
|
2368
|
-
if (Path.equals(op, onp)) {
|
|
2369
|
-
return;
|
|
2370
|
-
}
|
|
2371
|
-
if (Path.isAncestor(op, p) || Path.equals(op, p)) {
|
|
2372
|
-
const copy = onp.slice();
|
|
2373
|
-
// op.length <= onp.length is different for slate
|
|
2374
|
-
// resolve drag from [0, 0] to [0, 3] issue
|
|
2375
|
-
if (Path.endsBefore(op, onp) && op.length <= onp.length) {
|
|
2376
|
-
copy[op.length - 1] -= 1;
|
|
2377
|
-
}
|
|
2378
|
-
return copy.concat(p.slice(op.length));
|
|
2379
|
-
}
|
|
2380
|
-
else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
|
|
2381
|
-
if (Path.endsBefore(op, p)) {
|
|
2382
|
-
p[op.length - 1] -= 1;
|
|
2383
|
-
}
|
|
2384
|
-
else {
|
|
2385
|
-
p[op.length - 1] += 1;
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
|
|
2389
|
-
if (Path.endsBefore(op, p)) {
|
|
2390
|
-
p[op.length - 1] -= 1;
|
|
2391
|
-
}
|
|
2392
|
-
p[onp.length - 1] += 1;
|
|
2393
|
-
}
|
|
2394
|
-
else if (Path.endsBefore(op, p)) {
|
|
2395
|
-
if (Path.equals(onp, p)) {
|
|
2396
|
-
p[onp.length - 1] += 1;
|
|
2397
|
-
}
|
|
2398
|
-
p[op.length - 1] -= 1;
|
|
2399
|
-
}
|
|
2400
|
-
break;
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
return p;
|
|
2404
|
-
});
|
|
2800
|
+
};
|
|
2801
|
+
const setClipboardData = async (dataTransfer, clipboardContext) => {
|
|
2802
|
+
if (!clipboardContext) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
const { type, elements, text } = clipboardContext;
|
|
2806
|
+
if (getProbablySupportsClipboardWrite()) {
|
|
2807
|
+
return await setNavigatorClipboard(type, elements, text);
|
|
2808
|
+
}
|
|
2809
|
+
if (dataTransfer) {
|
|
2810
|
+
setDataTransferClipboard(dataTransfer, type, elements);
|
|
2811
|
+
setDataTransferClipboardText(dataTransfer, text);
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
// Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
|
|
2815
|
+
// Such as contextmenu copy in Firefox.
|
|
2816
|
+
if (getProbablySupportsClipboardWriteText()) {
|
|
2817
|
+
return await navigator.clipboard.writeText(buildPlaitHtml(type, elements));
|
|
2405
2818
|
}
|
|
2406
2819
|
};
|
|
2407
2820
|
|
|
2408
|
-
const
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
*/
|
|
2420
|
-
*parents(root, path, options = {}) {
|
|
2421
|
-
for (const p of Path.ancestors(path, options)) {
|
|
2422
|
-
const n = PlaitNode.get(root, p);
|
|
2423
|
-
yield n;
|
|
2821
|
+
const BOARD_TO_TOUCH_REF = new WeakMap();
|
|
2822
|
+
const isPreventTouchMove = (board) => {
|
|
2823
|
+
return !!BOARD_TO_TOUCH_REF.get(board);
|
|
2824
|
+
};
|
|
2825
|
+
const preventTouchMove = (board, event, state) => {
|
|
2826
|
+
const hostElement = PlaitBoard.getElementHost(board);
|
|
2827
|
+
const activeHostElement = PlaitBoard.getElementActiveHost(board);
|
|
2828
|
+
if (state) {
|
|
2829
|
+
if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) &&
|
|
2830
|
+
(hostElement.contains(event.target) || activeHostElement.contains(event.target))) {
|
|
2831
|
+
BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
|
|
2424
2832
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
let node = root;
|
|
2428
|
-
for (let i = 0; i < path.length; i++) {
|
|
2429
|
-
const p = path[i];
|
|
2430
|
-
if (!node || !node.children || !node.children[p]) {
|
|
2431
|
-
throw new Error(`Cannot find a descendant at path [${path}]`);
|
|
2432
|
-
}
|
|
2433
|
-
node = node.children[p];
|
|
2833
|
+
else {
|
|
2834
|
+
BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
|
|
2434
2835
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
n = n.children[i];
|
|
2836
|
+
}
|
|
2837
|
+
else {
|
|
2838
|
+
const ref = BOARD_TO_TOUCH_REF.get(board);
|
|
2839
|
+
if (ref) {
|
|
2840
|
+
BOARD_TO_TOUCH_REF.delete(board);
|
|
2841
|
+
ref.host?.remove();
|
|
2442
2842
|
}
|
|
2443
|
-
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
/**
|
|
2846
|
+
* some intersection maybe cause target is removed from current browser window,
|
|
2847
|
+
* after it was removed touch move event will not be fired
|
|
2848
|
+
* so scroll behavior will can not be prevented in mobile browser device
|
|
2849
|
+
* this function will prevent target element being remove.
|
|
2850
|
+
*/
|
|
2851
|
+
const handleTouchTarget = (board) => {
|
|
2852
|
+
const touchRef = BOARD_TO_TOUCH_REF.get(board);
|
|
2853
|
+
if (touchRef &&
|
|
2854
|
+
touchRef.target &&
|
|
2855
|
+
!PlaitBoard.getElementHost(board).contains(touchRef.target) &&
|
|
2856
|
+
!PlaitBoard.getElementActiveHost(board).contains(touchRef.target)) {
|
|
2857
|
+
touchRef.target.style.opacity = '0';
|
|
2858
|
+
const host = createG();
|
|
2859
|
+
host.appendChild(touchRef.target);
|
|
2860
|
+
touchRef.host = host;
|
|
2861
|
+
host.classList.add('touch-target');
|
|
2862
|
+
PlaitBoard.getElementActiveHost(board).append(host);
|
|
2863
|
+
}
|
|
2864
|
+
};
|
|
2865
|
+
|
|
2866
|
+
const Viewport = {
|
|
2867
|
+
isViewport: (value) => {
|
|
2868
|
+
return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
|
|
2444
2869
|
}
|
|
2445
2870
|
};
|
|
2446
2871
|
|
|
@@ -2586,40 +3011,65 @@ const GeneralTransforms = {
|
|
|
2586
3011
|
}
|
|
2587
3012
|
};
|
|
2588
3013
|
|
|
2589
|
-
|
|
2590
|
-
const
|
|
2591
|
-
board
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
3014
|
+
const addGroup = (board, elements) => {
|
|
3015
|
+
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3016
|
+
const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
|
|
3017
|
+
const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
|
|
3018
|
+
const group = createGroup();
|
|
3019
|
+
if (canAddGroup(board)) {
|
|
3020
|
+
highestSelectedElements.forEach(item => {
|
|
3021
|
+
const path = PlaitBoard.findPath(board, item);
|
|
3022
|
+
NodeTransforms.setNode(board, { groupId: group.id }, path);
|
|
3023
|
+
});
|
|
3024
|
+
const selectedElements = getSelectedElements(board);
|
|
3025
|
+
const highestIndexOfSelectedElement = getHighestIndexOfElement(board, selectedElements);
|
|
3026
|
+
const indices = getElementsIndices(board, highestSelectedElements);
|
|
3027
|
+
const isContinuous = isIndicesContinuous(indices);
|
|
3028
|
+
if (!isContinuous) {
|
|
3029
|
+
moveElementsToNewPathAfterAddGroup(board, selectedElements, [highestIndexOfSelectedElement - 1]);
|
|
3030
|
+
}
|
|
3031
|
+
if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
|
|
3032
|
+
const newGroupId = selectedIsolatedElements[0].groupId;
|
|
3033
|
+
NodeTransforms.insertNode(board, {
|
|
3034
|
+
...group,
|
|
3035
|
+
groupId: newGroupId
|
|
3036
|
+
}, [board.children.length]);
|
|
3037
|
+
}
|
|
3038
|
+
else {
|
|
3039
|
+
NodeTransforms.insertNode(board, group, [board.children.length]);
|
|
2604
3040
|
}
|
|
2605
3041
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
3042
|
+
};
|
|
3043
|
+
const removeGroup = (board, elements) => {
|
|
3044
|
+
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3045
|
+
if (canRemoveGroup(board)) {
|
|
3046
|
+
selectedGroups.forEach(group => {
|
|
3047
|
+
const elementsInGroup = findElements(board, {
|
|
3048
|
+
match: item => item.groupId === group.id,
|
|
3049
|
+
recursion: () => false
|
|
3050
|
+
});
|
|
3051
|
+
elementsInGroup.forEach(element => {
|
|
3052
|
+
const path = PlaitBoard.findPath(board, element);
|
|
3053
|
+
NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
|
|
3054
|
+
});
|
|
3055
|
+
});
|
|
3056
|
+
selectedGroups
|
|
3057
|
+
.map(group => {
|
|
3058
|
+
const groupPath = PlaitBoard.findPath(board, group);
|
|
3059
|
+
const groupRef = board.pathRef(groupPath);
|
|
3060
|
+
return () => {
|
|
3061
|
+
groupRef.current && NodeTransforms.removeNode(board, groupRef.current);
|
|
3062
|
+
groupRef.unref();
|
|
3063
|
+
};
|
|
3064
|
+
})
|
|
3065
|
+
.forEach(action => {
|
|
3066
|
+
action();
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3070
|
+
const GroupTransforms = {
|
|
3071
|
+
addGroup,
|
|
3072
|
+
removeGroup
|
|
2623
3073
|
};
|
|
2624
3074
|
|
|
2625
3075
|
function setSelection(board, selection) {
|
|
@@ -2646,6 +3096,184 @@ function addSelectionWithTemporaryElements(board, elements) {
|
|
|
2646
3096
|
}
|
|
2647
3097
|
}
|
|
2648
3098
|
|
|
3099
|
+
const getOneMoveOptions = (board, direction) => {
|
|
3100
|
+
const indicesToMove = getElementsIndices(board, getSelectedElements(board));
|
|
3101
|
+
let groupedIndices = toContiguousGroups(board, indicesToMove);
|
|
3102
|
+
if (direction === 'up') {
|
|
3103
|
+
groupedIndices = groupedIndices.reverse();
|
|
3104
|
+
}
|
|
3105
|
+
let moveContents = [];
|
|
3106
|
+
groupedIndices.forEach((indices, i) => {
|
|
3107
|
+
const leadingIndex = indices[0];
|
|
3108
|
+
const trailingIndex = indices[indices.length - 1];
|
|
3109
|
+
const boundaryIndex = direction === 'down' ? leadingIndex : trailingIndex;
|
|
3110
|
+
const targetIndex = getTargetIndex(board, boundaryIndex, direction);
|
|
3111
|
+
if (targetIndex === -1 || boundaryIndex === targetIndex) {
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
if (direction === 'down') {
|
|
3115
|
+
indices = indices.reverse();
|
|
3116
|
+
}
|
|
3117
|
+
moveContents.push(...indices.map(path => {
|
|
3118
|
+
return {
|
|
3119
|
+
element: board.children[path],
|
|
3120
|
+
newPath: [targetIndex]
|
|
3121
|
+
};
|
|
3122
|
+
}));
|
|
3123
|
+
});
|
|
3124
|
+
return moveContents;
|
|
3125
|
+
};
|
|
3126
|
+
const getAllMoveOptions = (board, direction) => {
|
|
3127
|
+
const indicesToMove = getElementsIndices(board, getSelectedElements(board));
|
|
3128
|
+
let groupedIndices = toContiguousGroups(board, indicesToMove);
|
|
3129
|
+
let moveContents = [];
|
|
3130
|
+
if (direction === 'down') {
|
|
3131
|
+
groupedIndices = groupedIndices.reverse();
|
|
3132
|
+
}
|
|
3133
|
+
groupedIndices.forEach(indices => {
|
|
3134
|
+
const leadingIndex = indices[0];
|
|
3135
|
+
const trailingIndex = indices[indices.length - 1];
|
|
3136
|
+
const boundaryIndex = direction === 'down' ? leadingIndex : trailingIndex;
|
|
3137
|
+
const sourceElement = board.children[boundaryIndex];
|
|
3138
|
+
const editingGroup = getEditingGroup(board, sourceElement);
|
|
3139
|
+
let targetIndex = direction === 'down' ? 0 : board.children.length - 1;
|
|
3140
|
+
if (editingGroup) {
|
|
3141
|
+
const elementsInGroup = sortElements(board, getElementsInGroup(board, editingGroup, true, true));
|
|
3142
|
+
targetIndex =
|
|
3143
|
+
direction === 'down'
|
|
3144
|
+
? board.children.indexOf(elementsInGroup[0])
|
|
3145
|
+
: board.children.indexOf(elementsInGroup[elementsInGroup.length - 1]);
|
|
3146
|
+
}
|
|
3147
|
+
if (direction === 'down') {
|
|
3148
|
+
indices = indices.reverse();
|
|
3149
|
+
}
|
|
3150
|
+
moveContents.push(...indices.map(path => {
|
|
3151
|
+
return {
|
|
3152
|
+
element: board.children[path],
|
|
3153
|
+
newPath: [targetIndex]
|
|
3154
|
+
};
|
|
3155
|
+
}));
|
|
3156
|
+
});
|
|
3157
|
+
return moveContents;
|
|
3158
|
+
};
|
|
3159
|
+
const canSetZIndex = (board) => {
|
|
3160
|
+
const selectedElements = getSelectedElements(board).filter(item => board.canSetZIndex(item));
|
|
3161
|
+
return selectedElements.length > 0;
|
|
3162
|
+
};
|
|
3163
|
+
const toContiguousGroups = (board, array) => {
|
|
3164
|
+
let cursor = 0;
|
|
3165
|
+
return array.reduce((acc, value, index) => {
|
|
3166
|
+
if (index > 0) {
|
|
3167
|
+
const currentElement = board.children[value];
|
|
3168
|
+
const previousElement = board.children[array[index - 1]];
|
|
3169
|
+
const isContiguous = value - 1 === array[index - 1]
|
|
3170
|
+
? true
|
|
3171
|
+
: board.children.every((item, childIndex) => {
|
|
3172
|
+
if (childIndex > array[index - 1] && childIndex <= value - 1) {
|
|
3173
|
+
return PlaitGroupElement.isGroup(item);
|
|
3174
|
+
}
|
|
3175
|
+
return true;
|
|
3176
|
+
});
|
|
3177
|
+
let isPartialSelectGroupElement = false;
|
|
3178
|
+
if (previousElement?.groupId || (currentElement?.groupId && previousElement?.groupId !== currentElement?.groupId)) {
|
|
3179
|
+
let isPartialSelectPreviousGroup = false;
|
|
3180
|
+
let isPartialSelectCurrentElement = false;
|
|
3181
|
+
if (previousElement.groupId) {
|
|
3182
|
+
const highestGroup = getHighestGroup(board, previousElement);
|
|
3183
|
+
isPartialSelectPreviousGroup = !isSelectedAllElementsInGroup(board, highestGroup);
|
|
3184
|
+
}
|
|
3185
|
+
if (currentElement.groupId) {
|
|
3186
|
+
const highestGroup = getHighestGroup(board, currentElement);
|
|
3187
|
+
isPartialSelectCurrentElement = !isSelectedAllElementsInGroup(board, highestGroup);
|
|
3188
|
+
}
|
|
3189
|
+
isPartialSelectGroupElement = isPartialSelectPreviousGroup || isPartialSelectCurrentElement;
|
|
3190
|
+
}
|
|
3191
|
+
if (!isContiguous || isPartialSelectGroupElement) {
|
|
3192
|
+
cursor = ++cursor;
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
(acc[cursor] || (acc[cursor] = [])).push(value);
|
|
3196
|
+
return acc;
|
|
3197
|
+
}, []);
|
|
3198
|
+
};
|
|
3199
|
+
/**
|
|
3200
|
+
* Returns next candidate index that's available to be moved to. Currently that
|
|
3201
|
+
* is a non-deleted element, and not inside a group (unless we're editing it).
|
|
3202
|
+
*/
|
|
3203
|
+
const getTargetIndex = (board, boundaryIndex, direction) => {
|
|
3204
|
+
if ((boundaryIndex === 0 && direction === 'down') || (boundaryIndex === board.children.length - 1 && direction === 'up')) {
|
|
3205
|
+
return -1;
|
|
3206
|
+
}
|
|
3207
|
+
const indexFilter = (element) => {
|
|
3208
|
+
if (element.isDeleted || PlaitGroupElement.isGroup(element)) {
|
|
3209
|
+
return false;
|
|
3210
|
+
}
|
|
3211
|
+
return true;
|
|
3212
|
+
};
|
|
3213
|
+
const candidateIndex = direction === 'down'
|
|
3214
|
+
? findLastIndex(board.children, el => indexFilter(el), Math.max(0, boundaryIndex - 1))
|
|
3215
|
+
: findIndex(board.children, el => indexFilter(el), boundaryIndex + 1);
|
|
3216
|
+
const nextElement = board.children[candidateIndex];
|
|
3217
|
+
if (!nextElement) {
|
|
3218
|
+
return -1;
|
|
3219
|
+
}
|
|
3220
|
+
const elements = [...board.children];
|
|
3221
|
+
const sourceElement = elements[boundaryIndex];
|
|
3222
|
+
const editingGroup = getEditingGroup(board, sourceElement);
|
|
3223
|
+
const nextElementGroups = (getGroupByElement(board, nextElement, true) || []);
|
|
3224
|
+
// candidate element is a sibling in current editing group → return
|
|
3225
|
+
if (editingGroup && sourceElement?.groupId !== nextElement?.groupId) {
|
|
3226
|
+
// candidate element is outside current editing group → prevent
|
|
3227
|
+
if (!nextElementGroups.find(item => item.id === editingGroup.id)) {
|
|
3228
|
+
return -1;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
if (!nextElement.groupId) {
|
|
3232
|
+
return candidateIndex;
|
|
3233
|
+
}
|
|
3234
|
+
let siblingGroup;
|
|
3235
|
+
if (editingGroup) {
|
|
3236
|
+
siblingGroup = nextElementGroups[nextElementGroups.indexOf(editingGroup) - 1];
|
|
3237
|
+
}
|
|
3238
|
+
else {
|
|
3239
|
+
siblingGroup = nextElementGroups[nextElementGroups.length - 1];
|
|
3240
|
+
}
|
|
3241
|
+
if (siblingGroup) {
|
|
3242
|
+
let elementsInSiblingGroup = getElementsInGroup(board, siblingGroup, true, false);
|
|
3243
|
+
if (elementsInSiblingGroup.length) {
|
|
3244
|
+
elementsInSiblingGroup.sort((a, b) => {
|
|
3245
|
+
const indexA = board.children.findIndex(child => child.id === a.id);
|
|
3246
|
+
const indexB = board.children.findIndex(child => child.id === b.id);
|
|
3247
|
+
return indexA - indexB;
|
|
3248
|
+
});
|
|
3249
|
+
// assumes getElementsInGroup() returned elements are sorted
|
|
3250
|
+
// by zIndex (ascending)
|
|
3251
|
+
return direction === 'down'
|
|
3252
|
+
? elements.indexOf(elementsInSiblingGroup[0])
|
|
3253
|
+
: elements.indexOf(elementsInSiblingGroup[elementsInSiblingGroup.length - 1]);
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
return candidateIndex;
|
|
3257
|
+
};
|
|
3258
|
+
|
|
3259
|
+
const moveToTop = (board) => {
|
|
3260
|
+
const moveOptions = getAllMoveOptions(board, 'up');
|
|
3261
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3262
|
+
};
|
|
3263
|
+
const moveToBottom = (board) => {
|
|
3264
|
+
const moveOptions = getAllMoveOptions(board, 'down');
|
|
3265
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3266
|
+
};
|
|
3267
|
+
const moveUp = (board) => {
|
|
3268
|
+
const moveOptions = getOneMoveOptions(board, 'up');
|
|
3269
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3270
|
+
};
|
|
3271
|
+
const moveDown = (board) => {
|
|
3272
|
+
const moveOptions = getOneMoveOptions(board, 'down');
|
|
3273
|
+
moveElementsToNewPath(board, moveOptions);
|
|
3274
|
+
};
|
|
3275
|
+
const ZIndexTransforms = { moveUp, moveDown, moveToTop, moveToBottom };
|
|
3276
|
+
|
|
2649
3277
|
const removeElements = (board, elements) => {
|
|
2650
3278
|
elements
|
|
2651
3279
|
.map(element => {
|
|
@@ -2665,55 +3293,13 @@ const CoreTransforms = {
|
|
|
2665
3293
|
removeElements
|
|
2666
3294
|
};
|
|
2667
3295
|
|
|
2668
|
-
const addGroup = (board, elements) => {
|
|
2669
|
-
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
2670
|
-
const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
|
|
2671
|
-
const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
|
|
2672
|
-
const group = createGroup();
|
|
2673
|
-
if (canAddGroup(board)) {
|
|
2674
|
-
highestSelectedElements.forEach(item => {
|
|
2675
|
-
const path = PlaitBoard.findPath(board, item);
|
|
2676
|
-
NodeTransforms.setNode(board, { groupId: group.id }, path);
|
|
2677
|
-
});
|
|
2678
|
-
if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
|
|
2679
|
-
const newGroupId = selectedIsolatedElements[0].groupId;
|
|
2680
|
-
NodeTransforms.insertNode(board, {
|
|
2681
|
-
...group,
|
|
2682
|
-
groupId: newGroupId
|
|
2683
|
-
}, [board.children.length]);
|
|
2684
|
-
}
|
|
2685
|
-
else {
|
|
2686
|
-
NodeTransforms.insertNode(board, group, [board.children.length]);
|
|
2687
|
-
}
|
|
2688
|
-
}
|
|
2689
|
-
};
|
|
2690
|
-
const removeGroup = (board, elements) => {
|
|
2691
|
-
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
2692
|
-
if (canRemoveGroup(board)) {
|
|
2693
|
-
selectedGroups.map(group => {
|
|
2694
|
-
const elementsInGroup = findElements(board, {
|
|
2695
|
-
match: item => item.groupId === group.id,
|
|
2696
|
-
recursion: () => false
|
|
2697
|
-
});
|
|
2698
|
-
elementsInGroup.forEach(item => {
|
|
2699
|
-
const path = PlaitBoard.findPath(board, item);
|
|
2700
|
-
NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
|
|
2701
|
-
});
|
|
2702
|
-
const groupPath = PlaitBoard.findPath(board, group);
|
|
2703
|
-
NodeTransforms.removeNode(board, groupPath);
|
|
2704
|
-
});
|
|
2705
|
-
}
|
|
2706
|
-
};
|
|
2707
|
-
const GroupTransforms = {
|
|
2708
|
-
addGroup,
|
|
2709
|
-
removeGroup
|
|
2710
|
-
};
|
|
2711
|
-
|
|
2712
3296
|
const Transforms = {
|
|
2713
3297
|
...GeneralTransforms,
|
|
2714
3298
|
...ViewportTransforms$1,
|
|
2715
3299
|
...SelectionTransforms,
|
|
2716
|
-
...NodeTransforms
|
|
3300
|
+
...NodeTransforms,
|
|
3301
|
+
...GroupTransforms,
|
|
3302
|
+
...ZIndexTransforms
|
|
2717
3303
|
};
|
|
2718
3304
|
|
|
2719
3305
|
const rotatePoints = (points, centerPoint, angle) => {
|
|
@@ -2820,11 +3406,25 @@ function rotateElements(board, elements, angle) {
|
|
|
2820
3406
|
const selectionCenterPoint = RectangleClient.getCenterPoint(selectionRectangle);
|
|
2821
3407
|
elements.forEach(item => {
|
|
2822
3408
|
const originAngle = item.angle;
|
|
2823
|
-
const points = rotatedDataPoints(item.points, selectionCenterPoint, angle);
|
|
3409
|
+
const points = rotatedDataPoints(item.points, selectionCenterPoint, normalizeAngle(angle));
|
|
2824
3410
|
const path = PlaitBoard.findPath(board, item);
|
|
2825
|
-
Transforms.setNode(board, { points, angle: originAngle + angle }, path);
|
|
3411
|
+
Transforms.setNode(board, { points, angle: normalizeAngle(originAngle + angle) }, path);
|
|
2826
3412
|
});
|
|
2827
3413
|
}
|
|
3414
|
+
const normalizeAngle = (angle) => {
|
|
3415
|
+
if (angle < 0) {
|
|
3416
|
+
return angle + 2 * Math.PI;
|
|
3417
|
+
}
|
|
3418
|
+
if (angle >= 2 * Math.PI) {
|
|
3419
|
+
return angle - 2 * Math.PI;
|
|
3420
|
+
}
|
|
3421
|
+
return angle;
|
|
3422
|
+
};
|
|
3423
|
+
const getAngleBetweenPoints = (startPoint, endPoint, centerPoint) => {
|
|
3424
|
+
const startAngle = (5 * Math.PI) / 2 + Math.atan2(startPoint[1] - centerPoint[1], startPoint[0] - centerPoint[0]);
|
|
3425
|
+
const endAngle = (5 * Math.PI) / 2 + Math.atan2(endPoint[1] - centerPoint[1], endPoint[0] - centerPoint[0]);
|
|
3426
|
+
return normalizeAngle(endAngle - startAngle);
|
|
3427
|
+
};
|
|
2828
3428
|
|
|
2829
3429
|
function isSelectionMoving(board) {
|
|
2830
3430
|
return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
|
|
@@ -3103,12 +3703,15 @@ const getHighestSelectedElements = (board, elements) => {
|
|
|
3103
3703
|
return [...getHighestSelectedGroups(board, elements), ...getSelectedIsolatedElements(board, elements)];
|
|
3104
3704
|
};
|
|
3105
3705
|
const createGroupRectangleG = (board, elements) => {
|
|
3106
|
-
const
|
|
3107
|
-
|
|
3706
|
+
const selectedElementIds = getSelectedElements(board).map(item => item.id);
|
|
3707
|
+
let groupRectangleG = null;
|
|
3108
3708
|
const isMoving = isSelectionMoving(board);
|
|
3109
3709
|
elements.forEach(item => {
|
|
3110
|
-
const isRender = (!
|
|
3710
|
+
const isRender = (!selectedElementIds.includes(item.id) && !isMoving) || isMoving;
|
|
3111
3711
|
if (item.groupId && isRender) {
|
|
3712
|
+
if (!groupRectangleG) {
|
|
3713
|
+
groupRectangleG = createG();
|
|
3714
|
+
}
|
|
3112
3715
|
const elements = getElementsInGroupByElement(board, item);
|
|
3113
3716
|
const rectangle = getRectangleByElements(board, elements, false);
|
|
3114
3717
|
const rectangleG = drawRectangle(board, rectangle, {
|
|
@@ -3151,16 +3754,262 @@ const canAddGroup = (board, elements) => {
|
|
|
3151
3754
|
}
|
|
3152
3755
|
return false;
|
|
3153
3756
|
};
|
|
3154
|
-
const canRemoveGroup = (board, elements) => {
|
|
3155
|
-
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3156
|
-
const selectedElements = elements || getSelectedElements(board);
|
|
3157
|
-
return selectedElements.length > 0 && selectedGroups.length > 0;
|
|
3757
|
+
const canRemoveGroup = (board, elements) => {
|
|
3758
|
+
const selectedGroups = getHighestSelectedGroups(board, elements);
|
|
3759
|
+
const selectedElements = elements || getSelectedElements(board);
|
|
3760
|
+
return selectedElements.length > 0 && selectedGroups.length > 0;
|
|
3761
|
+
};
|
|
3762
|
+
const getEditingGroup = (board, element) => {
|
|
3763
|
+
const groups = getGroupByElement(board, element, true);
|
|
3764
|
+
let editingGroup = null;
|
|
3765
|
+
if (groups?.length) {
|
|
3766
|
+
for (let i = 0; i < groups?.length; i++) {
|
|
3767
|
+
if (!isSelectedAllElementsInGroup(board, groups[i])) {
|
|
3768
|
+
editingGroup = groups[i];
|
|
3769
|
+
break;
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
return editingGroup;
|
|
3774
|
+
};
|
|
3775
|
+
const moveElementsToNewPathAfterAddGroup = (board, selectedElements, newPath) => {
|
|
3776
|
+
const moveElements = [...selectedElements];
|
|
3777
|
+
sortElements(board, moveElements);
|
|
3778
|
+
moveElements.pop();
|
|
3779
|
+
moveElementsToNewPath(board, moveElements.map(element => {
|
|
3780
|
+
return {
|
|
3781
|
+
element,
|
|
3782
|
+
newPath
|
|
3783
|
+
};
|
|
3784
|
+
}));
|
|
3158
3785
|
};
|
|
3159
3786
|
|
|
3160
3787
|
const deleteFragment = (board) => {
|
|
3161
3788
|
const elements = board.getDeletedFragment([]);
|
|
3162
3789
|
board.deleteFragment(elements);
|
|
3163
3790
|
};
|
|
3791
|
+
const setFragment = (board, type, clipboardData) => {
|
|
3792
|
+
const selectedElements = getSelectedElements(board);
|
|
3793
|
+
const rectangle = getRectangleByElements(board, selectedElements, false);
|
|
3794
|
+
const clipboardContext = board.buildFragment(null, rectangle, type);
|
|
3795
|
+
clipboardContext && setClipboardData(clipboardData, clipboardContext);
|
|
3796
|
+
};
|
|
3797
|
+
const duplicateElements = (board, elements) => {
|
|
3798
|
+
const selectedElements = elements || getSelectedElements(board);
|
|
3799
|
+
const rectangle = getRectangleByElements(board, selectedElements, false);
|
|
3800
|
+
const clipboardContext = board.buildFragment(null, rectangle, 'copy');
|
|
3801
|
+
const stringifiedContext = clipboardContext && JSON.stringify(clipboardContext);
|
|
3802
|
+
const clonedContext = stringifiedContext && JSON.parse(stringifiedContext);
|
|
3803
|
+
clonedContext &&
|
|
3804
|
+
board.insertFragment({
|
|
3805
|
+
...clonedContext,
|
|
3806
|
+
text: undefined
|
|
3807
|
+
}, [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]);
|
|
3808
|
+
};
|
|
3809
|
+
|
|
3810
|
+
const SNAP_TOLERANCE = 2;
|
|
3811
|
+
const SNAP_SPACING = 24;
|
|
3812
|
+
function getSnapRectangles(board, activeElements) {
|
|
3813
|
+
const elements = findElements(board, {
|
|
3814
|
+
match: element => board.isAlign(element) && !activeElements.some(item => item.id === element.id),
|
|
3815
|
+
recursion: () => true,
|
|
3816
|
+
isReverse: false
|
|
3817
|
+
});
|
|
3818
|
+
return elements.map(item => getRectangleByAngle(board.getRectangle(item), item.angle) || board.getRectangle(item));
|
|
3819
|
+
}
|
|
3820
|
+
function getBarPoint(point, isHorizontal) {
|
|
3821
|
+
return isHorizontal
|
|
3822
|
+
? [
|
|
3823
|
+
[point[0], point[1] - 4],
|
|
3824
|
+
[point[0], point[1] + 4]
|
|
3825
|
+
]
|
|
3826
|
+
: [
|
|
3827
|
+
[point[0] - 4, point[1]],
|
|
3828
|
+
[point[0] + 4, point[1]]
|
|
3829
|
+
];
|
|
3830
|
+
}
|
|
3831
|
+
function getMinPointDelta(pointRectangles, axis, isHorizontal) {
|
|
3832
|
+
let delta = SNAP_TOLERANCE;
|
|
3833
|
+
pointRectangles.forEach(item => {
|
|
3834
|
+
const distance = getNearestDelta(axis, item, isHorizontal);
|
|
3835
|
+
if (Math.abs(distance) < Math.abs(delta)) {
|
|
3836
|
+
delta = distance;
|
|
3837
|
+
}
|
|
3838
|
+
});
|
|
3839
|
+
return delta;
|
|
3840
|
+
}
|
|
3841
|
+
const getNearestDelta = (axis, rectangle, isHorizontal) => {
|
|
3842
|
+
const pointAxis = getTripleAxis(rectangle, isHorizontal);
|
|
3843
|
+
const deltas = pointAxis.map(item => item - axis);
|
|
3844
|
+
const absDeltas = deltas.map(item => Math.abs(item));
|
|
3845
|
+
const index = absDeltas.indexOf(Math.min(...absDeltas));
|
|
3846
|
+
return deltas[index];
|
|
3847
|
+
};
|
|
3848
|
+
const getTripleAxis = (rectangle, isHorizontal) => {
|
|
3849
|
+
const axis = isHorizontal ? 'x' : 'y';
|
|
3850
|
+
const side = isHorizontal ? 'width' : 'height';
|
|
3851
|
+
return [rectangle[axis], rectangle[axis] + rectangle[side] / 2, rectangle[axis] + rectangle[side]];
|
|
3852
|
+
};
|
|
3853
|
+
function getNearestPointRectangle(snapRectangles, activeRectangle) {
|
|
3854
|
+
let minDistance = Infinity;
|
|
3855
|
+
let nearestRectangle = snapRectangles[0];
|
|
3856
|
+
snapRectangles.forEach(item => {
|
|
3857
|
+
const distance = Math.sqrt(Math.pow(activeRectangle.x - item.x, 2) + Math.pow(activeRectangle.y - item.y, 2));
|
|
3858
|
+
if (distance < minDistance) {
|
|
3859
|
+
minDistance = distance;
|
|
3860
|
+
nearestRectangle = item;
|
|
3861
|
+
}
|
|
3862
|
+
});
|
|
3863
|
+
return nearestRectangle;
|
|
3864
|
+
}
|
|
3865
|
+
const isSnapPoint = (axis, rectangle, isHorizontal) => {
|
|
3866
|
+
const pointAxis = getTripleAxis(rectangle, isHorizontal);
|
|
3867
|
+
return pointAxis.includes(axis);
|
|
3868
|
+
};
|
|
3869
|
+
function drawPointSnapLines(board, activeRectangle, snapRectangles, drawHorizontal = true, drawVertical = true, snapMiddle = false) {
|
|
3870
|
+
let pointLinePoints = [];
|
|
3871
|
+
const pointAxisX = getTripleAxis(activeRectangle, true);
|
|
3872
|
+
const pointAxisY = getTripleAxis(activeRectangle, false);
|
|
3873
|
+
const pointLineRefs = [
|
|
3874
|
+
{
|
|
3875
|
+
axis: pointAxisX[0],
|
|
3876
|
+
isHorizontal: true,
|
|
3877
|
+
pointRectangles: []
|
|
3878
|
+
},
|
|
3879
|
+
{
|
|
3880
|
+
axis: pointAxisX[1],
|
|
3881
|
+
isHorizontal: true,
|
|
3882
|
+
pointRectangles: []
|
|
3883
|
+
},
|
|
3884
|
+
{
|
|
3885
|
+
axis: pointAxisX[2],
|
|
3886
|
+
isHorizontal: true,
|
|
3887
|
+
pointRectangles: []
|
|
3888
|
+
},
|
|
3889
|
+
{
|
|
3890
|
+
axis: pointAxisY[0],
|
|
3891
|
+
isHorizontal: false,
|
|
3892
|
+
pointRectangles: []
|
|
3893
|
+
},
|
|
3894
|
+
{
|
|
3895
|
+
axis: pointAxisY[1],
|
|
3896
|
+
isHorizontal: false,
|
|
3897
|
+
pointRectangles: []
|
|
3898
|
+
},
|
|
3899
|
+
{
|
|
3900
|
+
axis: pointAxisY[2],
|
|
3901
|
+
isHorizontal: false,
|
|
3902
|
+
pointRectangles: []
|
|
3903
|
+
}
|
|
3904
|
+
];
|
|
3905
|
+
for (let index = 0; index < snapRectangles.length; index++) {
|
|
3906
|
+
const element = snapRectangles[index];
|
|
3907
|
+
if (isSnapPoint(pointLineRefs[0].axis, element, pointLineRefs[0].isHorizontal)) {
|
|
3908
|
+
pointLineRefs[0].pointRectangles.push(element);
|
|
3909
|
+
}
|
|
3910
|
+
if (isSnapPoint(pointLineRefs[1].axis, element, pointLineRefs[1].isHorizontal)) {
|
|
3911
|
+
pointLineRefs[1].pointRectangles.push(element);
|
|
3912
|
+
}
|
|
3913
|
+
if (isSnapPoint(pointLineRefs[2].axis, element, pointLineRefs[2].isHorizontal)) {
|
|
3914
|
+
pointLineRefs[2].pointRectangles.push(element);
|
|
3915
|
+
}
|
|
3916
|
+
if (isSnapPoint(pointLineRefs[3].axis, element, pointLineRefs[3].isHorizontal)) {
|
|
3917
|
+
pointLineRefs[3].pointRectangles.push(element);
|
|
3918
|
+
}
|
|
3919
|
+
if (isSnapPoint(pointLineRefs[4].axis, element, pointLineRefs[4].isHorizontal)) {
|
|
3920
|
+
pointLineRefs[4].pointRectangles.push(element);
|
|
3921
|
+
}
|
|
3922
|
+
if (isSnapPoint(pointLineRefs[5].axis, element, pointLineRefs[5].isHorizontal)) {
|
|
3923
|
+
pointLineRefs[5].pointRectangles.push(element);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
const setResizePointSnapLine = (axis, pointRectangle, isHorizontal) => {
|
|
3927
|
+
const boundingRectangle = RectangleClient.inflate(RectangleClient.getBoundingRectangle([activeRectangle, pointRectangle]), SNAP_SPACING);
|
|
3928
|
+
if (isHorizontal) {
|
|
3929
|
+
const pointStart = [axis, boundingRectangle.y];
|
|
3930
|
+
const pointEnd = [axis, boundingRectangle.y + boundingRectangle.height];
|
|
3931
|
+
pointLinePoints.push([pointStart, pointEnd]);
|
|
3932
|
+
}
|
|
3933
|
+
else {
|
|
3934
|
+
const pointStart = [boundingRectangle.x, axis];
|
|
3935
|
+
const pointEnd = [boundingRectangle.x + boundingRectangle.width, axis];
|
|
3936
|
+
pointLinePoints.push([pointStart, pointEnd]);
|
|
3937
|
+
}
|
|
3938
|
+
};
|
|
3939
|
+
if (drawHorizontal && pointLineRefs[0].pointRectangles.length) {
|
|
3940
|
+
const leftRectangle = pointLineRefs[0].pointRectangles.length === 1
|
|
3941
|
+
? pointLineRefs[0].pointRectangles[0]
|
|
3942
|
+
: getNearestPointRectangle(pointLineRefs[0].pointRectangles, activeRectangle);
|
|
3943
|
+
setResizePointSnapLine(pointLineRefs[0].axis, leftRectangle, pointLineRefs[0].isHorizontal);
|
|
3944
|
+
}
|
|
3945
|
+
if (drawHorizontal && snapMiddle && pointLineRefs[1].pointRectangles.length) {
|
|
3946
|
+
const middleRectangle = pointLineRefs[1].pointRectangles.length === 1
|
|
3947
|
+
? pointLineRefs[1].pointRectangles[0]
|
|
3948
|
+
: getNearestPointRectangle(pointLineRefs[1].pointRectangles, activeRectangle);
|
|
3949
|
+
setResizePointSnapLine(pointLineRefs[1].axis, middleRectangle, pointLineRefs[1].isHorizontal);
|
|
3950
|
+
}
|
|
3951
|
+
if (drawHorizontal && pointLineRefs[2].pointRectangles.length) {
|
|
3952
|
+
const rightRectangle = pointLineRefs[2].pointRectangles.length === 1
|
|
3953
|
+
? pointLineRefs[2].pointRectangles[0]
|
|
3954
|
+
: getNearestPointRectangle(pointLineRefs[2].pointRectangles, activeRectangle);
|
|
3955
|
+
setResizePointSnapLine(pointLineRefs[2].axis, rightRectangle, pointLineRefs[2].isHorizontal);
|
|
3956
|
+
}
|
|
3957
|
+
if (drawVertical && pointLineRefs[3].pointRectangles.length) {
|
|
3958
|
+
const topRectangle = pointLineRefs[3].pointRectangles.length === 1
|
|
3959
|
+
? pointLineRefs[3].pointRectangles[0]
|
|
3960
|
+
: getNearestPointRectangle(pointLineRefs[3].pointRectangles, activeRectangle);
|
|
3961
|
+
setResizePointSnapLine(pointLineRefs[3].axis, topRectangle, pointLineRefs[3].isHorizontal);
|
|
3962
|
+
}
|
|
3963
|
+
if (drawVertical && snapMiddle && pointLineRefs[4].pointRectangles.length) {
|
|
3964
|
+
const middleRectangle = pointLineRefs[4].pointRectangles.length === 1
|
|
3965
|
+
? pointLineRefs[4].pointRectangles[0]
|
|
3966
|
+
: getNearestPointRectangle(pointLineRefs[4].pointRectangles, activeRectangle);
|
|
3967
|
+
setResizePointSnapLine(pointLineRefs[4].axis, middleRectangle, pointLineRefs[4].isHorizontal);
|
|
3968
|
+
}
|
|
3969
|
+
if (drawVertical && pointLineRefs[5].pointRectangles.length) {
|
|
3970
|
+
const rightRectangle = pointLineRefs[5].pointRectangles.length === 1
|
|
3971
|
+
? pointLineRefs[5].pointRectangles[0]
|
|
3972
|
+
: getNearestPointRectangle(pointLineRefs[5].pointRectangles, activeRectangle);
|
|
3973
|
+
setResizePointSnapLine(pointLineRefs[5].axis, rightRectangle, pointLineRefs[5].isHorizontal);
|
|
3974
|
+
}
|
|
3975
|
+
return drawDashedLines(board, pointLinePoints);
|
|
3976
|
+
}
|
|
3977
|
+
function drawDashedLines(board, lines) {
|
|
3978
|
+
const g = createG();
|
|
3979
|
+
lines.forEach(points => {
|
|
3980
|
+
if (!points.length)
|
|
3981
|
+
return;
|
|
3982
|
+
const line = PlaitBoard.getRoughSVG(board).line(points[0][0], points[0][1], points[1][0], points[1][1], {
|
|
3983
|
+
stroke: SELECTION_BORDER_COLOR,
|
|
3984
|
+
strokeWidth: 1,
|
|
3985
|
+
strokeLineDash: [4, 4]
|
|
3986
|
+
});
|
|
3987
|
+
g.appendChild(line);
|
|
3988
|
+
});
|
|
3989
|
+
return g;
|
|
3990
|
+
}
|
|
3991
|
+
function drawSolidLines(board, lines) {
|
|
3992
|
+
const g = createG();
|
|
3993
|
+
lines.forEach(points => {
|
|
3994
|
+
if (!points.length)
|
|
3995
|
+
return;
|
|
3996
|
+
let isHorizontal = points[0][1] === points[1][1];
|
|
3997
|
+
const line = PlaitBoard.getRoughSVG(board).line(points[0][0], points[0][1], points[1][0], points[1][1], {
|
|
3998
|
+
stroke: SELECTION_BORDER_COLOR,
|
|
3999
|
+
strokeWidth: 1
|
|
4000
|
+
});
|
|
4001
|
+
g.appendChild(line);
|
|
4002
|
+
points.forEach(point => {
|
|
4003
|
+
const barPoint = getBarPoint(point, isHorizontal);
|
|
4004
|
+
const bar = PlaitBoard.getRoughSVG(board).line(barPoint[0][0], barPoint[0][1], barPoint[1][0], barPoint[1][1], {
|
|
4005
|
+
stroke: SELECTION_BORDER_COLOR,
|
|
4006
|
+
strokeWidth: 1
|
|
4007
|
+
});
|
|
4008
|
+
g.appendChild(bar);
|
|
4009
|
+
});
|
|
4010
|
+
});
|
|
4011
|
+
return g;
|
|
4012
|
+
}
|
|
3164
4013
|
|
|
3165
4014
|
const PlaitElement = {
|
|
3166
4015
|
isRootElement(value) {
|
|
@@ -3174,6 +4023,32 @@ const PlaitElement = {
|
|
|
3174
4023
|
},
|
|
3175
4024
|
getComponent(value) {
|
|
3176
4025
|
return ELEMENT_TO_COMPONENT.get(value);
|
|
4026
|
+
},
|
|
4027
|
+
getElementG(value) {
|
|
4028
|
+
const g = NODE_TO_G.get(value);
|
|
4029
|
+
if (!g) {
|
|
4030
|
+
throw new Error(`can not resolve element g: ${JSON.stringify(value)}`);
|
|
4031
|
+
}
|
|
4032
|
+
return g;
|
|
4033
|
+
},
|
|
4034
|
+
hasMounted(element) {
|
|
4035
|
+
const containerG = PlaitElement.getContainerG(element, { suppressThrow: true });
|
|
4036
|
+
if (containerG) {
|
|
4037
|
+
return true;
|
|
4038
|
+
}
|
|
4039
|
+
else {
|
|
4040
|
+
return false;
|
|
4041
|
+
}
|
|
4042
|
+
},
|
|
4043
|
+
getContainerG(value, options) {
|
|
4044
|
+
const containerG = NODE_TO_CONTAINER_G.get(value) || null;
|
|
4045
|
+
if (!containerG) {
|
|
4046
|
+
if (options.suppressThrow) {
|
|
4047
|
+
return null;
|
|
4048
|
+
}
|
|
4049
|
+
throw new Error('can not resolve container g');
|
|
4050
|
+
}
|
|
4051
|
+
return containerG;
|
|
3177
4052
|
}
|
|
3178
4053
|
};
|
|
3179
4054
|
|
|
@@ -3437,7 +4312,7 @@ const PlaitBoard = {
|
|
|
3437
4312
|
return isBoard;
|
|
3438
4313
|
},
|
|
3439
4314
|
isAlive(board) {
|
|
3440
|
-
const isAlive =
|
|
4315
|
+
const isAlive = IS_BOARD_ALIVE.get(board);
|
|
3441
4316
|
return !!isAlive;
|
|
3442
4317
|
},
|
|
3443
4318
|
findPath(board, node) {
|
|
@@ -3615,18 +4490,16 @@ function createBoard(children, options) {
|
|
|
3615
4490
|
globalKeyDown: (event) => { },
|
|
3616
4491
|
keyUp: (event) => { },
|
|
3617
4492
|
dblClick: (event) => { },
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
},
|
|
3621
|
-
insertFragment: (data) => { },
|
|
4493
|
+
buildFragment: (clipboardContext) => clipboardContext,
|
|
4494
|
+
insertFragment: () => { },
|
|
3622
4495
|
deleteFragment: (elements) => {
|
|
3623
4496
|
CoreTransforms.removeElements(board, elements);
|
|
3624
4497
|
},
|
|
3625
4498
|
getDeletedFragment: (data) => data,
|
|
3626
4499
|
getRelatedFragment: (data, originData) => data,
|
|
3627
|
-
drawElement: (context) =>
|
|
3628
|
-
|
|
3629
|
-
|
|
4500
|
+
drawElement: (context) => {
|
|
4501
|
+
throw new Error(`can not resolve plugin element component type: ${context.element.type}`);
|
|
4502
|
+
},
|
|
3630
4503
|
isWithinSelection: element => false,
|
|
3631
4504
|
isRectangleHit: element => false,
|
|
3632
4505
|
isHit: element => false,
|
|
@@ -3645,7 +4518,9 @@ function createBoard(children, options) {
|
|
|
3645
4518
|
globalPointerMove: pointer => { },
|
|
3646
4519
|
globalPointerUp: pointer => { },
|
|
3647
4520
|
isImageBindingAllowed: (element) => false,
|
|
3648
|
-
canAddToGroup: (element) => true
|
|
4521
|
+
canAddToGroup: (element) => true,
|
|
4522
|
+
canSetZIndex: (element) => true,
|
|
4523
|
+
isExpanded: (element) => true
|
|
3649
4524
|
};
|
|
3650
4525
|
return board;
|
|
3651
4526
|
}
|
|
@@ -4039,354 +4914,194 @@ function withViewport(board) {
|
|
|
4039
4914
|
return board;
|
|
4040
4915
|
}
|
|
4041
4916
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4917
|
+
function getSnapMovingRef(board, activeRectangle, activeElements) {
|
|
4918
|
+
const snapRectangles = getSnapRectangles(board, activeElements);
|
|
4919
|
+
const snapG = createG();
|
|
4920
|
+
let snapDelta = getPointLineDelta(activeRectangle, snapRectangles);
|
|
4921
|
+
const pointLinesG = drawMovingPointSnapLines(board, snapDelta, activeRectangle, snapRectangles);
|
|
4922
|
+
snapG.append(pointLinesG);
|
|
4923
|
+
const result = getGapSnapLinesAndDelta(board, snapDelta, activeRectangle, snapRectangles);
|
|
4924
|
+
snapDelta = result.snapDelta;
|
|
4925
|
+
snapG.append(result.snapG);
|
|
4926
|
+
return { ...snapDelta, snapG };
|
|
4927
|
+
}
|
|
4928
|
+
function getPointLineDeltas(activeRectangle, snapRectangles, isHorizontal) {
|
|
4929
|
+
const axis = getTripleAxis(activeRectangle, isHorizontal);
|
|
4930
|
+
const deltaStart = getMinPointDelta(snapRectangles, axis[0], isHorizontal);
|
|
4931
|
+
const deltaMiddle = getMinPointDelta(snapRectangles, axis[1], isHorizontal);
|
|
4932
|
+
const deltaEnd = getMinPointDelta(snapRectangles, axis[2], isHorizontal);
|
|
4933
|
+
return [deltaStart, deltaMiddle, deltaEnd];
|
|
4934
|
+
}
|
|
4935
|
+
function getPointLineDelta(activeRectangle, snapRectangles) {
|
|
4936
|
+
let snapDelta = {
|
|
4937
|
+
deltaX: 0,
|
|
4938
|
+
deltaY: 0
|
|
4939
|
+
};
|
|
4940
|
+
function getDelta(isHorizontal) {
|
|
4941
|
+
let delta = 0;
|
|
4942
|
+
const deltas = getPointLineDeltas(activeRectangle, snapRectangles, isHorizontal);
|
|
4943
|
+
for (let i = 0; i < deltas.length; i++) {
|
|
4944
|
+
if (Math.abs(deltas[i]) < SNAP_TOLERANCE) {
|
|
4945
|
+
delta = deltas[i];
|
|
4946
|
+
break;
|
|
4064
4947
|
}
|
|
4065
|
-
}
|
|
4066
|
-
return
|
|
4948
|
+
}
|
|
4949
|
+
return delta;
|
|
4067
4950
|
}
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4951
|
+
snapDelta.deltaX = getDelta(true);
|
|
4952
|
+
snapDelta.deltaY = getDelta(false);
|
|
4953
|
+
return snapDelta;
|
|
4954
|
+
}
|
|
4955
|
+
function updateActiveRectangle(snapDelta, activeRectangle) {
|
|
4956
|
+
const { deltaX, deltaY } = snapDelta;
|
|
4957
|
+
const { x, y, width, height } = activeRectangle;
|
|
4958
|
+
return {
|
|
4959
|
+
x: x + deltaX,
|
|
4960
|
+
y: y + deltaY,
|
|
4961
|
+
width,
|
|
4962
|
+
height
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4965
|
+
function drawMovingPointSnapLines(board, snapDelta, activeRectangle, snapRectangles) {
|
|
4966
|
+
const newActiveRectangle = updateActiveRectangle(snapDelta, activeRectangle);
|
|
4967
|
+
return drawPointSnapLines(board, newActiveRectangle, snapRectangles, true, true, true);
|
|
4968
|
+
}
|
|
4969
|
+
function getGapSnapLinesAndDelta(board, snapDelta, activeRectangle, snapRectangles) {
|
|
4970
|
+
let deltaX = snapDelta.deltaX;
|
|
4971
|
+
let deltaY = snapDelta.deltaY;
|
|
4972
|
+
const gapHorizontalResult = getGapLinesAndDelta(activeRectangle, snapRectangles, true);
|
|
4973
|
+
const gapVerticalResult = getGapLinesAndDelta(activeRectangle, snapRectangles, false);
|
|
4974
|
+
const gapSnapLines = [...gapHorizontalResult.lines, ...gapVerticalResult.lines];
|
|
4975
|
+
if (gapHorizontalResult.delta) {
|
|
4976
|
+
deltaX = gapHorizontalResult.delta;
|
|
4977
|
+
}
|
|
4978
|
+
if (gapVerticalResult.delta) {
|
|
4979
|
+
deltaY = gapVerticalResult.delta;
|
|
4980
|
+
}
|
|
4981
|
+
return {
|
|
4982
|
+
snapDelta: { deltaX, deltaY },
|
|
4983
|
+
snapG: drawSolidLines(board, gapSnapLines)
|
|
4984
|
+
};
|
|
4985
|
+
}
|
|
4986
|
+
function getGapLinesAndDelta(activeRectangle, snapRectangles, isHorizontal) {
|
|
4987
|
+
let lines = [];
|
|
4988
|
+
let delta = 0;
|
|
4989
|
+
let rectangles = [];
|
|
4990
|
+
const axis = isHorizontal ? 'x' : 'y';
|
|
4991
|
+
const side = isHorizontal ? 'width' : 'height';
|
|
4992
|
+
const activeRectangleCenter = activeRectangle[axis] + activeRectangle[side] / 2;
|
|
4993
|
+
snapRectangles.forEach(rec => {
|
|
4994
|
+
const isCross = isHorizontal ? isHorizontalCross(rec, activeRectangle) : isVerticalCross(rec, activeRectangle);
|
|
4995
|
+
if (isCross && !RectangleClient.isHit(rec, activeRectangle)) {
|
|
4996
|
+
rectangles.push(rec);
|
|
4997
|
+
}
|
|
4998
|
+
});
|
|
4999
|
+
rectangles = [...rectangles, activeRectangle].sort((a, b) => a[axis] - b[axis]);
|
|
5000
|
+
const refArray = [];
|
|
5001
|
+
let gapDistance = 0;
|
|
5002
|
+
let beforeIndex = undefined;
|
|
5003
|
+
let afterIndex = undefined;
|
|
5004
|
+
for (let i = 0; i < rectangles.length; i++) {
|
|
5005
|
+
for (let j = i + 1; j < rectangles.length; j++) {
|
|
5006
|
+
const before = rectangles[i];
|
|
5007
|
+
const after = rectangles[j];
|
|
5008
|
+
const distance = after[axis] - (before[axis] + before[side]);
|
|
5009
|
+
let dif = Infinity;
|
|
5010
|
+
if (refArray[i]?.after) {
|
|
5011
|
+
refArray[i].after.push({ distance, index: j });
|
|
4088
5012
|
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
alignRectangle.y,
|
|
4092
|
-
alignRectangle.y + alignRectangle.height,
|
|
4093
|
-
this.activeRectangle.y,
|
|
4094
|
-
this.activeRectangle.y + this.activeRectangle.height
|
|
4095
|
-
];
|
|
4096
|
-
const lineTopY = Math.min(...verticalY) - offset;
|
|
4097
|
-
const lineBottomY = Math.max(...verticalY) + offset;
|
|
4098
|
-
const leftLine = [this.activeRectangle.x, lineTopY, this.activeRectangle.x, lineBottomY];
|
|
4099
|
-
const middleLine = [
|
|
4100
|
-
this.activeRectangle.x + this.activeRectangle.width / 2,
|
|
4101
|
-
lineTopY,
|
|
4102
|
-
this.activeRectangle.x + this.activeRectangle.width / 2,
|
|
4103
|
-
lineBottomY
|
|
4104
|
-
];
|
|
4105
|
-
const rightLine = [
|
|
4106
|
-
this.activeRectangle.x + this.activeRectangle.width,
|
|
4107
|
-
lineTopY,
|
|
4108
|
-
this.activeRectangle.x + this.activeRectangle.width,
|
|
4109
|
-
lineBottomY
|
|
4110
|
-
];
|
|
4111
|
-
const shouldDrawLeftLine = closestDistances.indexX === 0 ||
|
|
4112
|
-
closestDistances.indexX === 1 ||
|
|
4113
|
-
(closestDistances.indexX === 2 && this.activeRectangle.width === alignRectangle.width);
|
|
4114
|
-
if (shouldDrawLeftLine && !alignLines[0]) {
|
|
4115
|
-
alignLines[0] = leftLine;
|
|
4116
|
-
}
|
|
4117
|
-
const shouldDrawRightLine = closestDistances.indexX === 2 ||
|
|
4118
|
-
closestDistances.indexX === 3 ||
|
|
4119
|
-
(closestDistances.indexX === 0 && this.activeRectangle.width === alignRectangle.width);
|
|
4120
|
-
if (shouldDrawRightLine && !alignLines[2]) {
|
|
4121
|
-
alignLines[2] = rightLine;
|
|
4122
|
-
}
|
|
4123
|
-
const shouldDrawMiddleLine = closestDistances.indexX === 4 || (!shouldDrawLeftLine && !shouldDrawRightLine);
|
|
4124
|
-
if (shouldDrawMiddleLine && !alignLines[1]) {
|
|
4125
|
-
alignLines[1] = middleLine;
|
|
4126
|
-
}
|
|
4127
|
-
isCorrectX = true;
|
|
5013
|
+
else {
|
|
5014
|
+
refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
|
|
4128
5015
|
}
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
deltaY = closestDistances.yDistance;
|
|
4132
|
-
this.activeRectangle.y -= deltaY;
|
|
4133
|
-
isCorrectY = true;
|
|
4134
|
-
canDrawVertical = true;
|
|
5016
|
+
if (refArray[j]?.before) {
|
|
5017
|
+
refArray[j].before.push({ distance, index: i });
|
|
4135
5018
|
}
|
|
4136
|
-
|
|
4137
|
-
|
|
5019
|
+
else {
|
|
5020
|
+
refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
|
|
4138
5021
|
}
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
const lineRightX = Math.max(...horizontalX) + offset;
|
|
4148
|
-
const topLine = [lineLeftX, this.activeRectangle.y, lineRightX, this.activeRectangle.y];
|
|
4149
|
-
const horizontalMiddleLine = [
|
|
4150
|
-
lineLeftX,
|
|
4151
|
-
this.activeRectangle.y + this.activeRectangle.height / 2,
|
|
4152
|
-
lineRightX,
|
|
4153
|
-
this.activeRectangle.y + this.activeRectangle.height / 2
|
|
4154
|
-
];
|
|
4155
|
-
const bottomLine = [
|
|
4156
|
-
lineLeftX,
|
|
4157
|
-
this.activeRectangle.y + this.activeRectangle.height,
|
|
4158
|
-
lineRightX,
|
|
4159
|
-
this.activeRectangle.y + this.activeRectangle.height
|
|
4160
|
-
];
|
|
4161
|
-
const shouldDrawTopLine = closestDistances.indexY === 0 ||
|
|
4162
|
-
closestDistances.indexY === 1 ||
|
|
4163
|
-
(closestDistances.indexY === 2 && this.activeRectangle.height === alignRectangle.height);
|
|
4164
|
-
if (shouldDrawTopLine && !alignLines[3]) {
|
|
4165
|
-
alignLines[3] = topLine;
|
|
4166
|
-
}
|
|
4167
|
-
const shouldDrawBottomLine = closestDistances.indexY === 2 ||
|
|
4168
|
-
closestDistances.indexY === 3 ||
|
|
4169
|
-
(closestDistances.indexY === 0 && this.activeRectangle.width === alignRectangle.width);
|
|
4170
|
-
if (shouldDrawBottomLine && !alignLines[5]) {
|
|
4171
|
-
alignLines[5] = bottomLine;
|
|
4172
|
-
}
|
|
4173
|
-
const shouldDrawMiddleLine = closestDistances.indexY === 4 || (!shouldDrawTopLine && !shouldDrawBottomLine);
|
|
4174
|
-
if (shouldDrawMiddleLine && !alignLines[4]) {
|
|
4175
|
-
alignLines[4] = horizontalMiddleLine;
|
|
4176
|
-
}
|
|
5022
|
+
//middle
|
|
5023
|
+
let _center = (before[axis] + before[side] + after[axis]) / 2;
|
|
5024
|
+
dif = Math.abs(_center - activeRectangleCenter);
|
|
5025
|
+
if (dif < SNAP_TOLERANCE) {
|
|
5026
|
+
gapDistance = (after[axis] - (before[axis] + before[side]) - activeRectangle[side]) / 2;
|
|
5027
|
+
delta = _center - activeRectangleCenter;
|
|
5028
|
+
beforeIndex = i;
|
|
5029
|
+
afterIndex = j;
|
|
4177
5030
|
}
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
if (distributeHorizontalResult.delta) {
|
|
4187
|
-
deltaX = distributeHorizontalResult.delta;
|
|
4188
|
-
if (alignDeltaX !== deltaX) {
|
|
4189
|
-
alignLines[0] = [];
|
|
4190
|
-
alignLines[1] = [];
|
|
4191
|
-
alignLines[2] = [];
|
|
5031
|
+
//after
|
|
5032
|
+
const distanceRight = after[axis] - (before[axis] + before[side]);
|
|
5033
|
+
_center = after[axis] + after[side] + distanceRight + activeRectangle[side] / 2;
|
|
5034
|
+
dif = Math.abs(_center - activeRectangleCenter);
|
|
5035
|
+
if (!gapDistance && dif < SNAP_TOLERANCE) {
|
|
5036
|
+
gapDistance = distanceRight;
|
|
5037
|
+
beforeIndex = j;
|
|
5038
|
+
delta = _center - activeRectangleCenter;
|
|
4192
5039
|
}
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
5040
|
+
//before
|
|
5041
|
+
const distanceBefore = after[axis] - (before[axis] + before[side]);
|
|
5042
|
+
_center = before[axis] - distanceBefore - activeRectangle[side] / 2;
|
|
5043
|
+
dif = Math.abs(_center - activeRectangleCenter);
|
|
5044
|
+
if (!gapDistance && dif < SNAP_TOLERANCE) {
|
|
5045
|
+
gapDistance = distanceBefore;
|
|
5046
|
+
afterIndex = i;
|
|
5047
|
+
delta = _center - activeRectangleCenter;
|
|
4200
5048
|
}
|
|
4201
5049
|
}
|
|
4202
|
-
if (alignLines.length) {
|
|
4203
|
-
this.drawAlignLines(alignLines, g);
|
|
4204
|
-
}
|
|
4205
|
-
if (distributeLines.length) {
|
|
4206
|
-
this.drawDistributeLines(distributeLines, g);
|
|
4207
|
-
}
|
|
4208
|
-
return { deltaX, deltaY, g };
|
|
4209
|
-
}
|
|
4210
|
-
calculateClosestDistances(activeRectangle, alignRectangle) {
|
|
4211
|
-
const activeRectangleCenter = [activeRectangle.x + activeRectangle.width / 2, activeRectangle.y + activeRectangle.height / 2];
|
|
4212
|
-
const alignRectangleCenter = [alignRectangle.x + alignRectangle.width / 2, alignRectangle.y + alignRectangle.height / 2];
|
|
4213
|
-
const centerXDistance = activeRectangleCenter[0] - alignRectangleCenter[0];
|
|
4214
|
-
const centerYDistance = activeRectangleCenter[1] - alignRectangleCenter[1];
|
|
4215
|
-
const leftToLeft = activeRectangle.x - alignRectangle.x;
|
|
4216
|
-
const leftToRight = activeRectangle.x - (alignRectangle.x + alignRectangle.width);
|
|
4217
|
-
const rightToRight = activeRectangle.x + activeRectangle.width - (alignRectangle.x + alignRectangle.width);
|
|
4218
|
-
const rightToLeft = activeRectangle.x + activeRectangle.width - alignRectangle.x;
|
|
4219
|
-
const topToTop = activeRectangle.y - alignRectangle.y;
|
|
4220
|
-
const topToBottom = activeRectangle.y - (alignRectangle.y + alignRectangle.height);
|
|
4221
|
-
const bottomToTop = activeRectangle.y + activeRectangle.height - alignRectangle.y;
|
|
4222
|
-
const bottomToBottom = activeRectangle.y + activeRectangle.height - (alignRectangle.y + alignRectangle.height);
|
|
4223
|
-
const xDistances = [leftToLeft, leftToRight, rightToRight, rightToLeft, centerXDistance];
|
|
4224
|
-
const yDistances = [topToTop, topToBottom, bottomToBottom, bottomToTop, centerYDistance];
|
|
4225
|
-
const xDistancesAbs = xDistances.map(distance => Math.abs(distance));
|
|
4226
|
-
const yDistancesAbs = yDistances.map(distance => Math.abs(distance));
|
|
4227
|
-
const indexX = xDistancesAbs.indexOf(Math.min(...xDistancesAbs));
|
|
4228
|
-
const indexY = yDistancesAbs.indexOf(Math.min(...yDistancesAbs));
|
|
4229
|
-
return {
|
|
4230
|
-
absXDistance: xDistancesAbs[indexX],
|
|
4231
|
-
xDistance: xDistances[indexX],
|
|
4232
|
-
absYDistance: yDistancesAbs[indexY],
|
|
4233
|
-
yDistance: yDistances[indexY],
|
|
4234
|
-
indexX,
|
|
4235
|
-
indexY
|
|
4236
|
-
};
|
|
4237
5050
|
}
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
let beforeIndex = undefined;
|
|
4255
|
-
let afterIndex = undefined;
|
|
4256
|
-
for (let i = 0; i < rectangles.length; i++) {
|
|
4257
|
-
for (let j = i + 1; j < rectangles.length; j++) {
|
|
4258
|
-
const before = rectangles[i];
|
|
4259
|
-
const after = rectangles[j];
|
|
4260
|
-
const distance = after[axis] - (before[axis] + before[side]);
|
|
4261
|
-
let dif = Infinity;
|
|
4262
|
-
if (refArray[i]?.after) {
|
|
4263
|
-
refArray[i].after.push({ distance, index: j });
|
|
4264
|
-
}
|
|
4265
|
-
else {
|
|
4266
|
-
refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
|
|
4267
|
-
}
|
|
4268
|
-
if (refArray[j]?.before) {
|
|
4269
|
-
refArray[j].before.push({ distance, index: i });
|
|
4270
|
-
}
|
|
4271
|
-
else {
|
|
4272
|
-
refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
|
|
4273
|
-
}
|
|
4274
|
-
//middle
|
|
4275
|
-
let _center = (before[axis] + before[side] + after[axis]) / 2;
|
|
4276
|
-
dif = Math.abs(activeRectangleCenter - _center);
|
|
4277
|
-
if (dif < SNAP_TOLERANCE) {
|
|
4278
|
-
distributeDistance = (after[axis] - (before[axis] + before[side]) - this.activeRectangle[side]) / 2;
|
|
4279
|
-
delta = activeRectangleCenter - _center;
|
|
4280
|
-
beforeIndex = i;
|
|
4281
|
-
afterIndex = j;
|
|
4282
|
-
}
|
|
4283
|
-
//after
|
|
4284
|
-
const distanceRight = after[axis] - (before[axis] + before[side]);
|
|
4285
|
-
_center = after[axis] + after[side] + distanceRight + this.activeRectangle[side] / 2;
|
|
4286
|
-
dif = Math.abs(activeRectangleCenter - _center);
|
|
4287
|
-
if (!distributeDistance && dif < SNAP_TOLERANCE) {
|
|
4288
|
-
distributeDistance = distanceRight;
|
|
4289
|
-
beforeIndex = j;
|
|
4290
|
-
delta = activeRectangleCenter - _center;
|
|
4291
|
-
}
|
|
4292
|
-
//before
|
|
4293
|
-
const distanceBefore = after[axis] - (before[axis] + before[side]);
|
|
4294
|
-
_center = before[axis] - distanceBefore - this.activeRectangle[side] / 2;
|
|
4295
|
-
dif = Math.abs(activeRectangleCenter - _center);
|
|
4296
|
-
if (!distributeDistance && dif < SNAP_TOLERANCE) {
|
|
4297
|
-
distributeDistance = distanceBefore;
|
|
4298
|
-
afterIndex = i;
|
|
4299
|
-
delta = activeRectangleCenter - _center;
|
|
4300
|
-
}
|
|
4301
|
-
}
|
|
4302
|
-
}
|
|
4303
|
-
const activeIndex = rectangles.indexOf(this.activeRectangle);
|
|
4304
|
-
let beforeIndexes = [];
|
|
4305
|
-
let afterIndexes = [];
|
|
4306
|
-
if (beforeIndex !== undefined) {
|
|
4307
|
-
beforeIndexes.push(beforeIndex);
|
|
4308
|
-
findRectangle(distributeDistance, refArray[beforeIndex], 'before', beforeIndexes);
|
|
4309
|
-
}
|
|
4310
|
-
if (afterIndex !== undefined) {
|
|
4311
|
-
afterIndexes.push(afterIndex);
|
|
4312
|
-
findRectangle(distributeDistance, refArray[afterIndex], 'after', afterIndexes);
|
|
4313
|
-
}
|
|
4314
|
-
if (beforeIndexes.length || afterIndexes.length) {
|
|
4315
|
-
const indexArr = [...beforeIndexes.reverse(), activeIndex, ...afterIndexes];
|
|
4316
|
-
this.activeRectangle[axis] -= delta;
|
|
4317
|
-
for (let i = 1; i < indexArr.length; i++) {
|
|
4318
|
-
distributeLines.push(getLinePoints(rectangles[indexArr[i - 1]], rectangles[indexArr[i]]));
|
|
4319
|
-
}
|
|
5051
|
+
const activeIndex = rectangles.indexOf(activeRectangle);
|
|
5052
|
+
let beforeIndexes = [];
|
|
5053
|
+
let afterIndexes = [];
|
|
5054
|
+
if (beforeIndex !== undefined) {
|
|
5055
|
+
beforeIndexes.push(beforeIndex);
|
|
5056
|
+
findRectangle(gapDistance, refArray[beforeIndex], 'before', beforeIndexes);
|
|
5057
|
+
}
|
|
5058
|
+
if (afterIndex !== undefined) {
|
|
5059
|
+
afterIndexes.push(afterIndex);
|
|
5060
|
+
findRectangle(gapDistance, refArray[afterIndex], 'after', afterIndexes);
|
|
5061
|
+
}
|
|
5062
|
+
if (beforeIndexes.length || afterIndexes.length) {
|
|
5063
|
+
const indexArr = [...beforeIndexes.reverse(), activeIndex, ...afterIndexes];
|
|
5064
|
+
activeRectangle[axis] += delta;
|
|
5065
|
+
for (let i = 1; i < indexArr.length; i++) {
|
|
5066
|
+
lines.push(getLinePoints(rectangles[indexArr[i - 1]], rectangles[indexArr[i]]));
|
|
4320
5067
|
}
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
5068
|
+
}
|
|
5069
|
+
function findRectangle(distance, ref, direction, rectangleIndexes) {
|
|
5070
|
+
const arr = ref[direction];
|
|
5071
|
+
const index = refArray.indexOf(ref);
|
|
5072
|
+
if ((index === 0 && direction === 'before') || (index === refArray.length - 1 && direction === 'after'))
|
|
5073
|
+
return;
|
|
5074
|
+
for (let i = 0; i < arr.length; i++) {
|
|
5075
|
+
if (Math.abs(arr[i].distance - distance) < 0.1) {
|
|
5076
|
+
rectangleIndexes.push(arr[i].index);
|
|
5077
|
+
findRectangle(distance, refArray[arr[i].index], direction, rectangleIndexes);
|
|
4325
5078
|
return;
|
|
4326
|
-
for (let i = 0; i < arr.length; i++) {
|
|
4327
|
-
if (Math.abs(arr[i].distance - distance) < 0.1) {
|
|
4328
|
-
rectangleIndexes.push(arr[i].index);
|
|
4329
|
-
findRectangle(distance, refArray[arr[i].index], direction, rectangleIndexes);
|
|
4330
|
-
return;
|
|
4331
|
-
}
|
|
4332
5079
|
}
|
|
4333
5080
|
}
|
|
4334
|
-
function getLinePoints(beforeRectangle, afterRectangle) {
|
|
4335
|
-
const oppositeAxis = axis === 'x' ? 'y' : 'x';
|
|
4336
|
-
const oppositeSide = side === 'width' ? 'height' : 'width';
|
|
4337
|
-
const align = [
|
|
4338
|
-
beforeRectangle[oppositeAxis],
|
|
4339
|
-
beforeRectangle[oppositeAxis] + beforeRectangle[oppositeSide],
|
|
4340
|
-
afterRectangle[oppositeAxis],
|
|
4341
|
-
afterRectangle[oppositeAxis] + afterRectangle[oppositeSide]
|
|
4342
|
-
];
|
|
4343
|
-
const sortArr = align.sort((a, b) => a - b);
|
|
4344
|
-
const average = (sortArr[1] + sortArr[2]) / 2;
|
|
4345
|
-
const offset = 3;
|
|
4346
|
-
return isHorizontal
|
|
4347
|
-
? [
|
|
4348
|
-
[beforeRectangle.x + beforeRectangle.width + offset, average],
|
|
4349
|
-
[afterRectangle.x - offset, average]
|
|
4350
|
-
]
|
|
4351
|
-
: [
|
|
4352
|
-
[average, beforeRectangle.y + beforeRectangle.height + offset],
|
|
4353
|
-
[average, afterRectangle.y - offset]
|
|
4354
|
-
];
|
|
4355
|
-
}
|
|
4356
|
-
return { delta, distributeLines };
|
|
4357
|
-
}
|
|
4358
|
-
drawAlignLines(lines, g) {
|
|
4359
|
-
lines.forEach(points => {
|
|
4360
|
-
if (!points.length)
|
|
4361
|
-
return;
|
|
4362
|
-
const xAlign = PlaitBoard.getRoughSVG(this.board).line(points[0], points[1], points[2], points[3], {
|
|
4363
|
-
stroke: SELECTION_BORDER_COLOR,
|
|
4364
|
-
strokeWidth: 1,
|
|
4365
|
-
strokeLineDash: [4, 4]
|
|
4366
|
-
});
|
|
4367
|
-
g.appendChild(xAlign);
|
|
4368
|
-
});
|
|
4369
5081
|
}
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
5082
|
+
function getLinePoints(beforeRectangle, afterRectangle) {
|
|
5083
|
+
const oppositeAxis = axis === 'x' ? 'y' : 'x';
|
|
5084
|
+
const oppositeSide = side === 'width' ? 'height' : 'width';
|
|
5085
|
+
const snap = [
|
|
5086
|
+
beforeRectangle[oppositeAxis],
|
|
5087
|
+
beforeRectangle[oppositeAxis] + beforeRectangle[oppositeSide],
|
|
5088
|
+
afterRectangle[oppositeAxis],
|
|
5089
|
+
afterRectangle[oppositeAxis] + afterRectangle[oppositeSide]
|
|
5090
|
+
];
|
|
5091
|
+
const sortArr = snap.sort((a, b) => a - b);
|
|
5092
|
+
const average = (sortArr[1] + sortArr[2]) / 2;
|
|
5093
|
+
const offset = 3;
|
|
5094
|
+
return isHorizontal
|
|
5095
|
+
? [
|
|
5096
|
+
[beforeRectangle.x + beforeRectangle.width + offset, average],
|
|
5097
|
+
[afterRectangle.x - offset, average]
|
|
5098
|
+
]
|
|
5099
|
+
: [
|
|
5100
|
+
[average, beforeRectangle.y + beforeRectangle.height + offset],
|
|
5101
|
+
[average, afterRectangle.y - offset]
|
|
5102
|
+
];
|
|
4389
5103
|
}
|
|
5104
|
+
return { delta, lines };
|
|
4390
5105
|
}
|
|
4391
5106
|
function isHorizontalCross(rectangle, other) {
|
|
4392
5107
|
return !(rectangle.y + rectangle.height < other.y || rectangle.y > other.y + other.height);
|
|
@@ -4394,17 +5109,6 @@ function isHorizontalCross(rectangle, other) {
|
|
|
4394
5109
|
function isVerticalCross(rectangle, other) {
|
|
4395
5110
|
return !(rectangle.x + rectangle.width < other.x || rectangle.x > other.x + other.width);
|
|
4396
5111
|
}
|
|
4397
|
-
function getBarPoint(point, isHorizontal) {
|
|
4398
|
-
return isHorizontal
|
|
4399
|
-
? [
|
|
4400
|
-
[point[0], point[1] - 4],
|
|
4401
|
-
[point[0], point[1] + 4]
|
|
4402
|
-
]
|
|
4403
|
-
: [
|
|
4404
|
-
[point[0] - 4, point[1]],
|
|
4405
|
-
[point[0] + 4, point[1]]
|
|
4406
|
-
];
|
|
4407
|
-
}
|
|
4408
5112
|
|
|
4409
5113
|
function withMoving(board) {
|
|
4410
5114
|
const { pointerDown, pointerMove, globalPointerUp, globalPointerMove } = board;
|
|
@@ -4413,7 +5117,7 @@ function withMoving(board) {
|
|
|
4413
5117
|
let isPreventDefault = false;
|
|
4414
5118
|
let startPoint;
|
|
4415
5119
|
let activeElements = [];
|
|
4416
|
-
let
|
|
5120
|
+
let snapG = null;
|
|
4417
5121
|
let activeElementsRectangle = null;
|
|
4418
5122
|
let selectedTargetElements = null;
|
|
4419
5123
|
let hitTargetElement = undefined;
|
|
@@ -4462,7 +5166,7 @@ function withMoving(board) {
|
|
|
4462
5166
|
if (!isPreventDefault) {
|
|
4463
5167
|
isPreventDefault = true;
|
|
4464
5168
|
}
|
|
4465
|
-
|
|
5169
|
+
snapG?.remove();
|
|
4466
5170
|
const endPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
|
|
4467
5171
|
offsetX = endPoint[0] - startPoint[0];
|
|
4468
5172
|
offsetY = endPoint[1] - startPoint[1];
|
|
@@ -4483,13 +5187,13 @@ function withMoving(board) {
|
|
|
4483
5187
|
x: activeElementsRectangle.x + offsetX,
|
|
4484
5188
|
y: activeElementsRectangle.y + offsetY
|
|
4485
5189
|
};
|
|
4486
|
-
const
|
|
4487
|
-
const ref =
|
|
4488
|
-
offsetX
|
|
4489
|
-
offsetY
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
PlaitBoard.getElementActiveHost(board).append(
|
|
5190
|
+
const activeRectangle = getRectangleByAngle(newRectangle, getSelectionAngle(activeElements)) || newRectangle;
|
|
5191
|
+
const ref = getSnapMovingRef(board, activeRectangle, activeElements);
|
|
5192
|
+
offsetX += ref.deltaX;
|
|
5193
|
+
offsetY += ref.deltaY;
|
|
5194
|
+
snapG = ref.snapG;
|
|
5195
|
+
snapG.classList.add(ACTIVE_MOVING_CLASS_NAME);
|
|
5196
|
+
PlaitBoard.getElementActiveHost(board).append(snapG);
|
|
4493
5197
|
handleTouchTarget(board);
|
|
4494
5198
|
const currentElements = updatePoints(board, activeElements, offsetX, offsetY);
|
|
4495
5199
|
PlaitBoard.getBoardContainer(board).classList.add('element-moving');
|
|
@@ -4524,7 +5228,7 @@ function withMoving(board) {
|
|
|
4524
5228
|
globalPointerUp(event);
|
|
4525
5229
|
};
|
|
4526
5230
|
function cancelMove(board) {
|
|
4527
|
-
|
|
5231
|
+
snapG?.remove();
|
|
4528
5232
|
startPoint = null;
|
|
4529
5233
|
activeElementsRectangle = null;
|
|
4530
5234
|
offsetX = 0;
|
|
@@ -4708,7 +5412,36 @@ const withHotkey = (board) => {
|
|
|
4708
5412
|
Transforms.addSelectionWithTemporaryElements(board, elements);
|
|
4709
5413
|
return;
|
|
4710
5414
|
}
|
|
5415
|
+
if (!PlaitBoard.isReadonly(board)) {
|
|
5416
|
+
if (isKeyHotkey('mod+]', event)) {
|
|
5417
|
+
event.preventDefault();
|
|
5418
|
+
Transforms.moveUp(board);
|
|
5419
|
+
return;
|
|
5420
|
+
}
|
|
5421
|
+
if (isKeyHotkey('mod+[', event)) {
|
|
5422
|
+
event.preventDefault();
|
|
5423
|
+
Transforms.moveDown(board);
|
|
5424
|
+
return;
|
|
5425
|
+
}
|
|
5426
|
+
if (isKeyHotkey('mod+option+‘', event)) {
|
|
5427
|
+
event.preventDefault();
|
|
5428
|
+
Transforms.moveToTop(board);
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5431
|
+
if (isKeyHotkey('mod+option+“', event)) {
|
|
5432
|
+
event.preventDefault();
|
|
5433
|
+
Transforms.moveToBottom(board);
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
4711
5437
|
const selectedElements = getSelectedElements(board);
|
|
5438
|
+
if (!PlaitBoard.isReadonly(board) && selectedElements.length > 0) {
|
|
5439
|
+
if (isKeyHotkey('mod+d', event)) {
|
|
5440
|
+
event.preventDefault();
|
|
5441
|
+
duplicateElements(board, selectedElements);
|
|
5442
|
+
return;
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
4712
5445
|
if (!PlaitBoard.isReadonly(board) &&
|
|
4713
5446
|
selectedElements.length > 0 &&
|
|
4714
5447
|
(hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
|
|
@@ -4750,6 +5483,7 @@ const withHotkey = (board) => {
|
|
|
4750
5483
|
|
|
4751
5484
|
class PlaitContextService {
|
|
4752
5485
|
constructor() {
|
|
5486
|
+
this._stable = new Subject();
|
|
4753
5487
|
this.uploadingFiles = [];
|
|
4754
5488
|
}
|
|
4755
5489
|
getUploadingFile(url) {
|
|
@@ -4761,6 +5495,12 @@ class PlaitContextService {
|
|
|
4761
5495
|
removeUploadingFile(fileEntry) {
|
|
4762
5496
|
this.uploadingFiles = this.uploadingFiles.filter(file => file.url !== fileEntry.url);
|
|
4763
5497
|
}
|
|
5498
|
+
onStable() {
|
|
5499
|
+
return this._stable.asObservable();
|
|
5500
|
+
}
|
|
5501
|
+
nextStable() {
|
|
5502
|
+
this._stable.next('');
|
|
5503
|
+
}
|
|
4764
5504
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4765
5505
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitContextService }); }
|
|
4766
5506
|
}
|
|
@@ -4768,162 +5508,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
|
|
|
4768
5508
|
type: Injectable
|
|
4769
5509
|
}] });
|
|
4770
5510
|
|
|
4771
|
-
class PlaitElementComponent {
|
|
4772
|
-
constructor(renderer2, viewContainerRef) {
|
|
4773
|
-
this.renderer2 = renderer2;
|
|
4774
|
-
this.viewContainerRef = viewContainerRef;
|
|
4775
|
-
this.initialized = false;
|
|
4776
|
-
}
|
|
4777
|
-
ngOnInit() {
|
|
4778
|
-
this.initialize();
|
|
4779
|
-
this.drawElement();
|
|
4780
|
-
}
|
|
4781
|
-
initialize() {
|
|
4782
|
-
NODE_TO_INDEX.set(this.element, this.index);
|
|
4783
|
-
NODE_TO_PARENT.set(this.element, this.parent);
|
|
4784
|
-
this.initialized = true;
|
|
4785
|
-
}
|
|
4786
|
-
drawElement() {
|
|
4787
|
-
const context = this.getContext();
|
|
4788
|
-
const result = this.board.drawElement(context);
|
|
4789
|
-
if (Array.isArray(result)) {
|
|
4790
|
-
}
|
|
4791
|
-
else {
|
|
4792
|
-
const componentRef = this.viewContainerRef.createComponent(result);
|
|
4793
|
-
const instance = componentRef.instance;
|
|
4794
|
-
instance.context = context;
|
|
4795
|
-
this.insertG(instance.rootG ? instance.rootG : instance.g);
|
|
4796
|
-
this.instance = instance;
|
|
4797
|
-
}
|
|
4798
|
-
}
|
|
4799
|
-
insertG(g) {
|
|
4800
|
-
if (PlaitBoard.isBoard(this.parent)) {
|
|
4801
|
-
this.parentG.append(g);
|
|
4802
|
-
}
|
|
4803
|
-
else {
|
|
4804
|
-
let siblingG = PlaitElement.getComponent(this.parent).g;
|
|
4805
|
-
if (this.index > 0) {
|
|
4806
|
-
const brotherElement = this.parent.children[this.index - 1];
|
|
4807
|
-
const lastElement = PlaitNode.last(this.board, PlaitBoard.findPath(this.board, brotherElement));
|
|
4808
|
-
let component = PlaitElement.getComponent(lastElement) || PlaitElement.getComponent(brotherElement);
|
|
4809
|
-
siblingG = component.g;
|
|
4810
|
-
}
|
|
4811
|
-
this.parentG.insertBefore(g, siblingG);
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
ngOnChanges(simpleChanges) {
|
|
4815
|
-
if (this.initialized) {
|
|
4816
|
-
NODE_TO_INDEX.set(this.element, this.index);
|
|
4817
|
-
NODE_TO_PARENT.set(this.element, this.parent);
|
|
4818
|
-
const elementChanged = simpleChanges['element'];
|
|
4819
|
-
const context = this.getContext();
|
|
4820
|
-
if (elementChanged && isSelectedElement(this.board, elementChanged.previousValue)) {
|
|
4821
|
-
context.selected = true;
|
|
4822
|
-
removeSelectedElement(this.board, elementChanged.previousValue);
|
|
4823
|
-
addSelectedElement(this.board, this.element);
|
|
4824
|
-
}
|
|
4825
|
-
if (this.instance) {
|
|
4826
|
-
this.instance.context = context;
|
|
4827
|
-
}
|
|
4828
|
-
}
|
|
4829
|
-
}
|
|
4830
|
-
getContext() {
|
|
4831
|
-
const isSelected = isSelectedElement(this.board, this.element);
|
|
4832
|
-
const context = {
|
|
4833
|
-
element: this.element,
|
|
4834
|
-
parent: this.parent,
|
|
4835
|
-
board: this.board,
|
|
4836
|
-
selected: isSelected,
|
|
4837
|
-
effect: this.effect
|
|
4838
|
-
};
|
|
4839
|
-
return context;
|
|
4840
|
-
}
|
|
4841
|
-
ngOnDestroy() {
|
|
4842
|
-
this.board.destroyElement(this.getContext());
|
|
4843
|
-
}
|
|
4844
|
-
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 }); }
|
|
4845
|
-
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 }); }
|
|
4846
|
-
}
|
|
4847
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitElementComponent, decorators: [{
|
|
4848
|
-
type: Component,
|
|
4849
|
-
args: [{
|
|
4850
|
-
selector: 'plait-element',
|
|
4851
|
-
template: '',
|
|
4852
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4853
|
-
standalone: true
|
|
4854
|
-
}]
|
|
4855
|
-
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }], propDecorators: { index: [{
|
|
4856
|
-
type: Input
|
|
4857
|
-
}], element: [{
|
|
4858
|
-
type: Input
|
|
4859
|
-
}], parent: [{
|
|
4860
|
-
type: Input
|
|
4861
|
-
}], board: [{
|
|
4862
|
-
type: Input
|
|
4863
|
-
}], effect: [{
|
|
4864
|
-
type: Input
|
|
4865
|
-
}], parentG: [{
|
|
4866
|
-
type: Input
|
|
4867
|
-
}] } });
|
|
4868
|
-
|
|
4869
|
-
class PlaitChildrenElementComponent {
|
|
4870
|
-
constructor() {
|
|
4871
|
-
this.trackBy = (index, element) => {
|
|
4872
|
-
return element.id;
|
|
4873
|
-
};
|
|
4874
|
-
}
|
|
4875
|
-
ngOnInit() {
|
|
4876
|
-
if (!this.parent) {
|
|
4877
|
-
this.parent = this.board;
|
|
4878
|
-
}
|
|
4879
|
-
if (!this.parentG) {
|
|
4880
|
-
this.parentG = PlaitBoard.getElementHost(this.board);
|
|
4881
|
-
}
|
|
4882
|
-
}
|
|
4883
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4884
|
-
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: `
|
|
4885
|
-
<plait-element
|
|
4886
|
-
*ngFor="let item of parent.children; let index = index; trackBy: trackBy"
|
|
4887
|
-
[index]="index"
|
|
4888
|
-
[element]="item"
|
|
4889
|
-
[parent]="parent"
|
|
4890
|
-
[board]="board"
|
|
4891
|
-
[effect]="effect"
|
|
4892
|
-
[parentG]="parentG"
|
|
4893
|
-
></plait-element>
|
|
4894
|
-
`, 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"] }] }); }
|
|
4895
|
-
}
|
|
4896
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, decorators: [{
|
|
4897
|
-
type: Component,
|
|
4898
|
-
args: [{
|
|
4899
|
-
selector: 'plait-children',
|
|
4900
|
-
template: `
|
|
4901
|
-
<plait-element
|
|
4902
|
-
*ngFor="let item of parent.children; let index = index; trackBy: trackBy"
|
|
4903
|
-
[index]="index"
|
|
4904
|
-
[element]="item"
|
|
4905
|
-
[parent]="parent"
|
|
4906
|
-
[board]="board"
|
|
4907
|
-
[effect]="effect"
|
|
4908
|
-
[parentG]="parentG"
|
|
4909
|
-
></plait-element>
|
|
4910
|
-
`,
|
|
4911
|
-
standalone: true,
|
|
4912
|
-
imports: [NgFor, PlaitElementComponent]
|
|
4913
|
-
}]
|
|
4914
|
-
}], ctorParameters: () => [], propDecorators: { board: [{
|
|
4915
|
-
type: Input
|
|
4916
|
-
}], parent: [{
|
|
4917
|
-
type: Input
|
|
4918
|
-
}], effect: [{
|
|
4919
|
-
type: Input
|
|
4920
|
-
}], parentG: [{
|
|
4921
|
-
type: Input
|
|
4922
|
-
}] } });
|
|
4923
|
-
|
|
4924
5511
|
function withRelatedFragment(board) {
|
|
4925
|
-
const {
|
|
4926
|
-
board.
|
|
5512
|
+
const { buildFragment } = board;
|
|
5513
|
+
board.buildFragment = (clipboardContext, rectangle, type) => {
|
|
4927
5514
|
const relatedFragment = board.getRelatedFragment([]);
|
|
4928
5515
|
if (!clipboardContext) {
|
|
4929
5516
|
clipboardContext = createClipboardContext(WritableClipboardType.elements, relatedFragment, '');
|
|
@@ -4932,10 +5519,10 @@ function withRelatedFragment(board) {
|
|
|
4932
5519
|
clipboardContext = addClipboardContext(clipboardContext, {
|
|
4933
5520
|
text: '',
|
|
4934
5521
|
type: WritableClipboardType.elements,
|
|
4935
|
-
|
|
5522
|
+
elements: relatedFragment
|
|
4936
5523
|
});
|
|
4937
5524
|
}
|
|
4938
|
-
|
|
5525
|
+
return buildFragment(clipboardContext, rectangle, type);
|
|
4939
5526
|
};
|
|
4940
5527
|
return board;
|
|
4941
5528
|
}
|
|
@@ -4980,12 +5567,12 @@ class PlaitBoardComponent {
|
|
|
4980
5567
|
this.elementRef = elementRef;
|
|
4981
5568
|
this.ngZone = ngZone;
|
|
4982
5569
|
this.hasInitialized = false;
|
|
4983
|
-
this.effect = {};
|
|
4984
5570
|
this.destroy$ = new Subject();
|
|
4985
5571
|
this.plaitValue = [];
|
|
4986
5572
|
this.plaitPlugins = [];
|
|
4987
5573
|
this.plaitChange = new EventEmitter();
|
|
4988
5574
|
this.plaitBoardInitialized = new EventEmitter();
|
|
5575
|
+
this.contextService = inject(PlaitContextService);
|
|
4989
5576
|
this.trackBy = (index, element) => {
|
|
4990
5577
|
return element.id;
|
|
4991
5578
|
};
|
|
@@ -5023,7 +5610,7 @@ class PlaitBoardComponent {
|
|
|
5023
5610
|
});
|
|
5024
5611
|
BOARD_TO_ON_CHANGE.set(this.board, () => {
|
|
5025
5612
|
this.ngZone.run(() => {
|
|
5026
|
-
this.
|
|
5613
|
+
this.updateListRender();
|
|
5027
5614
|
});
|
|
5028
5615
|
});
|
|
5029
5616
|
BOARD_TO_AFTER_CHANGE.set(this.board, () => {
|
|
@@ -5039,15 +5626,12 @@ class PlaitBoardComponent {
|
|
|
5039
5626
|
this.plaitChange.emit(changeEvent);
|
|
5040
5627
|
});
|
|
5041
5628
|
});
|
|
5629
|
+
this.initializeListRender();
|
|
5042
5630
|
this.hasInitialized = true;
|
|
5043
5631
|
}
|
|
5044
5632
|
ngAfterContentInit() {
|
|
5045
5633
|
this.initializeIslands();
|
|
5046
5634
|
}
|
|
5047
|
-
update() {
|
|
5048
|
-
this.effect = {};
|
|
5049
|
-
this.cdr.detectChanges();
|
|
5050
|
-
}
|
|
5051
5635
|
ngOnChanges(changes) {
|
|
5052
5636
|
if (this.hasInitialized) {
|
|
5053
5637
|
const valueChange = changes['plaitValue'];
|
|
@@ -5165,10 +5749,8 @@ class PlaitBoardComponent {
|
|
|
5165
5749
|
fromEvent(document, 'copy')
|
|
5166
5750
|
.pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board)))
|
|
5167
5751
|
.subscribe((event) => {
|
|
5168
|
-
const selectedElements = getSelectedElements(this.board);
|
|
5169
5752
|
event.preventDefault();
|
|
5170
|
-
|
|
5171
|
-
this.board.setFragment(event.clipboardData, null, rectangle, 'copy');
|
|
5753
|
+
setFragment(this.board, 'copy', event.clipboardData);
|
|
5172
5754
|
});
|
|
5173
5755
|
fromEvent(document, 'paste')
|
|
5174
5756
|
.pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
|
|
@@ -5177,19 +5759,32 @@ class PlaitBoardComponent {
|
|
|
5177
5759
|
if (mousePoint) {
|
|
5178
5760
|
const targetPoint = toViewBoxPoint(this.board, toHostPoint(this.board, mousePoint[0], mousePoint[1]));
|
|
5179
5761
|
const clipboardData = await getClipboardData(clipboardEvent.clipboardData);
|
|
5180
|
-
this.board.insertFragment(
|
|
5762
|
+
this.board.insertFragment(clipboardData, targetPoint);
|
|
5181
5763
|
}
|
|
5182
5764
|
});
|
|
5183
5765
|
fromEvent(document, 'cut')
|
|
5184
5766
|
.pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
|
|
5185
5767
|
.subscribe((event) => {
|
|
5186
|
-
const selectedElements = getSelectedElements(this.board);
|
|
5187
5768
|
event.preventDefault();
|
|
5188
|
-
|
|
5189
|
-
this.board.setFragment(event.clipboardData, null, rectangle, 'cut');
|
|
5769
|
+
setFragment(this.board, 'cut', event.clipboardData);
|
|
5190
5770
|
deleteFragment(this.board);
|
|
5191
5771
|
});
|
|
5192
5772
|
}
|
|
5773
|
+
initializeListRender() {
|
|
5774
|
+
this.listRender = new ListRender(this.board, this.viewContainerRef);
|
|
5775
|
+
this.listRender.initialize(this.board.children, this.initializeChildrenContext());
|
|
5776
|
+
}
|
|
5777
|
+
updateListRender() {
|
|
5778
|
+
this.listRender.update(this.board.children, this.initializeChildrenContext());
|
|
5779
|
+
this.contextService.nextStable();
|
|
5780
|
+
}
|
|
5781
|
+
initializeChildrenContext() {
|
|
5782
|
+
return {
|
|
5783
|
+
board: this.board,
|
|
5784
|
+
parent: this.board,
|
|
5785
|
+
parentG: PlaitBoard.getElementHost(this.board)
|
|
5786
|
+
};
|
|
5787
|
+
}
|
|
5193
5788
|
viewportScrollListener() {
|
|
5194
5789
|
fromEvent(this.viewportContainer.nativeElement, 'scroll')
|
|
5195
5790
|
.pipe(takeUntil(this.destroy$), filter(() => {
|
|
@@ -5287,10 +5882,9 @@ class PlaitBoardComponent {
|
|
|
5287
5882
|
<g class="element-upper-host"></g>
|
|
5288
5883
|
<g class="element-active-host"></g>
|
|
5289
5884
|
</svg>
|
|
5290
|
-
<plait-children [board]="board" [effect]="effect"></plait-children>
|
|
5291
5885
|
</div>
|
|
5292
5886
|
<ng-content></ng-content>
|
|
5293
|
-
`, isInline: true,
|
|
5887
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5294
5888
|
}
|
|
5295
5889
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitBoardComponent, decorators: [{
|
|
5296
5890
|
type: Component,
|
|
@@ -5303,14 +5897,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
|
|
|
5303
5897
|
<g class="element-upper-host"></g>
|
|
5304
5898
|
<g class="element-active-host"></g>
|
|
5305
5899
|
</svg>
|
|
5306
|
-
<plait-children [board]="board" [effect]="effect"></plait-children>
|
|
5307
5900
|
</div>
|
|
5308
5901
|
<ng-content></ng-content>
|
|
5309
5902
|
`,
|
|
5310
5903
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5311
5904
|
providers: [PlaitContextService],
|
|
5312
|
-
standalone: true
|
|
5313
|
-
imports: [PlaitChildrenElementComponent]
|
|
5905
|
+
standalone: true
|
|
5314
5906
|
}]
|
|
5315
5907
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ViewContainerRef }, { type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { plaitValue: [{
|
|
5316
5908
|
type: Input
|
|
@@ -5607,5 +6199,5 @@ const isDebug = (key) => {
|
|
|
5607
6199
|
* Generated bundle index. Do not edit.
|
|
5608
6200
|
*/
|
|
5609
6201
|
|
|
5610
|
-
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,
|
|
6202
|
+
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 };
|
|
5611
6203
|
//# sourceMappingURL=plait-core.mjs.map
|