@plait/core 0.0.13 → 0.0.17
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 +3 -1
- package/esm2020/board/board.component.mjs +14 -7
- package/esm2020/interfaces/board.mjs +1 -1
- package/esm2020/interfaces/history.mjs +2 -0
- package/esm2020/interfaces/index.mjs +2 -1
- package/esm2020/interfaces/operation.mjs +79 -2
- package/esm2020/plugins/create-board.mjs +8 -1
- package/esm2020/plugins/with-history.mjs +123 -0
- package/esm2020/transfroms/node.mjs +3 -2
- package/esm2020/utils/history.mjs +28 -0
- package/esm2020/utils/index.mjs +2 -1
- package/fesm2015/plait-core.mjs +389 -167
- package/fesm2015/plait-core.mjs.map +1 -1
- package/fesm2020/plait-core.mjs +408 -170
- package/fesm2020/plait-core.mjs.map +1 -1
- package/interfaces/board.d.ts +6 -0
- package/interfaces/history.d.ts +5 -0
- package/interfaces/index.d.ts +1 -0
- package/interfaces/operation.d.ts +7 -3
- package/package.json +1 -1
- package/plugins/with-history.d.ts +24 -0
- package/utils/history.d.ts +13 -0
- package/utils/index.d.ts +1 -0
- package/styles/styles.scss +0 -62
- package/styles/theme.scss +0 -14
package/fesm2020/plait-core.mjs
CHANGED
|
@@ -4,9 +4,9 @@ import produce, { createDraft, finishDraft, isDraft } from 'immer';
|
|
|
4
4
|
import { Subject, fromEvent } from 'rxjs';
|
|
5
5
|
import { takeUntil, filter } from 'rxjs/operators';
|
|
6
6
|
import rough from 'roughjs/bin/rough';
|
|
7
|
+
import { isKeyHotkey, isHotkey } from 'is-hotkey';
|
|
7
8
|
import * as i2 from '@angular/common';
|
|
8
9
|
import { BrowserModule } from '@angular/platform-browser';
|
|
9
|
-
import { isKeyHotkey } from 'is-hotkey';
|
|
10
10
|
|
|
11
11
|
// record richtext type status
|
|
12
12
|
const FLUSHING = new WeakMap();
|
|
@@ -340,7 +340,8 @@ function setNode(board, props, path) {
|
|
|
340
340
|
board.apply(operation);
|
|
341
341
|
}
|
|
342
342
|
function removeNode(board, path) {
|
|
343
|
-
const
|
|
343
|
+
const node = PlaitNode.get(board, path);
|
|
344
|
+
const operation = { type: 'remove_node', path, node };
|
|
344
345
|
board.apply(operation);
|
|
345
346
|
}
|
|
346
347
|
function moveNode(board, path, newPath) {
|
|
@@ -394,9 +395,16 @@ function createBoard(host, children, options) {
|
|
|
394
395
|
},
|
|
395
396
|
children,
|
|
396
397
|
operations: [],
|
|
398
|
+
history: {
|
|
399
|
+
redos: [],
|
|
400
|
+
undos: []
|
|
401
|
+
},
|
|
397
402
|
selection: null,
|
|
398
403
|
cursor: BaseCursorStatus.select,
|
|
399
404
|
readonly: options.readonly,
|
|
405
|
+
allowClearBoard: options.allowClearBoard,
|
|
406
|
+
undo: () => { },
|
|
407
|
+
redo: () => { },
|
|
400
408
|
apply: (operation) => {
|
|
401
409
|
board.operations.push(operation);
|
|
402
410
|
Transforms.transform(board, operation);
|
|
@@ -536,8 +544,392 @@ function withSelection(board) {
|
|
|
536
544
|
const isSetViewportOperation = (value) => {
|
|
537
545
|
return value.type === 'set_viewport';
|
|
538
546
|
};
|
|
547
|
+
const inverse = (op) => {
|
|
548
|
+
switch (op.type) {
|
|
549
|
+
case 'insert_node': {
|
|
550
|
+
return { ...op, type: 'remove_node' };
|
|
551
|
+
}
|
|
552
|
+
case 'remove_node': {
|
|
553
|
+
return { ...op, type: 'insert_node' };
|
|
554
|
+
}
|
|
555
|
+
case 'move_node': {
|
|
556
|
+
const { newPath, path } = op;
|
|
557
|
+
// PERF: in this case the move operation is a no-op anyways.
|
|
558
|
+
if (Path.equals(newPath, path)) {
|
|
559
|
+
return op;
|
|
560
|
+
}
|
|
561
|
+
// If the move happens completely within a single parent the path and
|
|
562
|
+
// newPath are stable with respect to each other.
|
|
563
|
+
if (Path.isSibling(path, newPath)) {
|
|
564
|
+
return { ...op, path: newPath, newPath: path };
|
|
565
|
+
}
|
|
566
|
+
// If the move does not happen within a single parent it is possible
|
|
567
|
+
// for the move to impact the true path to the location where the node
|
|
568
|
+
// was removed from and where it was inserted. We have to adjust for this
|
|
569
|
+
// and find the original path. We can accomplish this (only in non-sibling)
|
|
570
|
+
// moves by looking at the impact of the move operation on the node
|
|
571
|
+
// after the original move path.
|
|
572
|
+
const inversePath = Path.transform(path, op);
|
|
573
|
+
const inverseNewPath = Path.transform(Path.next(path), op);
|
|
574
|
+
return { ...op, path: inversePath, newPath: inverseNewPath };
|
|
575
|
+
}
|
|
576
|
+
case 'set_node': {
|
|
577
|
+
const { properties, newProperties } = op;
|
|
578
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
579
|
+
}
|
|
580
|
+
case 'set_selection': {
|
|
581
|
+
const { properties, newProperties } = op;
|
|
582
|
+
if (properties == null) {
|
|
583
|
+
return {
|
|
584
|
+
...op,
|
|
585
|
+
properties: newProperties,
|
|
586
|
+
newProperties: null
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
else if (newProperties == null) {
|
|
590
|
+
return {
|
|
591
|
+
...op,
|
|
592
|
+
properties: null,
|
|
593
|
+
newProperties: properties
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
case 'set_viewport': {
|
|
601
|
+
const { properties, newProperties } = op;
|
|
602
|
+
if (properties == null) {
|
|
603
|
+
return {
|
|
604
|
+
...op,
|
|
605
|
+
properties: newProperties,
|
|
606
|
+
newProperties: newProperties
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
else if (newProperties == null) {
|
|
610
|
+
return {
|
|
611
|
+
...op,
|
|
612
|
+
properties: properties,
|
|
613
|
+
newProperties: properties
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
};
|
|
539
622
|
const PlaitOperation = {
|
|
540
|
-
isSetViewportOperation
|
|
623
|
+
isSetViewportOperation,
|
|
624
|
+
inverse
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Extendable Custom Types Interface
|
|
629
|
+
*/
|
|
630
|
+
|
|
631
|
+
const IS_IOS = typeof navigator !== 'undefined' &&
|
|
632
|
+
typeof window !== 'undefined' &&
|
|
633
|
+
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
|
|
634
|
+
!window.MSStream;
|
|
635
|
+
const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
|
|
636
|
+
const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
|
637
|
+
const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
|
|
638
|
+
// "modern" Edge was released at 79.x
|
|
639
|
+
const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
|
|
640
|
+
const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
|
|
641
|
+
// Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
|
|
642
|
+
const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Hotkey mappings for each platform.
|
|
646
|
+
*/
|
|
647
|
+
const HOTKEYS = {
|
|
648
|
+
bold: 'mod+b',
|
|
649
|
+
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
650
|
+
moveBackward: 'left',
|
|
651
|
+
moveForward: 'right',
|
|
652
|
+
moveUp: 'up',
|
|
653
|
+
moveDown: 'down',
|
|
654
|
+
moveWordBackward: 'ctrl+left',
|
|
655
|
+
moveWordForward: 'ctrl+right',
|
|
656
|
+
deleteBackward: 'shift?+backspace',
|
|
657
|
+
deleteForward: 'shift?+delete',
|
|
658
|
+
extendBackward: 'shift+left',
|
|
659
|
+
extendForward: 'shift+right',
|
|
660
|
+
italic: 'mod+i',
|
|
661
|
+
splitBlock: 'shift?+enter',
|
|
662
|
+
undo: 'mod+z'
|
|
663
|
+
};
|
|
664
|
+
const APPLE_HOTKEYS = {
|
|
665
|
+
moveLineBackward: 'opt+up',
|
|
666
|
+
moveLineForward: 'opt+down',
|
|
667
|
+
moveWordBackward: 'opt+left',
|
|
668
|
+
moveWordForward: 'opt+right',
|
|
669
|
+
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
670
|
+
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
671
|
+
deleteLineBackward: 'cmd+shift?+backspace',
|
|
672
|
+
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
673
|
+
deleteWordBackward: 'opt+shift?+backspace',
|
|
674
|
+
deleteWordForward: 'opt+shift?+delete',
|
|
675
|
+
extendLineBackward: 'opt+shift+up',
|
|
676
|
+
extendLineForward: 'opt+shift+down',
|
|
677
|
+
redo: 'cmd+shift+z',
|
|
678
|
+
transposeCharacter: 'ctrl+t'
|
|
679
|
+
};
|
|
680
|
+
const WINDOWS_HOTKEYS = {
|
|
681
|
+
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
682
|
+
deleteWordForward: 'ctrl+shift?+delete',
|
|
683
|
+
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
684
|
+
};
|
|
685
|
+
/**
|
|
686
|
+
* Create a platform-aware hotkey checker.
|
|
687
|
+
*/
|
|
688
|
+
const create = (key) => {
|
|
689
|
+
const generic = HOTKEYS[key];
|
|
690
|
+
const apple = APPLE_HOTKEYS[key];
|
|
691
|
+
const windows = WINDOWS_HOTKEYS[key];
|
|
692
|
+
const isGeneric = generic && isKeyHotkey(generic);
|
|
693
|
+
const isApple = apple && isKeyHotkey(apple);
|
|
694
|
+
const isWindows = windows && isKeyHotkey(windows);
|
|
695
|
+
return (event) => {
|
|
696
|
+
if (isGeneric && isGeneric(event)) {
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
if (IS_APPLE && isApple && isApple(event)) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
if (!IS_APPLE && isWindows && isWindows(event)) {
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
return false;
|
|
706
|
+
};
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* Hotkeys.
|
|
710
|
+
*/
|
|
711
|
+
const hotkeys = {
|
|
712
|
+
isBold: create('bold'),
|
|
713
|
+
isCompose: create('compose'),
|
|
714
|
+
isMoveBackward: create('moveBackward'),
|
|
715
|
+
isMoveForward: create('moveForward'),
|
|
716
|
+
isMoveUp: create('moveUp'),
|
|
717
|
+
isMoveDown: create('moveDown'),
|
|
718
|
+
isDeleteBackward: create('deleteBackward'),
|
|
719
|
+
isDeleteForward: create('deleteForward'),
|
|
720
|
+
isDeleteLineBackward: create('deleteLineBackward'),
|
|
721
|
+
isDeleteLineForward: create('deleteLineForward'),
|
|
722
|
+
isDeleteWordBackward: create('deleteWordBackward'),
|
|
723
|
+
isDeleteWordForward: create('deleteWordForward'),
|
|
724
|
+
isExtendBackward: create('extendBackward'),
|
|
725
|
+
isExtendForward: create('extendForward'),
|
|
726
|
+
isExtendLineBackward: create('extendLineBackward'),
|
|
727
|
+
isExtendLineForward: create('extendLineForward'),
|
|
728
|
+
isItalic: create('italic'),
|
|
729
|
+
isMoveLineBackward: create('moveLineBackward'),
|
|
730
|
+
isMoveLineForward: create('moveLineForward'),
|
|
731
|
+
isMoveWordBackward: create('moveWordBackward'),
|
|
732
|
+
isMoveWordForward: create('moveWordForward'),
|
|
733
|
+
isRedo: create('redo'),
|
|
734
|
+
isSplitBlock: create('splitBlock'),
|
|
735
|
+
isTransposeCharacter: create('transposeCharacter'),
|
|
736
|
+
isUndo: create('undo')
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
function idCreator(length = 5) {
|
|
740
|
+
// remove numeral
|
|
741
|
+
const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
|
|
742
|
+
const maxPosition = $chars.length;
|
|
743
|
+
let key = '';
|
|
744
|
+
for (let i = 0; i < length; i++) {
|
|
745
|
+
key += $chars.charAt(Math.floor(Math.random() * maxPosition));
|
|
746
|
+
}
|
|
747
|
+
return key;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// https://stackoverflow.com/a/6853926/232122
|
|
751
|
+
function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
|
|
752
|
+
const A = x - x1;
|
|
753
|
+
const B = y - y1;
|
|
754
|
+
const C = x2 - x1;
|
|
755
|
+
const D = y2 - y1;
|
|
756
|
+
const dot = A * C + B * D;
|
|
757
|
+
const lenSquare = C * C + D * D;
|
|
758
|
+
let param = -1;
|
|
759
|
+
if (lenSquare !== 0) {
|
|
760
|
+
// in case of 0 length line
|
|
761
|
+
param = dot / lenSquare;
|
|
762
|
+
}
|
|
763
|
+
let xx, yy;
|
|
764
|
+
if (param < 0) {
|
|
765
|
+
xx = x1;
|
|
766
|
+
yy = y1;
|
|
767
|
+
}
|
|
768
|
+
else if (param > 1) {
|
|
769
|
+
xx = x2;
|
|
770
|
+
yy = y2;
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
xx = x1 + param * C;
|
|
774
|
+
yy = y1 + param * D;
|
|
775
|
+
}
|
|
776
|
+
const dx = x - xx;
|
|
777
|
+
const dy = y - yy;
|
|
778
|
+
return Math.hypot(dx, dy);
|
|
779
|
+
}
|
|
780
|
+
function rotate(x1, y1, x2, y2, angle) {
|
|
781
|
+
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
|
782
|
+
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
|
783
|
+
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
|
784
|
+
return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Check whether to merge an operation into the previous operation.
|
|
789
|
+
*/
|
|
790
|
+
const shouldMerge = (op, prev) => {
|
|
791
|
+
if (op.type === 'set_viewport') {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
return false;
|
|
795
|
+
};
|
|
796
|
+
/**
|
|
797
|
+
* Check whether an operation needs to be saved to the history.
|
|
798
|
+
*/
|
|
799
|
+
const shouldSave = (op, prev) => {
|
|
800
|
+
if (op.type === 'set_selection') {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
return true;
|
|
804
|
+
};
|
|
805
|
+
/**
|
|
806
|
+
* Check whether an operation should clear the redos stack.
|
|
807
|
+
*/
|
|
808
|
+
const shouldClear = (op) => {
|
|
809
|
+
if (op.type === 'set_selection') {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
return true;
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
function withHistroy(board) {
|
|
816
|
+
const { apply, keydown } = board;
|
|
817
|
+
board.history = { undos: [], redos: [] };
|
|
818
|
+
board.redo = () => {
|
|
819
|
+
const { history } = board;
|
|
820
|
+
const { redos } = history;
|
|
821
|
+
if (redos.length > 0) {
|
|
822
|
+
const batch = redos[redos.length - 1];
|
|
823
|
+
PlaitHistoryBoard.withoutSaving(board, () => {
|
|
824
|
+
for (const op of batch) {
|
|
825
|
+
board.apply(op);
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
history.redos.pop();
|
|
829
|
+
history.undos.push(batch);
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
board.undo = () => {
|
|
833
|
+
const { history } = board;
|
|
834
|
+
const { undos } = history;
|
|
835
|
+
if (undos.length > 0) {
|
|
836
|
+
const batch = undos[undos.length - 1];
|
|
837
|
+
PlaitHistoryBoard.withoutSaving(board, () => {
|
|
838
|
+
const inverseOps = batch.map(PlaitOperation.inverse).reverse();
|
|
839
|
+
for (const op of inverseOps) {
|
|
840
|
+
board.apply(op);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
history.redos.push(batch);
|
|
844
|
+
history.undos.pop();
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
board.apply = (op) => {
|
|
848
|
+
const { operations, history } = board;
|
|
849
|
+
const { undos } = history;
|
|
850
|
+
const lastBatch = undos[undos.length - 1];
|
|
851
|
+
const lastOp = lastBatch && lastBatch[lastBatch.length - 1];
|
|
852
|
+
let save = PlaitHistoryBoard.isSaving(board);
|
|
853
|
+
let merge = PlaitHistoryBoard.isMerging(board);
|
|
854
|
+
if (save == null) {
|
|
855
|
+
save = shouldSave(op, lastOp);
|
|
856
|
+
}
|
|
857
|
+
if (save) {
|
|
858
|
+
if (merge == null) {
|
|
859
|
+
if (lastBatch == null) {
|
|
860
|
+
merge = false;
|
|
861
|
+
}
|
|
862
|
+
else if (operations.length !== 0) {
|
|
863
|
+
merge = true;
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
merge = shouldMerge(op, lastOp);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (lastBatch && merge) {
|
|
870
|
+
lastBatch.push(op);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
const batch = [op];
|
|
874
|
+
undos.push(batch);
|
|
875
|
+
}
|
|
876
|
+
while (undos.length > 100) {
|
|
877
|
+
undos.shift();
|
|
878
|
+
}
|
|
879
|
+
if (shouldClear(op)) {
|
|
880
|
+
history.redos = [];
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
apply(op);
|
|
884
|
+
};
|
|
885
|
+
board.keydown = (event) => {
|
|
886
|
+
if (isHotkey('mod+z', event)) {
|
|
887
|
+
board.undo();
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (isHotkey('mod+shift+z', event)) {
|
|
891
|
+
board.redo();
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
keydown(event);
|
|
895
|
+
};
|
|
896
|
+
return board;
|
|
897
|
+
}
|
|
898
|
+
const SAVING = new WeakMap();
|
|
899
|
+
const MERGING = new WeakMap();
|
|
900
|
+
const PlaitHistoryBoard = {
|
|
901
|
+
/**
|
|
902
|
+
* Get the saving flag's current value.
|
|
903
|
+
*/
|
|
904
|
+
isSaving(board) {
|
|
905
|
+
return SAVING.get(board);
|
|
906
|
+
},
|
|
907
|
+
/**
|
|
908
|
+
* Get the merge flag's current value.
|
|
909
|
+
*/
|
|
910
|
+
isMerging(board) {
|
|
911
|
+
return MERGING.get(board);
|
|
912
|
+
},
|
|
913
|
+
/**
|
|
914
|
+
* Apply a series of changes inside a synchronous `fn`, without merging any of
|
|
915
|
+
* the new operations into previous save point in the history.
|
|
916
|
+
*/
|
|
917
|
+
withoutMerging(editor, fn) {
|
|
918
|
+
const prev = PlaitHistoryBoard.isMerging(editor);
|
|
919
|
+
MERGING.set(editor, false);
|
|
920
|
+
fn();
|
|
921
|
+
MERGING.set(editor, prev);
|
|
922
|
+
},
|
|
923
|
+
/**
|
|
924
|
+
* Apply a series of changes inside a synchronous `fn`, without saving any of
|
|
925
|
+
* their operations into the history.
|
|
926
|
+
*/
|
|
927
|
+
withoutSaving(editor, fn) {
|
|
928
|
+
const prev = PlaitHistoryBoard.isSaving(editor);
|
|
929
|
+
SAVING.set(editor, false);
|
|
930
|
+
fn();
|
|
931
|
+
SAVING.set(editor, prev);
|
|
932
|
+
}
|
|
541
933
|
};
|
|
542
934
|
|
|
543
935
|
class PlaitElementComponent {
|
|
@@ -617,6 +1009,7 @@ class PlaitBoardComponent {
|
|
|
617
1009
|
this.plaitValue = [];
|
|
618
1010
|
this.plaitPlugins = [];
|
|
619
1011
|
this.plaitReadonly = false;
|
|
1012
|
+
this.plaitAllowClearBoard = false;
|
|
620
1013
|
this.plaitChange = new EventEmitter();
|
|
621
1014
|
this.plaitBoardInitialized = new EventEmitter();
|
|
622
1015
|
this.trackBy = (index, element) => {
|
|
@@ -654,8 +1047,8 @@ class PlaitBoardComponent {
|
|
|
654
1047
|
this.plaitBoardInitialized.emit(this.board);
|
|
655
1048
|
}
|
|
656
1049
|
initializePlugins() {
|
|
657
|
-
const options = { readonly: this.plaitReadonly };
|
|
658
|
-
let board = withSelection(withBoard(createBoard(this.host, this.plaitValue, options)));
|
|
1050
|
+
const options = { readonly: this.plaitReadonly, allowClearBoard: this.plaitAllowClearBoard };
|
|
1051
|
+
let board = withHistroy(withSelection(withBoard(createBoard(this.host, this.plaitValue, options))));
|
|
659
1052
|
this.plaitPlugins.forEach(plugin => {
|
|
660
1053
|
board = plugin(board);
|
|
661
1054
|
});
|
|
@@ -713,11 +1106,14 @@ class PlaitBoardComponent {
|
|
|
713
1106
|
this.board?.keyup(event);
|
|
714
1107
|
});
|
|
715
1108
|
window.onresize = () => {
|
|
716
|
-
|
|
717
|
-
const viewBoxValues = this.host.getAttribute('viewBox')?.split(',');
|
|
718
|
-
this.renderer2.setAttribute(this.host, 'viewBox', `${viewBoxValues[0].trim()}, ${viewBoxValues[1].trim()}, ${viewBoxModel.width}, ${viewBoxModel.height}`);
|
|
1109
|
+
this.refreshViewport();
|
|
719
1110
|
};
|
|
720
1111
|
}
|
|
1112
|
+
refreshViewport() {
|
|
1113
|
+
const viewBoxModel = getViewBox(this.board);
|
|
1114
|
+
const viewBoxValues = this.host.getAttribute('viewBox')?.split(',');
|
|
1115
|
+
this.renderer2.setAttribute(this.host, 'viewBox', `${viewBoxValues[0].trim()}, ${viewBoxValues[1].trim()}, ${viewBoxModel.width}, ${viewBoxModel.height}`);
|
|
1116
|
+
}
|
|
721
1117
|
updateViewport() {
|
|
722
1118
|
this.zoom = Math.floor(this.board.viewport.zoom * 100);
|
|
723
1119
|
const viewBox = getViewBox(this.board);
|
|
@@ -753,7 +1149,7 @@ class PlaitBoardComponent {
|
|
|
753
1149
|
}
|
|
754
1150
|
}
|
|
755
1151
|
PlaitBoardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitBoardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
|
|
756
|
-
PlaitBoardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: PlaitBoardComponent, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins", plaitReadonly: "plaitReadonly" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass" } }, viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }], ngImport: i0, template: `
|
|
1152
|
+
PlaitBoardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: PlaitBoardComponent, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins", plaitReadonly: "plaitReadonly", plaitAllowClearBoard: "plaitAllowClearBoard" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass" } }, viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }], ngImport: i0, template: `
|
|
757
1153
|
<svg #svg width="100%" height="100%" style="position: relative"></svg>
|
|
758
1154
|
<div *ngIf="isFocused" class="plait-toolbar island zoom-toolbar plait-board-attached">
|
|
759
1155
|
<button class="item" (mousedown)="zoomOut($event)">-</button>
|
|
@@ -809,6 +1205,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
|
|
|
809
1205
|
type: Input
|
|
810
1206
|
}], plaitReadonly: [{
|
|
811
1207
|
type: Input
|
|
1208
|
+
}], plaitAllowClearBoard: [{
|
|
1209
|
+
type: Input
|
|
812
1210
|
}], plaitChange: [{
|
|
813
1211
|
type: Output
|
|
814
1212
|
}], plaitBoardInitialized: [{
|
|
@@ -829,166 +1227,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImpo
|
|
|
829
1227
|
}]
|
|
830
1228
|
}] });
|
|
831
1229
|
|
|
832
|
-
const IS_IOS = typeof navigator !== 'undefined' &&
|
|
833
|
-
typeof window !== 'undefined' &&
|
|
834
|
-
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
|
|
835
|
-
!window.MSStream;
|
|
836
|
-
const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
|
|
837
|
-
const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
|
838
|
-
const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
|
|
839
|
-
// "modern" Edge was released at 79.x
|
|
840
|
-
const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
|
|
841
|
-
const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
|
|
842
|
-
// Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
|
|
843
|
-
const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
|
|
844
|
-
|
|
845
|
-
/**
|
|
846
|
-
* Hotkey mappings for each platform.
|
|
847
|
-
*/
|
|
848
|
-
const HOTKEYS = {
|
|
849
|
-
bold: 'mod+b',
|
|
850
|
-
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
851
|
-
moveBackward: 'left',
|
|
852
|
-
moveForward: 'right',
|
|
853
|
-
moveUp: 'up',
|
|
854
|
-
moveDown: 'down',
|
|
855
|
-
moveWordBackward: 'ctrl+left',
|
|
856
|
-
moveWordForward: 'ctrl+right',
|
|
857
|
-
deleteBackward: 'shift?+backspace',
|
|
858
|
-
deleteForward: 'shift?+delete',
|
|
859
|
-
extendBackward: 'shift+left',
|
|
860
|
-
extendForward: 'shift+right',
|
|
861
|
-
italic: 'mod+i',
|
|
862
|
-
splitBlock: 'shift?+enter',
|
|
863
|
-
undo: 'mod+z'
|
|
864
|
-
};
|
|
865
|
-
const APPLE_HOTKEYS = {
|
|
866
|
-
moveLineBackward: 'opt+up',
|
|
867
|
-
moveLineForward: 'opt+down',
|
|
868
|
-
moveWordBackward: 'opt+left',
|
|
869
|
-
moveWordForward: 'opt+right',
|
|
870
|
-
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
871
|
-
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
872
|
-
deleteLineBackward: 'cmd+shift?+backspace',
|
|
873
|
-
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
874
|
-
deleteWordBackward: 'opt+shift?+backspace',
|
|
875
|
-
deleteWordForward: 'opt+shift?+delete',
|
|
876
|
-
extendLineBackward: 'opt+shift+up',
|
|
877
|
-
extendLineForward: 'opt+shift+down',
|
|
878
|
-
redo: 'cmd+shift+z',
|
|
879
|
-
transposeCharacter: 'ctrl+t'
|
|
880
|
-
};
|
|
881
|
-
const WINDOWS_HOTKEYS = {
|
|
882
|
-
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
883
|
-
deleteWordForward: 'ctrl+shift?+delete',
|
|
884
|
-
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
885
|
-
};
|
|
886
|
-
/**
|
|
887
|
-
* Create a platform-aware hotkey checker.
|
|
888
|
-
*/
|
|
889
|
-
const create = (key) => {
|
|
890
|
-
const generic = HOTKEYS[key];
|
|
891
|
-
const apple = APPLE_HOTKEYS[key];
|
|
892
|
-
const windows = WINDOWS_HOTKEYS[key];
|
|
893
|
-
const isGeneric = generic && isKeyHotkey(generic);
|
|
894
|
-
const isApple = apple && isKeyHotkey(apple);
|
|
895
|
-
const isWindows = windows && isKeyHotkey(windows);
|
|
896
|
-
return (event) => {
|
|
897
|
-
if (isGeneric && isGeneric(event)) {
|
|
898
|
-
return true;
|
|
899
|
-
}
|
|
900
|
-
if (IS_APPLE && isApple && isApple(event)) {
|
|
901
|
-
return true;
|
|
902
|
-
}
|
|
903
|
-
if (!IS_APPLE && isWindows && isWindows(event)) {
|
|
904
|
-
return true;
|
|
905
|
-
}
|
|
906
|
-
return false;
|
|
907
|
-
};
|
|
908
|
-
};
|
|
909
|
-
/**
|
|
910
|
-
* Hotkeys.
|
|
911
|
-
*/
|
|
912
|
-
const hotkeys = {
|
|
913
|
-
isBold: create('bold'),
|
|
914
|
-
isCompose: create('compose'),
|
|
915
|
-
isMoveBackward: create('moveBackward'),
|
|
916
|
-
isMoveForward: create('moveForward'),
|
|
917
|
-
isMoveUp: create('moveUp'),
|
|
918
|
-
isMoveDown: create('moveDown'),
|
|
919
|
-
isDeleteBackward: create('deleteBackward'),
|
|
920
|
-
isDeleteForward: create('deleteForward'),
|
|
921
|
-
isDeleteLineBackward: create('deleteLineBackward'),
|
|
922
|
-
isDeleteLineForward: create('deleteLineForward'),
|
|
923
|
-
isDeleteWordBackward: create('deleteWordBackward'),
|
|
924
|
-
isDeleteWordForward: create('deleteWordForward'),
|
|
925
|
-
isExtendBackward: create('extendBackward'),
|
|
926
|
-
isExtendForward: create('extendForward'),
|
|
927
|
-
isExtendLineBackward: create('extendLineBackward'),
|
|
928
|
-
isExtendLineForward: create('extendLineForward'),
|
|
929
|
-
isItalic: create('italic'),
|
|
930
|
-
isMoveLineBackward: create('moveLineBackward'),
|
|
931
|
-
isMoveLineForward: create('moveLineForward'),
|
|
932
|
-
isMoveWordBackward: create('moveWordBackward'),
|
|
933
|
-
isMoveWordForward: create('moveWordForward'),
|
|
934
|
-
isRedo: create('redo'),
|
|
935
|
-
isSplitBlock: create('splitBlock'),
|
|
936
|
-
isTransposeCharacter: create('transposeCharacter'),
|
|
937
|
-
isUndo: create('undo')
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
function idCreator(length = 5) {
|
|
941
|
-
// remove numeral
|
|
942
|
-
const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
|
|
943
|
-
const maxPosition = $chars.length;
|
|
944
|
-
let key = '';
|
|
945
|
-
for (let i = 0; i < length; i++) {
|
|
946
|
-
key += $chars.charAt(Math.floor(Math.random() * maxPosition));
|
|
947
|
-
}
|
|
948
|
-
return key;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// https://stackoverflow.com/a/6853926/232122
|
|
952
|
-
function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
|
|
953
|
-
const A = x - x1;
|
|
954
|
-
const B = y - y1;
|
|
955
|
-
const C = x2 - x1;
|
|
956
|
-
const D = y2 - y1;
|
|
957
|
-
const dot = A * C + B * D;
|
|
958
|
-
const lenSquare = C * C + D * D;
|
|
959
|
-
let param = -1;
|
|
960
|
-
if (lenSquare !== 0) {
|
|
961
|
-
// in case of 0 length line
|
|
962
|
-
param = dot / lenSquare;
|
|
963
|
-
}
|
|
964
|
-
let xx, yy;
|
|
965
|
-
if (param < 0) {
|
|
966
|
-
xx = x1;
|
|
967
|
-
yy = y1;
|
|
968
|
-
}
|
|
969
|
-
else if (param > 1) {
|
|
970
|
-
xx = x2;
|
|
971
|
-
yy = y2;
|
|
972
|
-
}
|
|
973
|
-
else {
|
|
974
|
-
xx = x1 + param * C;
|
|
975
|
-
yy = y1 + param * D;
|
|
976
|
-
}
|
|
977
|
-
const dx = x - xx;
|
|
978
|
-
const dy = y - yy;
|
|
979
|
-
return Math.hypot(dx, dy);
|
|
980
|
-
}
|
|
981
|
-
function rotate(x1, y1, x2, y2, angle) {
|
|
982
|
-
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
|
983
|
-
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
|
984
|
-
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
|
985
|
-
return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
/**
|
|
989
|
-
* Extendable Custom Types Interface
|
|
990
|
-
*/
|
|
991
|
-
|
|
992
1230
|
/*
|
|
993
1231
|
* Public API Surface of plait
|
|
994
1232
|
*/
|
|
@@ -997,5 +1235,5 @@ function rotate(x1, y1, x2, y2, angle) {
|
|
|
997
1235
|
* Generated bundle index. Do not edit.
|
|
998
1236
|
*/
|
|
999
1237
|
|
|
1000
|
-
export { BOARD_TO_ON_CHANGE, BaseCursorStatus, FLUSHING, HOST_TO_ROUGH_SVG, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_SAFARI, IS_TEXT_EDITABLE, NS, Path, PlaitBoardComponent, PlaitElementComponent, PlaitModule, PlaitNode, PlaitOperation, Transforms, Viewport, createG, createSVG, createText, distanceBetweenPointAndSegment, getViewBox, hotkeys, idCreator, isNoSelectionElement, isNullOrUndefined, isSetViewportOperation, rotate, toPoint, toRectangleClient, transformPoint, transformPoints };
|
|
1238
|
+
export { BOARD_TO_ON_CHANGE, BaseCursorStatus, FLUSHING, HOST_TO_ROUGH_SVG, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_SAFARI, IS_TEXT_EDITABLE, NS, Path, PlaitBoardComponent, PlaitElementComponent, PlaitModule, PlaitNode, PlaitOperation, Transforms, Viewport, createG, createSVG, createText, distanceBetweenPointAndSegment, getViewBox, hotkeys, idCreator, inverse, isNoSelectionElement, isNullOrUndefined, isSetViewportOperation, rotate, shouldClear, shouldMerge, shouldSave, toPoint, toRectangleClient, transformPoint, transformPoints };
|
|
1001
1239
|
//# sourceMappingURL=plait-core.mjs.map
|