@plait/core 0.24.0-next.4 → 0.24.0-next.6
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/esm2022/board/board.component.mjs +2 -1
- package/esm2022/utils/dom/common.mjs +9 -1
- package/esm2022/utils/draw/rectangle.mjs +3 -5
- package/esm2022/utils/element.mjs +25 -7
- package/fesm2022/plait-core.mjs +1889 -1866
- package/fesm2022/plait-core.mjs.map +1 -1
- package/package.json +1 -1
- package/utils/dom/common.d.ts +2 -0
- package/utils/element.d.ts +5 -1
package/fesm2022/plait-core.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import { Directive, Input, Injectable, Component, ChangeDetectionStrategy, Event
|
|
|
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
|
-
import produce, { createDraft, finishDraft, isDraft } from 'immer';
|
|
7
6
|
import { isKeyHotkey, isHotkey } from 'is-hotkey';
|
|
7
|
+
import produce, { createDraft, finishDraft, isDraft } from 'immer';
|
|
8
8
|
import * as i1 from '@angular/common';
|
|
9
9
|
import { CommonModule } from '@angular/common';
|
|
10
10
|
|
|
@@ -30,6 +30,16 @@ const BOARD_TO_MOVING_ELEMENT = new WeakMap();
|
|
|
30
30
|
const IS_PREVENT_TOUCH_MOVE = new WeakMap();
|
|
31
31
|
const PATH_REFS = new WeakMap();
|
|
32
32
|
|
|
33
|
+
var PlaitPointerType;
|
|
34
|
+
(function (PlaitPointerType) {
|
|
35
|
+
PlaitPointerType["hand"] = "hand";
|
|
36
|
+
PlaitPointerType["selection"] = "selection";
|
|
37
|
+
})(PlaitPointerType || (PlaitPointerType = {}));
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extendable Custom Types Interface
|
|
41
|
+
*/
|
|
42
|
+
|
|
33
43
|
function depthFirstRecursion(node, callback, recursion, isReverse) {
|
|
34
44
|
if (!recursion || recursion(node)) {
|
|
35
45
|
let children = [];
|
|
@@ -54,2068 +64,2080 @@ const getIsRecursionFunc = (board) => {
|
|
|
54
64
|
};
|
|
55
65
|
};
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
const calcRectangleClient = (node) => {
|
|
65
|
-
const nodeRectangle = board.getRectangle(node);
|
|
66
|
-
if (nodeRectangle) {
|
|
67
|
-
boundaryBox.left = Math.min(boundaryBox.left, nodeRectangle.x);
|
|
68
|
-
boundaryBox.top = Math.min(boundaryBox.top, nodeRectangle.y);
|
|
69
|
-
boundaryBox.right = Math.max(boundaryBox.right, nodeRectangle.x + nodeRectangle.width);
|
|
70
|
-
boundaryBox.bottom = Math.max(boundaryBox.bottom, nodeRectangle.y + nodeRectangle.height);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
elements.forEach(element => {
|
|
74
|
-
if (recursion) {
|
|
75
|
-
depthFirstRecursion(element, node => calcRectangleClient(node), node => board.isRecursion(node));
|
|
67
|
+
const SELECTION_BORDER_COLOR = '#6698FF';
|
|
68
|
+
const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
|
|
69
|
+
const Selection = {
|
|
70
|
+
isCollapsed(selection) {
|
|
71
|
+
if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
|
|
72
|
+
return true;
|
|
76
73
|
}
|
|
77
74
|
else {
|
|
78
|
-
|
|
75
|
+
return false;
|
|
79
76
|
}
|
|
80
|
-
});
|
|
81
|
-
if (boundaryBox.left === Number.MAX_VALUE) {
|
|
82
|
-
return {
|
|
83
|
-
x: 0,
|
|
84
|
-
y: 0,
|
|
85
|
-
width: 0,
|
|
86
|
-
height: 0
|
|
87
|
-
};
|
|
88
77
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
function getBoardRectangle(board) {
|
|
97
|
-
return getRectangleByElements(board, board.children, true);
|
|
98
|
-
}
|
|
99
|
-
function getElementById(board, id) {
|
|
100
|
-
let element = null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const getHitElements = (board, selection, match = () => true) => {
|
|
81
|
+
const realSelection = selection || board.selection;
|
|
82
|
+
const selectedElements = [];
|
|
83
|
+
const isCollapsed = realSelection && realSelection.ranges.length === 1 && Selection.isCollapsed(realSelection.ranges[0]);
|
|
101
84
|
depthFirstRecursion(board, node => {
|
|
102
|
-
if (
|
|
103
|
-
|
|
85
|
+
if (selectedElements.length > 0 && isCollapsed) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!PlaitBoard.isBoard(node) &&
|
|
89
|
+
match(node) &&
|
|
90
|
+
realSelection &&
|
|
91
|
+
realSelection.ranges.some(range => {
|
|
92
|
+
return board.isHitSelection(node, range);
|
|
93
|
+
})) {
|
|
94
|
+
selectedElements.push(node);
|
|
104
95
|
}
|
|
105
96
|
}, getIsRecursionFunc(board), true);
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
var ThemeColorMode;
|
|
110
|
-
(function (ThemeColorMode) {
|
|
111
|
-
ThemeColorMode["default"] = "default";
|
|
112
|
-
ThemeColorMode["colorful"] = "colorful";
|
|
113
|
-
ThemeColorMode["soft"] = "soft";
|
|
114
|
-
ThemeColorMode["retro"] = "retro";
|
|
115
|
-
ThemeColorMode["dark"] = "dark";
|
|
116
|
-
ThemeColorMode["starry"] = "starry";
|
|
117
|
-
})(ThemeColorMode || (ThemeColorMode = {}));
|
|
118
|
-
const DefaultThemeColor = {
|
|
119
|
-
mode: ThemeColorMode.default,
|
|
120
|
-
boardBackground: '#ffffff',
|
|
121
|
-
textColor: '#333333'
|
|
97
|
+
return selectedElements;
|
|
122
98
|
};
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
99
|
+
const getHitElementOfRoot = (board, rootElements, range) => {
|
|
100
|
+
const newRootElements = [...rootElements].reverse();
|
|
101
|
+
return newRootElements.find(item => {
|
|
102
|
+
return board.isHitSelection(item, range);
|
|
103
|
+
});
|
|
127
104
|
};
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
105
|
+
const isHitElements = (board, elements, ranges) => {
|
|
106
|
+
let isIntersectionElements = false;
|
|
107
|
+
if (elements.length) {
|
|
108
|
+
elements.map(item => {
|
|
109
|
+
if (!isIntersectionElements) {
|
|
110
|
+
isIntersectionElements = ranges.some(range => {
|
|
111
|
+
return board.isHitSelection(item, range);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return isIntersectionElements;
|
|
132
117
|
};
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
boardBackground: '#f9f8ed',
|
|
136
|
-
textColor: '#333333'
|
|
118
|
+
const cacheSelectedElements = (board, selectedElements) => {
|
|
119
|
+
BOARD_TO_SELECTED_ELEMENT.set(board, selectedElements);
|
|
137
120
|
};
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
boardBackground: '#141414',
|
|
141
|
-
textColor: '#FFFFFF'
|
|
121
|
+
const getSelectedElements = (board) => {
|
|
122
|
+
return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
|
|
142
123
|
};
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
textColor: '#FFFFFF'
|
|
124
|
+
const addSelectedElement = (board, element) => {
|
|
125
|
+
const selectedElements = getSelectedElements(board);
|
|
126
|
+
cacheSelectedElements(board, [...selectedElements, element]);
|
|
147
127
|
};
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const minX = origin.x < target.x ? origin.x : target.x;
|
|
160
|
-
const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
|
|
161
|
-
const minY = origin.y < target.y ? origin.y : target.y;
|
|
162
|
-
const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
|
|
163
|
-
// float calculate error( eg: 1.4210854715202004e-14 > 0)
|
|
164
|
-
if (Math.floor(maxX - minX - origin.width - target.width) <= 0 && Math.floor(maxY - minY - origin.height - target.height) <= 0) {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
toRectangleClient: (points) => {
|
|
172
|
-
const xArray = points.map(ele => ele[0]);
|
|
173
|
-
const yArray = points.map(ele => ele[1]);
|
|
174
|
-
const xMin = Math.min(...xArray);
|
|
175
|
-
const xMax = Math.max(...xArray);
|
|
176
|
-
const yMin = Math.min(...yArray);
|
|
177
|
-
const yMax = Math.max(...yArray);
|
|
178
|
-
const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
179
|
-
return rect;
|
|
180
|
-
},
|
|
181
|
-
getOutlineRectangle: (rectangle, offset) => {
|
|
182
|
-
return {
|
|
183
|
-
x: rectangle.x + offset,
|
|
184
|
-
y: rectangle.y + offset,
|
|
185
|
-
width: rectangle.width - offset * 2,
|
|
186
|
-
height: rectangle.height - offset * 2
|
|
187
|
-
};
|
|
188
|
-
},
|
|
189
|
-
isEqual: (rectangle, otherRectangle) => {
|
|
190
|
-
return (rectangle.x === otherRectangle.x &&
|
|
191
|
-
rectangle.y === otherRectangle.y &&
|
|
192
|
-
rectangle.width === otherRectangle.width &&
|
|
193
|
-
rectangle.height === otherRectangle.height);
|
|
194
|
-
},
|
|
195
|
-
getCornerPoints: (rectangle) => {
|
|
196
|
-
return [
|
|
197
|
-
[rectangle.x, rectangle.y],
|
|
198
|
-
[rectangle.x + rectangle.width, rectangle.y],
|
|
199
|
-
[rectangle.x + rectangle.width, rectangle.y + rectangle.height],
|
|
200
|
-
[rectangle.x, rectangle.y + rectangle.height]
|
|
201
|
-
];
|
|
202
|
-
},
|
|
203
|
-
getEdgeCenterPoints: (rectangle) => {
|
|
204
|
-
return [
|
|
205
|
-
[rectangle.x + rectangle.width / 2, rectangle.y],
|
|
206
|
-
[rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
|
|
207
|
-
[rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
|
|
208
|
-
[rectangle.x, rectangle.y + rectangle.height / 2]
|
|
209
|
-
];
|
|
210
|
-
}
|
|
128
|
+
const removeSelectedElement = (board, element) => {
|
|
129
|
+
const selectedElements = getSelectedElements(board);
|
|
130
|
+
const newSelectedElements = selectedElements.filter(value => value !== element);
|
|
131
|
+
cacheSelectedElements(board, newSelectedElements);
|
|
132
|
+
};
|
|
133
|
+
const clearSelectedElement = (board) => {
|
|
134
|
+
cacheSelectedElements(board, []);
|
|
135
|
+
};
|
|
136
|
+
const isSelectedElement = (board, element) => {
|
|
137
|
+
const selectedElements = getSelectedElements(board);
|
|
138
|
+
return !!selectedElements.find(value => value === element);
|
|
211
139
|
};
|
|
212
140
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
141
|
+
/**
|
|
142
|
+
* @license
|
|
143
|
+
* Copyright Google LLC All Rights Reserved.
|
|
144
|
+
*
|
|
145
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
146
|
+
* found in the LICENSE file at https://angular.io/license
|
|
147
|
+
*/
|
|
148
|
+
const MAC_ENTER = 3;
|
|
149
|
+
const BACKSPACE = 8;
|
|
150
|
+
const TAB = 9;
|
|
151
|
+
const NUM_CENTER = 12;
|
|
152
|
+
const ENTER = 13;
|
|
153
|
+
const SHIFT = 16;
|
|
154
|
+
const CONTROL = 17;
|
|
155
|
+
const ALT = 18;
|
|
156
|
+
const PAUSE = 19;
|
|
157
|
+
const CAPS_LOCK = 20;
|
|
158
|
+
const ESCAPE = 27;
|
|
159
|
+
const SPACE = 32;
|
|
160
|
+
const PAGE_UP = 33;
|
|
161
|
+
const PAGE_DOWN = 34;
|
|
162
|
+
const END = 35;
|
|
163
|
+
const HOME = 36;
|
|
164
|
+
const LEFT_ARROW = 37;
|
|
165
|
+
const UP_ARROW = 38;
|
|
166
|
+
const RIGHT_ARROW = 39;
|
|
167
|
+
const DOWN_ARROW = 40;
|
|
168
|
+
const PLUS_SIGN = 43;
|
|
169
|
+
const PRINT_SCREEN = 44;
|
|
170
|
+
const INSERT = 45;
|
|
171
|
+
const DELETE = 46;
|
|
172
|
+
const ZERO = 48;
|
|
173
|
+
const ONE = 49;
|
|
174
|
+
const TWO = 50;
|
|
175
|
+
const THREE = 51;
|
|
176
|
+
const FOUR = 52;
|
|
177
|
+
const FIVE = 53;
|
|
178
|
+
const SIX = 54;
|
|
179
|
+
const SEVEN = 55;
|
|
180
|
+
const EIGHT = 56;
|
|
181
|
+
const NINE = 57;
|
|
182
|
+
const FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
|
|
183
|
+
const FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
|
|
184
|
+
const QUESTION_MARK = 63;
|
|
185
|
+
const AT_SIGN = 64;
|
|
186
|
+
const A = 65;
|
|
187
|
+
const B = 66;
|
|
188
|
+
const C = 67;
|
|
189
|
+
const D = 68;
|
|
190
|
+
const E = 69;
|
|
191
|
+
const F = 70;
|
|
192
|
+
const G = 71;
|
|
193
|
+
const H = 72;
|
|
194
|
+
const I = 73;
|
|
195
|
+
const J = 74;
|
|
196
|
+
const K = 75;
|
|
197
|
+
const L = 76;
|
|
198
|
+
const M = 77;
|
|
199
|
+
const N = 78;
|
|
200
|
+
const O = 79;
|
|
201
|
+
const P = 80;
|
|
202
|
+
const Q = 81;
|
|
203
|
+
const R = 82;
|
|
204
|
+
const S = 83;
|
|
205
|
+
const T = 84;
|
|
206
|
+
const U = 85;
|
|
207
|
+
const V = 86;
|
|
208
|
+
const W = 87;
|
|
209
|
+
const X = 88;
|
|
210
|
+
const Y = 89;
|
|
211
|
+
const Z = 90;
|
|
212
|
+
const META = 91; // WIN_KEY_LEFT
|
|
213
|
+
const MAC_WK_CMD_LEFT = 91;
|
|
214
|
+
const MAC_WK_CMD_RIGHT = 93;
|
|
215
|
+
const CONTEXT_MENU = 93;
|
|
216
|
+
const NUMPAD_ZERO = 96;
|
|
217
|
+
const NUMPAD_ONE = 97;
|
|
218
|
+
const NUMPAD_TWO = 98;
|
|
219
|
+
const NUMPAD_THREE = 99;
|
|
220
|
+
const NUMPAD_FOUR = 100;
|
|
221
|
+
const NUMPAD_FIVE = 101;
|
|
222
|
+
const NUMPAD_SIX = 102;
|
|
223
|
+
const NUMPAD_SEVEN = 103;
|
|
224
|
+
const NUMPAD_EIGHT = 104;
|
|
225
|
+
const NUMPAD_NINE = 105;
|
|
226
|
+
const NUMPAD_MULTIPLY = 106;
|
|
227
|
+
const NUMPAD_PLUS = 107;
|
|
228
|
+
const NUMPAD_MINUS = 109;
|
|
229
|
+
const NUMPAD_PERIOD = 110;
|
|
230
|
+
const NUMPAD_DIVIDE = 111;
|
|
231
|
+
const F1 = 112;
|
|
232
|
+
const F2 = 113;
|
|
233
|
+
const F3 = 114;
|
|
234
|
+
const F4 = 115;
|
|
235
|
+
const F5 = 116;
|
|
236
|
+
const F6 = 117;
|
|
237
|
+
const F7 = 118;
|
|
238
|
+
const F8 = 119;
|
|
239
|
+
const F9 = 120;
|
|
240
|
+
const F10 = 121;
|
|
241
|
+
const F11 = 122;
|
|
242
|
+
const F12 = 123;
|
|
243
|
+
const NUM_LOCK = 144;
|
|
244
|
+
const SCROLL_LOCK = 145;
|
|
245
|
+
const FIRST_MEDIA = 166;
|
|
246
|
+
const FF_MINUS = 173;
|
|
247
|
+
const MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
|
|
248
|
+
const VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
|
|
249
|
+
const VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
|
|
250
|
+
const FF_MUTE = 181;
|
|
251
|
+
const FF_VOLUME_DOWN = 182;
|
|
252
|
+
const LAST_MEDIA = 183;
|
|
253
|
+
const FF_VOLUME_UP = 183;
|
|
254
|
+
const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
|
|
255
|
+
const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
|
|
256
|
+
const COMMA = 188;
|
|
257
|
+
const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
|
|
258
|
+
const PERIOD = 190;
|
|
259
|
+
const SLASH = 191;
|
|
260
|
+
const APOSTROPHE = 192;
|
|
261
|
+
const TILDE = 192;
|
|
262
|
+
const OPEN_SQUARE_BRACKET = 219;
|
|
263
|
+
const BACKSLASH = 220;
|
|
264
|
+
const CLOSE_SQUARE_BRACKET = 221;
|
|
265
|
+
const SINGLE_QUOTE = 222;
|
|
266
|
+
const MAC_META = 224;
|
|
267
|
+
|
|
268
|
+
var ResizeCursorClass;
|
|
269
|
+
(function (ResizeCursorClass) {
|
|
270
|
+
ResizeCursorClass["ew-resize"] = "ew-resize";
|
|
271
|
+
})(ResizeCursorClass || (ResizeCursorClass = {}));
|
|
272
|
+
|
|
273
|
+
const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
|
|
274
|
+
|
|
275
|
+
const CLIP_BOARD_FORMAT_KEY = 'x-plait-fragment';
|
|
276
|
+
const HOST_CLASS_NAME = 'plait-board-container';
|
|
277
|
+
const SCROLL_BAR_WIDTH = 20;
|
|
278
|
+
const MAX_RADIUS = 16;
|
|
279
|
+
const POINTER_BUTTON = {
|
|
280
|
+
MAIN: 0,
|
|
281
|
+
WHEEL: 1,
|
|
282
|
+
SECONDARY: 2,
|
|
283
|
+
TOUCH: -1
|
|
284
|
+
};
|
|
285
|
+
const PRESS_AND_MOVE_BUFFER = 5;
|
|
286
|
+
|
|
287
|
+
const NS = 'http://www.w3.org/2000/svg';
|
|
288
|
+
function toPoint(x, y, container) {
|
|
289
|
+
const rect = container.getBoundingClientRect();
|
|
290
|
+
return [x - rect.x, y - rect.y];
|
|
270
291
|
}
|
|
271
|
-
function
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
for (let i = 0; i < len - 1; i++) {
|
|
275
|
-
const p = points[i];
|
|
276
|
-
const p2 = points[i + 1];
|
|
277
|
-
const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
|
|
278
|
-
if (currentDistance < distance) {
|
|
279
|
-
distance = currentDistance;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return distance;
|
|
292
|
+
function createG() {
|
|
293
|
+
const newG = document.createElementNS(NS, 'g');
|
|
294
|
+
return newG;
|
|
283
295
|
}
|
|
284
|
-
function
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
function createPath() {
|
|
297
|
+
const newG = document.createElementNS(NS, 'path');
|
|
298
|
+
return newG;
|
|
299
|
+
}
|
|
300
|
+
function createRect(rectangle, options) {
|
|
301
|
+
const rect = document.createElementNS(NS, 'rect');
|
|
302
|
+
rect.setAttribute('x', `${rectangle.x}`);
|
|
303
|
+
rect.setAttribute('y', `${rectangle.y}`);
|
|
304
|
+
rect.setAttribute('width', `${rectangle.width}`);
|
|
305
|
+
rect.setAttribute('height', `${rectangle.height}`);
|
|
306
|
+
for (let key in options) {
|
|
307
|
+
const optionKey = key;
|
|
308
|
+
rect.setAttribute(key, `${options[optionKey]}`);
|
|
296
309
|
}
|
|
297
|
-
return
|
|
310
|
+
return rect;
|
|
298
311
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
const setStrokeLinecap = (g, value) => {
|
|
313
|
+
g.setAttribute('stroke-linecap', value);
|
|
314
|
+
};
|
|
315
|
+
const setPathStrokeLinecap = (g, value) => {
|
|
316
|
+
g.querySelectorAll('path').forEach(path => {
|
|
317
|
+
path.setAttribute('stroke-linecap', value);
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
function createMask() {
|
|
321
|
+
return document.createElementNS(NS, 'mask');
|
|
304
322
|
}
|
|
305
|
-
function
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
return Math.hypot(dx, dy);
|
|
323
|
+
function createSVG() {
|
|
324
|
+
const svg = document.createElementNS(NS, 'svg');
|
|
325
|
+
return svg;
|
|
309
326
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
327
|
+
function createText(x, y, fill, textContent) {
|
|
328
|
+
var text = document.createElementNS(NS, 'text');
|
|
329
|
+
text.setAttribute('x', `${x}`);
|
|
330
|
+
text.setAttribute('y', `${y}`);
|
|
331
|
+
text.setAttribute('fill', fill);
|
|
332
|
+
text.textContent = textContent;
|
|
333
|
+
return text;
|
|
315
334
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const ca = [a[0] - c[0], a[1] - c[1]];
|
|
322
|
-
const cb = [b[0] - c[0], b[1] - c[1]];
|
|
323
|
-
const cd = [d[0] - c[0], d[1] - c[1]];
|
|
324
|
-
return crossProduct(ab, ac) * crossProduct(ab, ad) <= 0 && crossProduct(cd, ca) * crossProduct(cd, cb) <= 0;
|
|
335
|
+
/**
|
|
336
|
+
* Check if a DOM node is an element node.
|
|
337
|
+
*/
|
|
338
|
+
const isDOMElement = (value) => {
|
|
339
|
+
return isDOMNode(value) && value.nodeType === 1;
|
|
325
340
|
};
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
isLineHitLine(points[i], points[i - 1], rectanglePoints[2], rectanglePoints[3]) ||
|
|
332
|
-
isLineHitLine(points[i], points[i - 1], rectanglePoints[3], rectanglePoints[0]);
|
|
333
|
-
if (isIntersect) {
|
|
334
|
-
return true;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return false;
|
|
341
|
+
/**
|
|
342
|
+
* Check if a value is a DOM node.
|
|
343
|
+
*/
|
|
344
|
+
const isDOMNode = (value) => {
|
|
345
|
+
return value instanceof window.Node;
|
|
338
346
|
};
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
let inside = false;
|
|
345
|
-
for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
346
|
-
let xi = points[i][0], yi = points[i][1];
|
|
347
|
-
let xj = points[j][0], yj = points[j][1];
|
|
348
|
-
let intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
349
|
-
if (intersect)
|
|
350
|
-
inside = !inside;
|
|
347
|
+
const hasInputOrTextareaTarget = (target) => {
|
|
348
|
+
if (isDOMElement(target)) {
|
|
349
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
351
352
|
}
|
|
352
|
-
return
|
|
353
|
+
return false;
|
|
353
354
|
};
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
const sinAngle = Math.sin(rotation);
|
|
357
|
-
const x1 = (point[0] - center[0]) * cosAngle + (point[1] - center[1]) * sinAngle;
|
|
358
|
-
const y1 = (point[1] - center[1]) * cosAngle - (point[0] - center[0]) * sinAngle;
|
|
359
|
-
return (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry) <= 1;
|
|
355
|
+
const isSecondaryPointer = (event) => {
|
|
356
|
+
return event.button === POINTER_BUTTON.SECONDARY;
|
|
360
357
|
};
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
const isInRectangle = point[0] >= rectX && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + height;
|
|
364
|
-
const handleLeftTop = point[0] >= rectX &&
|
|
365
|
-
point[0] <= rectX + radius &&
|
|
366
|
-
point[1] >= rectY &&
|
|
367
|
-
point[1] <= rectY + radius &&
|
|
368
|
-
Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + radius)) > radius;
|
|
369
|
-
const handleLeftBottom = point[0] >= rectX &&
|
|
370
|
-
point[0] <= rectX + radius &&
|
|
371
|
-
point[1] >= rectY + height &&
|
|
372
|
-
point[1] <= rectY + height - radius &&
|
|
373
|
-
Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + height - radius)) > radius;
|
|
374
|
-
const handleRightTop = point[0] >= rectX + width - radius &&
|
|
375
|
-
point[0] <= rectX + width &&
|
|
376
|
-
point[1] >= rectY &&
|
|
377
|
-
point[1] <= rectY + radius &&
|
|
378
|
-
Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + radius)) > radius;
|
|
379
|
-
const handleRightBottom = point[0] >= rectX + width - radius &&
|
|
380
|
-
point[0] <= rectX + width &&
|
|
381
|
-
point[1] >= rectY + height - radius &&
|
|
382
|
-
point[1] <= rectY + height &&
|
|
383
|
-
Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + height - radius)) > radius;
|
|
384
|
-
const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
|
|
385
|
-
return isInRectangle && !isInCorner;
|
|
358
|
+
const isMainPointer = (event) => {
|
|
359
|
+
return event.button === POINTER_BUTTON.MAIN;
|
|
386
360
|
};
|
|
387
361
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
362
|
+
function hasBeforeContextChange(value) {
|
|
363
|
+
if (value.beforeContextChange) {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
function hasOnContextChanged(value) {
|
|
369
|
+
if (value.onContextChanged) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
class PlaitPluginElementComponent {
|
|
376
|
+
set context(value) {
|
|
377
|
+
if (hasBeforeContextChange(this)) {
|
|
378
|
+
this.beforeContextChange(value);
|
|
393
379
|
}
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (parent == null) {
|
|
404
|
-
if (PlaitBoard.isBoard(child)) {
|
|
405
|
-
return path;
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
380
|
+
const previousContext = this._context;
|
|
381
|
+
this._context = value;
|
|
382
|
+
if (this.element) {
|
|
383
|
+
ELEMENT_TO_COMPONENT.set(this.element, this);
|
|
384
|
+
}
|
|
385
|
+
if (this.initialized) {
|
|
386
|
+
this.cdr.markForCheck();
|
|
387
|
+
if (hasOnContextChanged(this)) {
|
|
388
|
+
this.onContextChanged(value, previousContext);
|
|
410
389
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
if (PlaitElement.isRootElement(this.element) && this.element.children) {
|
|
393
|
+
this.g = createG();
|
|
394
|
+
this.rootG = createG();
|
|
395
|
+
this.rootG.append(this.g);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
this.g = createG();
|
|
414
399
|
}
|
|
415
|
-
path.unshift(i);
|
|
416
|
-
child = parent;
|
|
417
400
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
getRectangle(board) {
|
|
442
|
-
return getRectangleByElements(board, board.children, true);
|
|
443
|
-
},
|
|
444
|
-
getViewportContainer(board) {
|
|
445
|
-
return PlaitBoard.getHost(board).parentElement;
|
|
446
|
-
},
|
|
447
|
-
isFocus(board) {
|
|
448
|
-
return !!board.selection;
|
|
449
|
-
},
|
|
450
|
-
isReadonly(board) {
|
|
451
|
-
return board.options.readonly;
|
|
452
|
-
},
|
|
453
|
-
hasBeenTextEditing(board) {
|
|
454
|
-
return !!IS_TEXT_EDITABLE.get(board);
|
|
455
|
-
},
|
|
456
|
-
getPointer(board) {
|
|
457
|
-
return board.pointer;
|
|
458
|
-
},
|
|
459
|
-
isPointer(board, pointer) {
|
|
460
|
-
return board.pointer === pointer;
|
|
461
|
-
},
|
|
462
|
-
isInPointer(board, pointers) {
|
|
463
|
-
const point = board.pointer;
|
|
464
|
-
return pointers.includes(point);
|
|
465
|
-
},
|
|
466
|
-
getMovingPointInBoard(board) {
|
|
467
|
-
return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
|
|
468
|
-
},
|
|
469
|
-
isMovingPointInBoard(board) {
|
|
470
|
-
const point = BOARD_TO_MOVING_POINT.get(board);
|
|
471
|
-
const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
472
|
-
if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
|
|
473
|
-
return true;
|
|
401
|
+
}
|
|
402
|
+
get context() {
|
|
403
|
+
return this._context;
|
|
404
|
+
}
|
|
405
|
+
get element() {
|
|
406
|
+
return this.context && this.context.element;
|
|
407
|
+
}
|
|
408
|
+
get board() {
|
|
409
|
+
return this.context && this.context.board;
|
|
410
|
+
}
|
|
411
|
+
get selected() {
|
|
412
|
+
return this.context && this.context.selected;
|
|
413
|
+
}
|
|
414
|
+
get effect() {
|
|
415
|
+
return this.context && this.context.effect;
|
|
416
|
+
}
|
|
417
|
+
constructor(cdr) {
|
|
418
|
+
this.cdr = cdr;
|
|
419
|
+
this.initialized = false;
|
|
420
|
+
}
|
|
421
|
+
ngOnInit() {
|
|
422
|
+
if (this.element.type) {
|
|
423
|
+
(this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
|
|
474
424
|
}
|
|
475
|
-
|
|
476
|
-
},
|
|
477
|
-
getThemeColors(board) {
|
|
478
|
-
return (board.options.themeColors || ThemeColors);
|
|
425
|
+
this.initialized = true;
|
|
479
426
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
(function (PlaitPointerType) {
|
|
484
|
-
PlaitPointerType["hand"] = "hand";
|
|
485
|
-
PlaitPointerType["selection"] = "selection";
|
|
486
|
-
})(PlaitPointerType || (PlaitPointerType = {}));
|
|
487
|
-
|
|
488
|
-
function isNullOrUndefined(value) {
|
|
489
|
-
return value === null || value === undefined;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* 规范 point
|
|
493
|
-
* @param point
|
|
494
|
-
* @returns point
|
|
495
|
-
*/
|
|
496
|
-
function normalizePoint(point) {
|
|
497
|
-
return Array.isArray(point)
|
|
498
|
-
? {
|
|
499
|
-
x: point[0],
|
|
500
|
-
y: point[1]
|
|
427
|
+
ngOnDestroy() {
|
|
428
|
+
if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
|
|
429
|
+
ELEMENT_TO_COMPONENT.delete(this.element);
|
|
501
430
|
}
|
|
502
|
-
|
|
431
|
+
removeSelectedElement(this.board, this.element);
|
|
432
|
+
(this.rootG || this.g).remove();
|
|
433
|
+
}
|
|
434
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
435
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
|
|
503
436
|
}
|
|
437
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
|
|
438
|
+
type: Directive
|
|
439
|
+
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { context: [{
|
|
440
|
+
type: Input
|
|
441
|
+
}] } });
|
|
442
|
+
const ELEMENT_TO_COMPONENT = new WeakMap();
|
|
504
443
|
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
444
|
+
const RectangleClient = {
|
|
445
|
+
isHit: (origin, target) => {
|
|
446
|
+
const minX = origin.x < target.x ? origin.x : target.x;
|
|
447
|
+
const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
|
|
448
|
+
const minY = origin.y < target.y ? origin.y : target.y;
|
|
449
|
+
const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
|
|
450
|
+
// float calculate error( eg: 1.4210854715202004e-14 > 0)
|
|
451
|
+
if (Math.floor(maxX - minX - origin.width - target.width) <= 0 && Math.floor(maxY - minY - origin.height - target.height) <= 0) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
508
457
|
},
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
ancestors(path, options = {}) {
|
|
519
|
-
const { reverse = false } = options;
|
|
520
|
-
let paths = Path.levels(path, options);
|
|
521
|
-
if (reverse) {
|
|
522
|
-
paths = paths.slice(1);
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
paths = paths.slice(0, -1);
|
|
526
|
-
}
|
|
527
|
-
return paths;
|
|
528
|
-
},
|
|
529
|
-
/**
|
|
530
|
-
* Get a list of paths at every level down to a path. Note: this is the same
|
|
531
|
-
* as `Path.ancestors`, but including the path itself.
|
|
532
|
-
*
|
|
533
|
-
* The paths are sorted from shallowest to deepest. However, if the `reverse:
|
|
534
|
-
* true` option is passed, they are reversed.
|
|
535
|
-
*/
|
|
536
|
-
levels(path, options = {}) {
|
|
537
|
-
const { reverse = false } = options;
|
|
538
|
-
const list = [];
|
|
539
|
-
for (let i = 0; i <= path.length; i++) {
|
|
540
|
-
list.push(path.slice(0, i));
|
|
541
|
-
}
|
|
542
|
-
if (reverse) {
|
|
543
|
-
list.reverse();
|
|
544
|
-
}
|
|
545
|
-
return list;
|
|
458
|
+
toRectangleClient: (points) => {
|
|
459
|
+
const xArray = points.map(ele => ele[0]);
|
|
460
|
+
const yArray = points.map(ele => ele[1]);
|
|
461
|
+
const xMin = Math.min(...xArray);
|
|
462
|
+
const xMax = Math.max(...xArray);
|
|
463
|
+
const yMin = Math.min(...yArray);
|
|
464
|
+
const yMax = Math.max(...yArray);
|
|
465
|
+
const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
466
|
+
return rect;
|
|
546
467
|
},
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
468
|
+
getOutlineRectangle: (rectangle, offset) => {
|
|
469
|
+
return {
|
|
470
|
+
x: rectangle.x + offset,
|
|
471
|
+
y: rectangle.y + offset,
|
|
472
|
+
width: rectangle.width - offset * 2,
|
|
473
|
+
height: rectangle.height - offset * 2
|
|
474
|
+
};
|
|
552
475
|
},
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return path.slice(0, -1).concat(last + 1);
|
|
476
|
+
isEqual: (rectangle, otherRectangle) => {
|
|
477
|
+
return (rectangle.x === otherRectangle.x &&
|
|
478
|
+
rectangle.y === otherRectangle.y &&
|
|
479
|
+
rectangle.width === otherRectangle.width &&
|
|
480
|
+
rectangle.height === otherRectangle.height);
|
|
559
481
|
},
|
|
560
|
-
|
|
561
|
-
return
|
|
482
|
+
getCornerPoints: (rectangle) => {
|
|
483
|
+
return [
|
|
484
|
+
[rectangle.x, rectangle.y],
|
|
485
|
+
[rectangle.x + rectangle.width, rectangle.y],
|
|
486
|
+
[rectangle.x + rectangle.width, rectangle.y + rectangle.height],
|
|
487
|
+
[rectangle.x, rectangle.y + rectangle.height]
|
|
488
|
+
];
|
|
562
489
|
},
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
490
|
+
getEdgeCenterPoints: (rectangle) => {
|
|
491
|
+
return [
|
|
492
|
+
[rectangle.x + rectangle.width / 2, rectangle.y],
|
|
493
|
+
[rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
|
|
494
|
+
[rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
|
|
495
|
+
[rectangle.x, rectangle.y + rectangle.height / 2]
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// https://stackoverflow.com/a/6853926/232122
|
|
501
|
+
function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
|
|
502
|
+
const A = x - x1;
|
|
503
|
+
const B = y - y1;
|
|
504
|
+
const C = x2 - x1;
|
|
505
|
+
const D = y2 - y1;
|
|
506
|
+
const dot = A * C + B * D;
|
|
507
|
+
const lenSquare = C * C + D * D;
|
|
508
|
+
let param = -1;
|
|
509
|
+
if (lenSquare !== 0) {
|
|
510
|
+
// in case of 0 length line
|
|
511
|
+
param = dot / lenSquare;
|
|
512
|
+
}
|
|
513
|
+
let xx, yy;
|
|
514
|
+
if (param < 0) {
|
|
515
|
+
xx = x1;
|
|
516
|
+
yy = y1;
|
|
517
|
+
}
|
|
518
|
+
else if (param > 1) {
|
|
519
|
+
xx = x2;
|
|
520
|
+
yy = y2;
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
xx = x1 + param * C;
|
|
524
|
+
yy = y1 + param * D;
|
|
525
|
+
}
|
|
526
|
+
const dx = x - xx;
|
|
527
|
+
const dy = y - yy;
|
|
528
|
+
return Math.hypot(dx, dy);
|
|
529
|
+
}
|
|
530
|
+
function getNearestPointBetweenPointAndSegment(point, linePoints) {
|
|
531
|
+
const x = point[0], y = point[1], x1 = linePoints[0][0], y1 = linePoints[0][1], x2 = linePoints[1][0], y2 = linePoints[1][1];
|
|
532
|
+
const A = x - x1;
|
|
533
|
+
const B = y - y1;
|
|
534
|
+
const C = x2 - x1;
|
|
535
|
+
const D = y2 - y1;
|
|
536
|
+
const dot = A * C + B * D;
|
|
537
|
+
const lenSquare = C * C + D * D;
|
|
538
|
+
let param = -1;
|
|
539
|
+
if (lenSquare !== 0) {
|
|
540
|
+
// in case of 0 length line
|
|
541
|
+
param = dot / lenSquare;
|
|
542
|
+
}
|
|
543
|
+
let xx, yy;
|
|
544
|
+
if (param < 0) {
|
|
545
|
+
xx = x1;
|
|
546
|
+
yy = y1;
|
|
547
|
+
}
|
|
548
|
+
else if (param > 1) {
|
|
549
|
+
xx = x2;
|
|
550
|
+
yy = y2;
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
xx = x1 + param * C;
|
|
554
|
+
yy = y1 + param * D;
|
|
555
|
+
}
|
|
556
|
+
return [xx, yy];
|
|
557
|
+
}
|
|
558
|
+
function distanceBetweenPointAndSegments(points, point) {
|
|
559
|
+
const len = points.length;
|
|
560
|
+
let distance = Infinity;
|
|
561
|
+
for (let i = 0; i < len - 1; i++) {
|
|
562
|
+
const p = points[i];
|
|
563
|
+
const p2 = points[i + 1];
|
|
564
|
+
const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
|
|
565
|
+
if (currentDistance < distance) {
|
|
566
|
+
distance = currentDistance;
|
|
566
567
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
* directly above or below the other. If you want exact matching, use
|
|
582
|
-
* [[Path.equals]] instead.
|
|
583
|
-
*/
|
|
584
|
-
compare(path, another) {
|
|
585
|
-
const min = Math.min(path.length, another.length);
|
|
586
|
-
for (let i = 0; i < min; i++) {
|
|
587
|
-
if (path[i] < another[i])
|
|
588
|
-
return -1;
|
|
589
|
-
if (path[i] > another[i])
|
|
590
|
-
return 1;
|
|
568
|
+
}
|
|
569
|
+
return distance;
|
|
570
|
+
}
|
|
571
|
+
function getNearestPointBetweenPointAndSegments(point, points) {
|
|
572
|
+
const len = points.length;
|
|
573
|
+
let distance = Infinity;
|
|
574
|
+
let result = point;
|
|
575
|
+
for (let i = 0; i < len; i++) {
|
|
576
|
+
const p = points[i];
|
|
577
|
+
const p2 = i === len - 1 ? points[0] : points[i + 1];
|
|
578
|
+
const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
|
|
579
|
+
if (currentDistance < distance) {
|
|
580
|
+
distance = currentDistance;
|
|
581
|
+
result = getNearestPointBetweenPointAndSegment(point, [p, p2]);
|
|
591
582
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
583
|
+
}
|
|
584
|
+
return result;
|
|
585
|
+
}
|
|
586
|
+
function rotate(x1, y1, x2, y2, angle) {
|
|
587
|
+
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
|
588
|
+
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
|
589
|
+
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
|
590
|
+
return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
|
|
591
|
+
}
|
|
592
|
+
function distanceBetweenPointAndPoint(x1, y1, x2, y2) {
|
|
593
|
+
const dx = x1 - x2;
|
|
594
|
+
const dy = y1 - y2;
|
|
595
|
+
return Math.hypot(dx, dy);
|
|
596
|
+
}
|
|
597
|
+
// https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
|
|
598
|
+
function distanceBetweenPointAndRectangle(x, y, rect) {
|
|
599
|
+
var dx = Math.max(rect.x - x, 0, x - (rect.x + rect.width));
|
|
600
|
+
var dy = Math.max(rect.y - y, 0, y - (rect.y + rect.height));
|
|
601
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
602
|
+
}
|
|
603
|
+
const isLineHitLine = (a, b, c, d) => {
|
|
604
|
+
const crossProduct = (v1, v2) => v1[0] * v2[1] - v1[1] * v2[0];
|
|
605
|
+
const ab = [b[0] - a[0], b[1] - a[1]];
|
|
606
|
+
const ac = [c[0] - a[0], c[1] - a[1]];
|
|
607
|
+
const ad = [d[0] - a[0], d[1] - a[1]];
|
|
608
|
+
const ca = [a[0] - c[0], a[1] - c[1]];
|
|
609
|
+
const cb = [b[0] - c[0], b[1] - c[1]];
|
|
610
|
+
const cd = [d[0] - c[0], d[1] - c[1]];
|
|
611
|
+
return crossProduct(ab, ac) * crossProduct(ab, ad) <= 0 && crossProduct(cd, ca) * crossProduct(cd, cb) <= 0;
|
|
612
|
+
};
|
|
613
|
+
const isPolylineHitRectangle = (points, rectangle) => {
|
|
614
|
+
const rectanglePoints = RectangleClient.getCornerPoints(rectangle);
|
|
615
|
+
for (let i = 1; i < points.length; i++) {
|
|
616
|
+
const isIntersect = isLineHitLine(points[i], points[i - 1], rectanglePoints[0], rectanglePoints[1]) ||
|
|
617
|
+
isLineHitLine(points[i], points[i - 1], rectanglePoints[1], rectanglePoints[2]) ||
|
|
618
|
+
isLineHitLine(points[i], points[i - 1], rectanglePoints[2], rectanglePoints[3]) ||
|
|
619
|
+
isLineHitLine(points[i], points[i - 1], rectanglePoints[3], rectanglePoints[0]);
|
|
620
|
+
if (isIntersect) {
|
|
621
|
+
return true;
|
|
617
622
|
}
|
|
618
|
-
const as = path.slice(0, -1);
|
|
619
|
-
const bs = another.slice(0, -1);
|
|
620
|
-
const al = path[path.length - 1];
|
|
621
|
-
const bl = another[another.length - 1];
|
|
622
|
-
return al !== bl && Path.equals(as, bs);
|
|
623
|
-
},
|
|
624
|
-
transform(path, operation) {
|
|
625
|
-
return produce(path, p => {
|
|
626
|
-
// PERF: Exit early if the operation is guaranteed not to have an effect.
|
|
627
|
-
if (!path || path?.length === 0) {
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
if (p === null) {
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
switch (operation.type) {
|
|
634
|
-
case 'insert_node': {
|
|
635
|
-
const { path: op } = operation;
|
|
636
|
-
if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
|
|
637
|
-
p[op.length - 1] += 1;
|
|
638
|
-
}
|
|
639
|
-
break;
|
|
640
|
-
}
|
|
641
|
-
case 'remove_node': {
|
|
642
|
-
const { path: op } = operation;
|
|
643
|
-
if (Path.equals(op, p) || Path.isAncestor(op, p)) {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
else if (Path.endsBefore(op, p)) {
|
|
647
|
-
p[op.length - 1] -= 1;
|
|
648
|
-
}
|
|
649
|
-
break;
|
|
650
|
-
}
|
|
651
|
-
case 'move_node': {
|
|
652
|
-
const { path: op, newPath: onp } = operation;
|
|
653
|
-
// If the old and new path are the same, it's a no-op.
|
|
654
|
-
if (Path.equals(op, onp)) {
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (Path.isAncestor(op, p) || Path.equals(op, p)) {
|
|
658
|
-
const copy = onp.slice();
|
|
659
|
-
// op.length <= onp.length is different for slate
|
|
660
|
-
// resolve drag from [0, 0] to [0, 3] issue
|
|
661
|
-
if (Path.endsBefore(op, onp) && op.length <= onp.length) {
|
|
662
|
-
copy[op.length - 1] -= 1;
|
|
663
|
-
}
|
|
664
|
-
return copy.concat(p.slice(op.length));
|
|
665
|
-
}
|
|
666
|
-
else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
|
|
667
|
-
if (Path.endsBefore(op, p)) {
|
|
668
|
-
p[op.length - 1] -= 1;
|
|
669
|
-
}
|
|
670
|
-
else {
|
|
671
|
-
p[op.length - 1] += 1;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
|
|
675
|
-
if (Path.endsBefore(op, p)) {
|
|
676
|
-
p[op.length - 1] -= 1;
|
|
677
|
-
}
|
|
678
|
-
p[onp.length - 1] += 1;
|
|
679
|
-
}
|
|
680
|
-
else if (Path.endsBefore(op, p)) {
|
|
681
|
-
if (Path.equals(onp, p)) {
|
|
682
|
-
p[onp.length - 1] += 1;
|
|
683
|
-
}
|
|
684
|
-
p[op.length - 1] -= 1;
|
|
685
|
-
}
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
return p;
|
|
690
|
-
});
|
|
691
623
|
}
|
|
692
|
-
|
|
624
|
+
return false;
|
|
625
|
+
};
|
|
626
|
+
//https://stackoverflow.com/questions/22521982/check-if-point-is-inside-a-polygon
|
|
627
|
+
const isPointInPolygon = (point, points) => {
|
|
628
|
+
// ray-casting algorithm based on
|
|
629
|
+
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
630
|
+
const x = point[0], y = point[1];
|
|
631
|
+
let inside = false;
|
|
632
|
+
for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
633
|
+
let xi = points[i][0], yi = points[i][1];
|
|
634
|
+
let xj = points[j][0], yj = points[j][1];
|
|
635
|
+
let intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
636
|
+
if (intersect)
|
|
637
|
+
inside = !inside;
|
|
638
|
+
}
|
|
639
|
+
return inside;
|
|
640
|
+
};
|
|
641
|
+
const isPointInEllipse = (point, center, rx, ry, rotation = 0) => {
|
|
642
|
+
const cosAngle = Math.cos(rotation);
|
|
643
|
+
const sinAngle = Math.sin(rotation);
|
|
644
|
+
const x1 = (point[0] - center[0]) * cosAngle + (point[1] - center[1]) * sinAngle;
|
|
645
|
+
const y1 = (point[1] - center[1]) * cosAngle - (point[0] - center[0]) * sinAngle;
|
|
646
|
+
return (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry) <= 1;
|
|
647
|
+
};
|
|
648
|
+
const isPointInRoundRectangle = (point, rectangle, radius) => {
|
|
649
|
+
const { x: rectX, y: rectY, width, height } = rectangle;
|
|
650
|
+
const isInRectangle = point[0] >= rectX && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + height;
|
|
651
|
+
const handleLeftTop = point[0] >= rectX &&
|
|
652
|
+
point[0] <= rectX + radius &&
|
|
653
|
+
point[1] >= rectY &&
|
|
654
|
+
point[1] <= rectY + radius &&
|
|
655
|
+
Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + radius)) > radius;
|
|
656
|
+
const handleLeftBottom = point[0] >= rectX &&
|
|
657
|
+
point[0] <= rectX + radius &&
|
|
658
|
+
point[1] >= rectY + height &&
|
|
659
|
+
point[1] <= rectY + height - radius &&
|
|
660
|
+
Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + height - radius)) > radius;
|
|
661
|
+
const handleRightTop = point[0] >= rectX + width - radius &&
|
|
662
|
+
point[0] <= rectX + width &&
|
|
663
|
+
point[1] >= rectY &&
|
|
664
|
+
point[1] <= rectY + radius &&
|
|
665
|
+
Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + radius)) > radius;
|
|
666
|
+
const handleRightBottom = point[0] >= rectX + width - radius &&
|
|
667
|
+
point[0] <= rectX + width &&
|
|
668
|
+
point[1] >= rectY + height - radius &&
|
|
669
|
+
point[1] <= rectY + height &&
|
|
670
|
+
Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + height - radius)) > radius;
|
|
671
|
+
const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
|
|
672
|
+
return isInRectangle && !isInCorner;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
function transformPoints(board, points) {
|
|
676
|
+
const newPoints = points.map(point => {
|
|
677
|
+
return transformPoint(board, point);
|
|
678
|
+
});
|
|
679
|
+
return newPoints;
|
|
680
|
+
}
|
|
681
|
+
function transformPoint(board, point) {
|
|
682
|
+
const { width, height } = PlaitBoard.getHost(board).getBoundingClientRect();
|
|
683
|
+
const viewBox = PlaitBoard.getHost(board).viewBox.baseVal;
|
|
684
|
+
const x = (point[0] / width) * viewBox.width + viewBox.x;
|
|
685
|
+
const y = (point[1] / height) * viewBox.height + viewBox.y;
|
|
686
|
+
const newPoint = [x, y];
|
|
687
|
+
return newPoint;
|
|
688
|
+
}
|
|
689
|
+
function isInPlaitBoard(board, x, y) {
|
|
690
|
+
const plaitBoardElement = PlaitBoard.getBoardContainer(board);
|
|
691
|
+
const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
|
|
692
|
+
const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
|
|
693
|
+
return distances === 0;
|
|
694
|
+
}
|
|
695
|
+
function getRealScrollBarWidth(board) {
|
|
696
|
+
const { hideScrollbar } = board.options;
|
|
697
|
+
let scrollBarWidth = 0;
|
|
698
|
+
if (!hideScrollbar) {
|
|
699
|
+
const viewportContainer = PlaitBoard.getViewportContainer(board);
|
|
700
|
+
scrollBarWidth = viewportContainer.offsetWidth - viewportContainer.clientWidth;
|
|
701
|
+
}
|
|
702
|
+
return scrollBarWidth;
|
|
703
|
+
}
|
|
693
704
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
yield n;
|
|
710
|
-
}
|
|
711
|
-
},
|
|
712
|
-
get(root, path) {
|
|
713
|
-
let node = root;
|
|
714
|
-
for (let i = 0; i < path.length; i++) {
|
|
715
|
-
const p = path[i];
|
|
716
|
-
if (!node || !node.children || !node.children[p]) {
|
|
717
|
-
throw new Error(`Cannot find a descendant at path [${path}]`);
|
|
718
|
-
}
|
|
719
|
-
node = node.children[p];
|
|
720
|
-
}
|
|
721
|
-
return node;
|
|
722
|
-
},
|
|
723
|
-
last(board, path) {
|
|
724
|
-
let n = PlaitNode.get(board, path);
|
|
725
|
-
while (n && n.children && n.children.length > 0) {
|
|
726
|
-
const i = n.children.length - 1;
|
|
727
|
-
n = n.children[i];
|
|
728
|
-
}
|
|
729
|
-
return n;
|
|
705
|
+
function createForeignObject(x, y, width, height) {
|
|
706
|
+
var newForeignObject = document.createElementNS(NS, 'foreignObject');
|
|
707
|
+
newForeignObject.setAttribute('x', `${x}`);
|
|
708
|
+
newForeignObject.setAttribute('y', `${y}`);
|
|
709
|
+
newForeignObject.setAttribute('width', `${width}`);
|
|
710
|
+
newForeignObject.setAttribute('height', `${height}`);
|
|
711
|
+
return newForeignObject;
|
|
712
|
+
}
|
|
713
|
+
function updateForeignObject(target, width, height, x, y) {
|
|
714
|
+
const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
|
|
715
|
+
if (foreignObject) {
|
|
716
|
+
foreignObject.setAttribute('width', `${width}`);
|
|
717
|
+
foreignObject.setAttribute('height', `${height}`);
|
|
718
|
+
foreignObject.setAttribute('x', `${x}`);
|
|
719
|
+
foreignObject.setAttribute('y', `${y}`);
|
|
730
720
|
}
|
|
731
|
-
}
|
|
721
|
+
}
|
|
722
|
+
function updateForeignObjectWidth(target, width) {
|
|
723
|
+
const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
|
|
724
|
+
if (foreignObject) {
|
|
725
|
+
foreignObject.setAttribute('width', `${width}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
732
728
|
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const parent = PlaitNode.parent(board, path);
|
|
762
|
-
const index = path[path.length - 1];
|
|
763
|
-
// This is tricky, but since the `path` and `newPath` both refer to
|
|
764
|
-
// the same snapshot in time, there's a mismatch. After either
|
|
765
|
-
// removing the original position, the second step's path can be out
|
|
766
|
-
// of date. So instead of using the `op.newPath` directly, we
|
|
767
|
-
// transform `op.path` to ascertain what the `newPath` would be after
|
|
768
|
-
// the operation was applied.
|
|
769
|
-
parent.children?.splice(index, 1);
|
|
770
|
-
const truePath = Path.transform(path, op);
|
|
771
|
-
const newParent = PlaitNode.get(board, Path.parent(truePath));
|
|
772
|
-
const newIndex = truePath[truePath.length - 1];
|
|
773
|
-
newParent.children?.splice(newIndex, 0, node);
|
|
774
|
-
break;
|
|
775
|
-
}
|
|
776
|
-
case 'set_node': {
|
|
777
|
-
const { path, properties, newProperties } = op;
|
|
778
|
-
if (path.length === 0) {
|
|
779
|
-
throw new Error(`Cannot set properties on the root node!`);
|
|
780
|
-
}
|
|
781
|
-
const node = PlaitNode.get(board, path);
|
|
782
|
-
for (const key in newProperties) {
|
|
783
|
-
const value = newProperties[key];
|
|
784
|
-
if (value == null) {
|
|
785
|
-
delete node[key];
|
|
786
|
-
}
|
|
787
|
-
else {
|
|
788
|
-
node[key] = value;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
// properties that were previously defined, but are now missing, must be deleted
|
|
792
|
-
for (const key in properties) {
|
|
793
|
-
if (!newProperties.hasOwnProperty(key)) {
|
|
794
|
-
delete node[key];
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
break;
|
|
798
|
-
}
|
|
799
|
-
case 'set_viewport': {
|
|
800
|
-
const { newProperties } = op;
|
|
801
|
-
if (newProperties == null) {
|
|
802
|
-
viewport = newProperties;
|
|
803
|
-
}
|
|
804
|
-
else {
|
|
805
|
-
if (viewport == null) {
|
|
806
|
-
if (!Viewport.isViewport(newProperties)) {
|
|
807
|
-
throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
|
|
808
|
-
}
|
|
809
|
-
viewport = { ...newProperties };
|
|
810
|
-
}
|
|
811
|
-
for (const key in newProperties) {
|
|
812
|
-
const value = newProperties[key];
|
|
813
|
-
if (value == null) {
|
|
814
|
-
delete viewport[key];
|
|
815
|
-
}
|
|
816
|
-
else {
|
|
817
|
-
viewport[key] = value;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
break;
|
|
822
|
-
}
|
|
823
|
-
case 'set_selection': {
|
|
824
|
-
const { newProperties } = op;
|
|
825
|
-
if (newProperties == null) {
|
|
826
|
-
selection = newProperties;
|
|
827
|
-
}
|
|
828
|
-
else {
|
|
829
|
-
if (selection === null) {
|
|
830
|
-
selection = op.newProperties;
|
|
831
|
-
}
|
|
832
|
-
else {
|
|
833
|
-
selection.ranges = newProperties.ranges;
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
break;
|
|
837
|
-
}
|
|
838
|
-
case 'set_theme': {
|
|
839
|
-
const { newProperties } = op;
|
|
840
|
-
theme = newProperties;
|
|
841
|
-
break;
|
|
729
|
+
const IS_MAC = typeof window != 'undefined' && /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
|
|
730
|
+
|
|
731
|
+
const IS_IOS = typeof navigator !== 'undefined' &&
|
|
732
|
+
typeof window !== 'undefined' &&
|
|
733
|
+
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
|
|
734
|
+
!window.MSStream;
|
|
735
|
+
const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
|
|
736
|
+
const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
|
737
|
+
const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
|
|
738
|
+
// "modern" Edge was released at 79.x
|
|
739
|
+
const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
|
|
740
|
+
const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
|
|
741
|
+
// Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
|
|
742
|
+
const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
|
|
743
|
+
|
|
744
|
+
function isNullOrUndefined(value) {
|
|
745
|
+
return value === null || value === undefined;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* 规范 point
|
|
749
|
+
* @param point
|
|
750
|
+
* @returns point
|
|
751
|
+
*/
|
|
752
|
+
function normalizePoint(point) {
|
|
753
|
+
return Array.isArray(point)
|
|
754
|
+
? {
|
|
755
|
+
x: point[0],
|
|
756
|
+
y: point[1]
|
|
842
757
|
}
|
|
758
|
+
: point;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Check whether to merge an operation into the previous operation.
|
|
763
|
+
*/
|
|
764
|
+
const shouldMerge = (op, prev) => {
|
|
765
|
+
if (op.type === 'set_viewport' && op.type === prev?.type) {
|
|
766
|
+
return true;
|
|
843
767
|
}
|
|
844
|
-
return
|
|
768
|
+
return false;
|
|
845
769
|
};
|
|
846
|
-
|
|
770
|
+
/**
|
|
771
|
+
* Check whether an operation needs to be saved to the history.
|
|
772
|
+
*/
|
|
773
|
+
const shouldSave = (op, prev) => {
|
|
774
|
+
if (op.type === 'set_selection' || op.type === 'set_viewport') {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
return true;
|
|
778
|
+
};
|
|
779
|
+
/**
|
|
780
|
+
* Check whether an operation should clear the redos stack.
|
|
781
|
+
*/
|
|
782
|
+
const shouldClear = (op) => {
|
|
783
|
+
if (op.type === 'set_selection') {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
return true;
|
|
787
|
+
};
|
|
788
|
+
const PlaitHistoryBoard = {
|
|
789
|
+
/**
|
|
790
|
+
* Get the saving flag's current value.
|
|
791
|
+
*/
|
|
792
|
+
isSaving(board) {
|
|
793
|
+
return SAVING.get(board);
|
|
794
|
+
},
|
|
795
|
+
/**
|
|
796
|
+
* Get the merge flag's current value.
|
|
797
|
+
*/
|
|
798
|
+
isMerging(board) {
|
|
799
|
+
return MERGING.get(board);
|
|
800
|
+
},
|
|
801
|
+
/**
|
|
802
|
+
* Apply a series of changes inside a synchronous `fn`, without merging any of
|
|
803
|
+
* the new operations into previous save point in the history.
|
|
804
|
+
*/
|
|
805
|
+
withoutMerging(board, fn) {
|
|
806
|
+
const prev = PlaitHistoryBoard.isMerging(board);
|
|
807
|
+
MERGING.set(board, false);
|
|
808
|
+
fn();
|
|
809
|
+
MERGING.set(board, prev);
|
|
810
|
+
},
|
|
847
811
|
/**
|
|
848
|
-
*
|
|
812
|
+
* Apply a series of changes inside a synchronous `fn`, without saving any of
|
|
813
|
+
* their operations into the history.
|
|
849
814
|
*/
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
815
|
+
withoutSaving(board, fn) {
|
|
816
|
+
const prev = PlaitHistoryBoard.isSaving(board);
|
|
817
|
+
SAVING.set(board, false);
|
|
818
|
+
fn();
|
|
819
|
+
SAVING.set(board, prev);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Hotkey mappings for each platform.
|
|
825
|
+
*/
|
|
826
|
+
const HOTKEYS = {
|
|
827
|
+
bold: 'mod+b',
|
|
828
|
+
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
829
|
+
moveBackward: 'left',
|
|
830
|
+
moveForward: 'right',
|
|
831
|
+
moveUp: 'up',
|
|
832
|
+
moveDown: 'down',
|
|
833
|
+
moveWordBackward: 'ctrl+left',
|
|
834
|
+
moveWordForward: 'ctrl+right',
|
|
835
|
+
deleteBackward: 'shift?+backspace',
|
|
836
|
+
deleteForward: 'shift?+delete',
|
|
837
|
+
extendBackward: 'shift+left',
|
|
838
|
+
extendForward: 'shift+right',
|
|
839
|
+
italic: 'mod+i',
|
|
840
|
+
splitBlock: 'shift?+enter',
|
|
841
|
+
undo: 'mod+z'
|
|
842
|
+
};
|
|
843
|
+
const APPLE_HOTKEYS = {
|
|
844
|
+
moveLineBackward: 'opt+up',
|
|
845
|
+
moveLineForward: 'opt+down',
|
|
846
|
+
moveWordBackward: 'opt+left',
|
|
847
|
+
moveWordForward: 'opt+right',
|
|
848
|
+
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
849
|
+
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
850
|
+
deleteLineBackward: 'cmd+shift?+backspace',
|
|
851
|
+
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
852
|
+
deleteWordBackward: 'opt+shift?+backspace',
|
|
853
|
+
deleteWordForward: 'opt+shift?+delete',
|
|
854
|
+
extendLineBackward: 'opt+shift+up',
|
|
855
|
+
extendLineForward: 'opt+shift+down',
|
|
856
|
+
redo: 'cmd+shift+z',
|
|
857
|
+
transposeCharacter: 'ctrl+t'
|
|
858
|
+
};
|
|
859
|
+
const WINDOWS_HOTKEYS = {
|
|
860
|
+
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
861
|
+
deleteWordForward: 'ctrl+shift?+delete',
|
|
862
|
+
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
863
|
+
};
|
|
864
|
+
/**
|
|
865
|
+
* Create a platform-aware hotkey checker.
|
|
866
|
+
*/
|
|
867
|
+
const create = (key) => {
|
|
868
|
+
const generic = HOTKEYS[key];
|
|
869
|
+
const apple = APPLE_HOTKEYS[key];
|
|
870
|
+
const windows = WINDOWS_HOTKEYS[key];
|
|
871
|
+
const isGeneric = generic && isKeyHotkey(generic);
|
|
872
|
+
const isApple = apple && isKeyHotkey(apple);
|
|
873
|
+
const isWindows = windows && isKeyHotkey(windows);
|
|
874
|
+
return (event) => {
|
|
875
|
+
if (isGeneric && isGeneric(event)) {
|
|
876
|
+
return true;
|
|
860
877
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (selection) {
|
|
864
|
-
board.selection = isDraft(selection) ? finishDraft(selection) : selection;
|
|
865
|
-
}
|
|
866
|
-
else {
|
|
867
|
-
board.selection = null;
|
|
868
|
-
}
|
|
869
|
-
board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
|
|
870
|
-
board.theme = isDraft(theme) ? finishDraft(theme) : theme;
|
|
878
|
+
if (IS_APPLE && isApple && isApple(event)) {
|
|
879
|
+
return true;
|
|
871
880
|
}
|
|
872
|
-
|
|
881
|
+
if (!IS_APPLE && isWindows && isWindows(event)) {
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
return false;
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
/**
|
|
888
|
+
* Hotkeys.
|
|
889
|
+
*/
|
|
890
|
+
const hotkeys = {
|
|
891
|
+
isBold: create('bold'),
|
|
892
|
+
isCompose: create('compose'),
|
|
893
|
+
isMoveBackward: create('moveBackward'),
|
|
894
|
+
isMoveForward: create('moveForward'),
|
|
895
|
+
isMoveUp: create('moveUp'),
|
|
896
|
+
isMoveDown: create('moveDown'),
|
|
897
|
+
isDeleteBackward: create('deleteBackward'),
|
|
898
|
+
isDeleteForward: create('deleteForward'),
|
|
899
|
+
isDeleteLineBackward: create('deleteLineBackward'),
|
|
900
|
+
isDeleteLineForward: create('deleteLineForward'),
|
|
901
|
+
isDeleteWordBackward: create('deleteWordBackward'),
|
|
902
|
+
isDeleteWordForward: create('deleteWordForward'),
|
|
903
|
+
isExtendBackward: create('extendBackward'),
|
|
904
|
+
isExtendForward: create('extendForward'),
|
|
905
|
+
isExtendLineBackward: create('extendLineBackward'),
|
|
906
|
+
isExtendLineForward: create('extendLineForward'),
|
|
907
|
+
isItalic: create('italic'),
|
|
908
|
+
isMoveLineBackward: create('moveLineBackward'),
|
|
909
|
+
isMoveLineForward: create('moveLineForward'),
|
|
910
|
+
isMoveWordBackward: create('moveWordBackward'),
|
|
911
|
+
isMoveWordForward: create('moveWordForward'),
|
|
912
|
+
isRedo: create('redo'),
|
|
913
|
+
isSplitBlock: create('splitBlock'),
|
|
914
|
+
isTransposeCharacter: create('transposeCharacter'),
|
|
915
|
+
isUndo: create('undo')
|
|
873
916
|
};
|
|
874
917
|
|
|
875
|
-
function
|
|
876
|
-
|
|
877
|
-
|
|
918
|
+
function idCreator(length = 5) {
|
|
919
|
+
// remove numeral
|
|
920
|
+
const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
|
|
921
|
+
const maxPosition = $chars.length;
|
|
922
|
+
let key = '';
|
|
923
|
+
for (let i = 0; i < length; i++) {
|
|
924
|
+
key += $chars.charAt(Math.floor(Math.random() * maxPosition));
|
|
925
|
+
}
|
|
926
|
+
return key;
|
|
878
927
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* drawRoundRectangle
|
|
931
|
+
*/
|
|
932
|
+
function drawRoundRectangle(rs, x1, y1, x2, y2, options, outline = false, borderRadius) {
|
|
933
|
+
const width = Math.abs(x1 - x2);
|
|
934
|
+
const height = Math.abs(y1 - y2);
|
|
935
|
+
let radius = borderRadius || 0;
|
|
936
|
+
if (radius === 0) {
|
|
937
|
+
const defaultRadius = Math.min(width, height) / 8;
|
|
938
|
+
let radius = defaultRadius;
|
|
939
|
+
if (defaultRadius > MAX_RADIUS) {
|
|
940
|
+
radius = outline ? MAX_RADIUS + 2 : MAX_RADIUS;
|
|
890
941
|
}
|
|
891
942
|
}
|
|
892
|
-
const
|
|
893
|
-
|
|
943
|
+
const point1 = [x1 + radius, y1];
|
|
944
|
+
const point2 = [x2 - radius, y1];
|
|
945
|
+
const point3 = [x2, y1 + radius];
|
|
946
|
+
const point4 = [x2, y2 - radius];
|
|
947
|
+
const point5 = [x2 - radius, y2];
|
|
948
|
+
const point6 = [x1 + radius, y2];
|
|
949
|
+
const point7 = [x1, y2 - radius];
|
|
950
|
+
const point8 = [x1, y1 + radius];
|
|
951
|
+
return rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options);
|
|
894
952
|
}
|
|
895
|
-
|
|
896
|
-
const
|
|
897
|
-
const
|
|
898
|
-
|
|
953
|
+
const drawRectangle = (board, rectangle, options) => {
|
|
954
|
+
const roughSVG = PlaitBoard.getRoughSVG(board);
|
|
955
|
+
const rectangleG = roughSVG.rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options);
|
|
956
|
+
setStrokeLinecap(rectangleG, 'round');
|
|
957
|
+
return rectangleG;
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
function arrowPoints(start, end, maxHypotenuseLength = 10, degree = 40) {
|
|
961
|
+
const width = Math.abs(start[0] - end[0]);
|
|
962
|
+
const height = Math.abs(start[1] - end[1]);
|
|
963
|
+
let hypotenuse = Math.hypot(width, height); // 斜边
|
|
964
|
+
const realRotateLine = hypotenuse > maxHypotenuseLength * 2 ? maxHypotenuseLength : hypotenuse / 2;
|
|
965
|
+
const rotateWidth = (realRotateLine / hypotenuse) * width;
|
|
966
|
+
const rotateHeight = (realRotateLine / hypotenuse) * height;
|
|
967
|
+
const rotatePoint = [
|
|
968
|
+
end[0] > start[0] ? end[0] - rotateWidth : end[0] + rotateWidth,
|
|
969
|
+
end[1] > start[1] ? end[1] - rotateHeight : end[1] + rotateHeight
|
|
970
|
+
];
|
|
971
|
+
const pointRight = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (degree * Math.PI) / 180);
|
|
972
|
+
const pointLeft = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (-degree * Math.PI) / 180);
|
|
973
|
+
return { pointLeft, pointRight };
|
|
899
974
|
}
|
|
900
|
-
function
|
|
901
|
-
const
|
|
902
|
-
|
|
975
|
+
function drawArrow(rs, start, end, options, maxHypotenuseLength = 10, degree = 40) {
|
|
976
|
+
const { pointLeft, pointRight } = arrowPoints(start, end, maxHypotenuseLength, degree);
|
|
977
|
+
const arrowLineLeft = rs.linearPath([pointLeft, end], options);
|
|
978
|
+
const arrowLineRight = rs.linearPath([pointRight, end], options);
|
|
979
|
+
return [arrowLineLeft, arrowLineRight];
|
|
903
980
|
}
|
|
904
|
-
const NodeTransforms = {
|
|
905
|
-
insertNode,
|
|
906
|
-
setNode,
|
|
907
|
-
removeNode,
|
|
908
|
-
moveNode
|
|
909
|
-
};
|
|
910
981
|
|
|
911
|
-
function
|
|
912
|
-
|
|
913
|
-
board.apply(operation);
|
|
982
|
+
function drawCircle(roughSVG, point, diameter, options) {
|
|
983
|
+
return roughSVG.circle(point[0], point[1], diameter, options);
|
|
914
984
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
}
|
|
919
|
-
function
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
985
|
+
|
|
986
|
+
function drawLine(rs, start, end, options) {
|
|
987
|
+
return rs.linearPath([start, end], options);
|
|
988
|
+
}
|
|
989
|
+
function drawLinearPath(points, options) {
|
|
990
|
+
const g = createG();
|
|
991
|
+
const path = createPath();
|
|
992
|
+
let polylinePath = '';
|
|
993
|
+
points.forEach((point, index) => {
|
|
994
|
+
if (index === 0) {
|
|
995
|
+
polylinePath += `M ${point[0]} ${point[1]} `;
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
polylinePath += `L ${point[0]} ${point[1]} `;
|
|
999
|
+
}
|
|
923
1000
|
});
|
|
1001
|
+
path.setAttribute('d', polylinePath);
|
|
1002
|
+
path.setAttribute('stroke', `${options?.stroke}`);
|
|
1003
|
+
path.setAttribute('stroke-width', `${options?.strokeWidth}`);
|
|
1004
|
+
path.setAttribute('fill', `none`);
|
|
1005
|
+
g.appendChild(path);
|
|
1006
|
+
return g;
|
|
1007
|
+
}
|
|
1008
|
+
function drawBezierPath(points, options) {
|
|
1009
|
+
const g = createG();
|
|
1010
|
+
const path = createPath();
|
|
1011
|
+
let polylinePath = '';
|
|
1012
|
+
for (let i = 0; i < points.length - 3; i += 3) {
|
|
1013
|
+
if (i === 0) {
|
|
1014
|
+
polylinePath += `M ${points[0][0]} ${points[0][1]} `;
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
polylinePath += `C ${points[i + 1][0]} ${points[i + 1][1]}, ${points[i + 2][0]} ${points[i + 2][1]}, ${points[i + 3][0]} ${points[i + 3][1]}`;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
path.setAttribute('d', polylinePath);
|
|
1021
|
+
path.setAttribute('stroke', `${options?.stroke}`);
|
|
1022
|
+
path.setAttribute('stroke-width', `${options?.strokeWidth}`);
|
|
1023
|
+
path.setAttribute('fill', `none`);
|
|
1024
|
+
g.appendChild(path);
|
|
1025
|
+
return g;
|
|
924
1026
|
}
|
|
925
1027
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1028
|
+
const IS_FROM_SCROLLING = new WeakMap();
|
|
1029
|
+
const IS_FROM_VIEWPORT_CHANGE = new WeakMap();
|
|
1030
|
+
function getViewportContainerRect(board) {
|
|
1031
|
+
const { hideScrollbar } = board.options;
|
|
1032
|
+
const scrollBarWidth = hideScrollbar ? SCROLL_BAR_WIDTH : 0;
|
|
1033
|
+
const viewportRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1034
|
+
return {
|
|
1035
|
+
width: viewportRect.width + scrollBarWidth,
|
|
1036
|
+
height: viewportRect.height + scrollBarWidth
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function getElementHostBBox(board, zoom) {
|
|
1040
|
+
const childrenRect = getRectangleByElements(board, board.children, true);
|
|
1041
|
+
const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1042
|
+
const containerWidth = viewportContainerRect.width / zoom;
|
|
1043
|
+
const containerHeight = viewportContainerRect.height / zoom;
|
|
1044
|
+
let left;
|
|
1045
|
+
let right;
|
|
1046
|
+
let top;
|
|
1047
|
+
let bottom;
|
|
1048
|
+
if (childrenRect.width < containerWidth) {
|
|
1049
|
+
const centerX = childrenRect.x + childrenRect.width / 2;
|
|
1050
|
+
const halfContainerWidth = containerWidth / 2;
|
|
1051
|
+
left = centerX - halfContainerWidth;
|
|
1052
|
+
right = centerX + halfContainerWidth;
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
left = childrenRect.x;
|
|
1056
|
+
right = childrenRect.x + childrenRect.width;
|
|
1057
|
+
}
|
|
1058
|
+
if (childrenRect.height < containerHeight) {
|
|
1059
|
+
const centerY = childrenRect.y + childrenRect.height / 2;
|
|
1060
|
+
const halfContainerHeight = containerHeight / 2;
|
|
1061
|
+
top = centerY - halfContainerHeight;
|
|
1062
|
+
bottom = centerY + halfContainerHeight;
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
top = childrenRect.y;
|
|
1066
|
+
bottom = childrenRect.y + childrenRect.height;
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
left,
|
|
1070
|
+
right,
|
|
1071
|
+
top,
|
|
1072
|
+
bottom
|
|
1073
|
+
};
|
|
929
1074
|
}
|
|
930
|
-
const ViewportTransforms$1 = {
|
|
931
|
-
setViewport
|
|
932
|
-
};
|
|
933
|
-
|
|
934
1075
|
/**
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
1076
|
+
* 验证缩放比是否符合限制,如果超出限制,则返回合适的缩放比
|
|
1077
|
+
* @param zoom 缩放比
|
|
1078
|
+
* @param minZoom 最小缩放比
|
|
1079
|
+
* @param maxZoom 最大缩放比
|
|
1080
|
+
* @returns 正确的缩放比
|
|
940
1081
|
*/
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const TAB = 9;
|
|
944
|
-
const NUM_CENTER = 12;
|
|
945
|
-
const ENTER = 13;
|
|
946
|
-
const SHIFT = 16;
|
|
947
|
-
const CONTROL = 17;
|
|
948
|
-
const ALT = 18;
|
|
949
|
-
const PAUSE = 19;
|
|
950
|
-
const CAPS_LOCK = 20;
|
|
951
|
-
const ESCAPE = 27;
|
|
952
|
-
const SPACE = 32;
|
|
953
|
-
const PAGE_UP = 33;
|
|
954
|
-
const PAGE_DOWN = 34;
|
|
955
|
-
const END = 35;
|
|
956
|
-
const HOME = 36;
|
|
957
|
-
const LEFT_ARROW = 37;
|
|
958
|
-
const UP_ARROW = 38;
|
|
959
|
-
const RIGHT_ARROW = 39;
|
|
960
|
-
const DOWN_ARROW = 40;
|
|
961
|
-
const PLUS_SIGN = 43;
|
|
962
|
-
const PRINT_SCREEN = 44;
|
|
963
|
-
const INSERT = 45;
|
|
964
|
-
const DELETE = 46;
|
|
965
|
-
const ZERO = 48;
|
|
966
|
-
const ONE = 49;
|
|
967
|
-
const TWO = 50;
|
|
968
|
-
const THREE = 51;
|
|
969
|
-
const FOUR = 52;
|
|
970
|
-
const FIVE = 53;
|
|
971
|
-
const SIX = 54;
|
|
972
|
-
const SEVEN = 55;
|
|
973
|
-
const EIGHT = 56;
|
|
974
|
-
const NINE = 57;
|
|
975
|
-
const FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
|
|
976
|
-
const FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
|
|
977
|
-
const QUESTION_MARK = 63;
|
|
978
|
-
const AT_SIGN = 64;
|
|
979
|
-
const A = 65;
|
|
980
|
-
const B = 66;
|
|
981
|
-
const C = 67;
|
|
982
|
-
const D = 68;
|
|
983
|
-
const E = 69;
|
|
984
|
-
const F = 70;
|
|
985
|
-
const G = 71;
|
|
986
|
-
const H = 72;
|
|
987
|
-
const I = 73;
|
|
988
|
-
const J = 74;
|
|
989
|
-
const K = 75;
|
|
990
|
-
const L = 76;
|
|
991
|
-
const M = 77;
|
|
992
|
-
const N = 78;
|
|
993
|
-
const O = 79;
|
|
994
|
-
const P = 80;
|
|
995
|
-
const Q = 81;
|
|
996
|
-
const R = 82;
|
|
997
|
-
const S = 83;
|
|
998
|
-
const T = 84;
|
|
999
|
-
const U = 85;
|
|
1000
|
-
const V = 86;
|
|
1001
|
-
const W = 87;
|
|
1002
|
-
const X = 88;
|
|
1003
|
-
const Y = 89;
|
|
1004
|
-
const Z = 90;
|
|
1005
|
-
const META = 91; // WIN_KEY_LEFT
|
|
1006
|
-
const MAC_WK_CMD_LEFT = 91;
|
|
1007
|
-
const MAC_WK_CMD_RIGHT = 93;
|
|
1008
|
-
const CONTEXT_MENU = 93;
|
|
1009
|
-
const NUMPAD_ZERO = 96;
|
|
1010
|
-
const NUMPAD_ONE = 97;
|
|
1011
|
-
const NUMPAD_TWO = 98;
|
|
1012
|
-
const NUMPAD_THREE = 99;
|
|
1013
|
-
const NUMPAD_FOUR = 100;
|
|
1014
|
-
const NUMPAD_FIVE = 101;
|
|
1015
|
-
const NUMPAD_SIX = 102;
|
|
1016
|
-
const NUMPAD_SEVEN = 103;
|
|
1017
|
-
const NUMPAD_EIGHT = 104;
|
|
1018
|
-
const NUMPAD_NINE = 105;
|
|
1019
|
-
const NUMPAD_MULTIPLY = 106;
|
|
1020
|
-
const NUMPAD_PLUS = 107;
|
|
1021
|
-
const NUMPAD_MINUS = 109;
|
|
1022
|
-
const NUMPAD_PERIOD = 110;
|
|
1023
|
-
const NUMPAD_DIVIDE = 111;
|
|
1024
|
-
const F1 = 112;
|
|
1025
|
-
const F2 = 113;
|
|
1026
|
-
const F3 = 114;
|
|
1027
|
-
const F4 = 115;
|
|
1028
|
-
const F5 = 116;
|
|
1029
|
-
const F6 = 117;
|
|
1030
|
-
const F7 = 118;
|
|
1031
|
-
const F8 = 119;
|
|
1032
|
-
const F9 = 120;
|
|
1033
|
-
const F10 = 121;
|
|
1034
|
-
const F11 = 122;
|
|
1035
|
-
const F12 = 123;
|
|
1036
|
-
const NUM_LOCK = 144;
|
|
1037
|
-
const SCROLL_LOCK = 145;
|
|
1038
|
-
const FIRST_MEDIA = 166;
|
|
1039
|
-
const FF_MINUS = 173;
|
|
1040
|
-
const MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
|
|
1041
|
-
const VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
|
|
1042
|
-
const VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
|
|
1043
|
-
const FF_MUTE = 181;
|
|
1044
|
-
const FF_VOLUME_DOWN = 182;
|
|
1045
|
-
const LAST_MEDIA = 183;
|
|
1046
|
-
const FF_VOLUME_UP = 183;
|
|
1047
|
-
const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
|
|
1048
|
-
const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
|
|
1049
|
-
const COMMA = 188;
|
|
1050
|
-
const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
|
|
1051
|
-
const PERIOD = 190;
|
|
1052
|
-
const SLASH = 191;
|
|
1053
|
-
const APOSTROPHE = 192;
|
|
1054
|
-
const TILDE = 192;
|
|
1055
|
-
const OPEN_SQUARE_BRACKET = 219;
|
|
1056
|
-
const BACKSLASH = 220;
|
|
1057
|
-
const CLOSE_SQUARE_BRACKET = 221;
|
|
1058
|
-
const SINGLE_QUOTE = 222;
|
|
1059
|
-
const MAC_META = 224;
|
|
1060
|
-
|
|
1061
|
-
var ResizeCursorClass;
|
|
1062
|
-
(function (ResizeCursorClass) {
|
|
1063
|
-
ResizeCursorClass["ew-resize"] = "ew-resize";
|
|
1064
|
-
})(ResizeCursorClass || (ResizeCursorClass = {}));
|
|
1065
|
-
|
|
1066
|
-
const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
|
|
1067
|
-
|
|
1068
|
-
const CLIP_BOARD_FORMAT_KEY = 'x-plait-fragment';
|
|
1069
|
-
const HOST_CLASS_NAME = 'plait-board-container';
|
|
1070
|
-
const SCROLL_BAR_WIDTH = 20;
|
|
1071
|
-
const MAX_RADIUS = 16;
|
|
1072
|
-
const POINTER_BUTTON = {
|
|
1073
|
-
MAIN: 0,
|
|
1074
|
-
WHEEL: 1,
|
|
1075
|
-
SECONDARY: 2,
|
|
1076
|
-
TOUCH: -1
|
|
1077
|
-
};
|
|
1078
|
-
const PRESS_AND_MOVE_BUFFER = 5;
|
|
1079
|
-
|
|
1080
|
-
const NS = 'http://www.w3.org/2000/svg';
|
|
1081
|
-
function toPoint(x, y, container) {
|
|
1082
|
-
const rect = container.getBoundingClientRect();
|
|
1083
|
-
return [x - rect.x, y - rect.y];
|
|
1082
|
+
function clampZoomLevel(zoom, minZoom = 0.2, maxZoom = 4) {
|
|
1083
|
+
return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
|
|
1084
1084
|
}
|
|
1085
|
-
function
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1085
|
+
function getViewBox(board, zoom) {
|
|
1086
|
+
const boardContainerRectangle = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1087
|
+
const elementHostBBox = getElementHostBBox(board, zoom);
|
|
1088
|
+
const horizontalPadding = boardContainerRectangle.width / 2;
|
|
1089
|
+
const verticalPadding = boardContainerRectangle.height / 2;
|
|
1090
|
+
const viewBox = [
|
|
1091
|
+
elementHostBBox.left - horizontalPadding / zoom,
|
|
1092
|
+
elementHostBBox.top - verticalPadding / zoom,
|
|
1093
|
+
elementHostBBox.right - elementHostBBox.left + (horizontalPadding * 2) / zoom,
|
|
1094
|
+
elementHostBBox.bottom - elementHostBBox.top + (verticalPadding * 2) / zoom
|
|
1095
|
+
];
|
|
1096
|
+
return viewBox;
|
|
1088
1097
|
}
|
|
1089
|
-
function
|
|
1090
|
-
const
|
|
1091
|
-
return
|
|
1098
|
+
function getViewBoxCenterPoint(board) {
|
|
1099
|
+
const childrenRectangle = getRectangleByElements(board, board.children, true);
|
|
1100
|
+
return [childrenRectangle.x + childrenRectangle.width / 2, childrenRectangle.y + childrenRectangle.height / 2];
|
|
1092
1101
|
}
|
|
1093
|
-
function
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
rect.setAttribute(key, `${options[optionKey]}`);
|
|
1102
|
+
function setSVGViewBox(board, viewBox) {
|
|
1103
|
+
const zoom = board.viewport.zoom;
|
|
1104
|
+
const hostElement = PlaitBoard.getHost(board);
|
|
1105
|
+
hostElement.style.display = 'block';
|
|
1106
|
+
hostElement.style.width = `${viewBox[2] * zoom}px`;
|
|
1107
|
+
hostElement.style.height = `${viewBox[3] * zoom}px`;
|
|
1108
|
+
if (viewBox && viewBox[2] > 0 && viewBox[3] > 0) {
|
|
1109
|
+
hostElement.setAttribute('viewBox', viewBox.join(' '));
|
|
1102
1110
|
}
|
|
1103
|
-
return rect;
|
|
1104
1111
|
}
|
|
1105
|
-
function
|
|
1106
|
-
|
|
1112
|
+
function updateViewportOffset(board) {
|
|
1113
|
+
const origination = getViewportOrigination(board);
|
|
1114
|
+
if (!origination)
|
|
1115
|
+
return;
|
|
1116
|
+
const { zoom } = board.viewport;
|
|
1117
|
+
const viewBox = getViewBox(board, zoom);
|
|
1118
|
+
const scrollLeft = (origination[0] - viewBox[0]) * zoom;
|
|
1119
|
+
const scrollTop = (origination[1] - viewBox[1]) * zoom;
|
|
1120
|
+
updateViewportContainerScroll(board, scrollLeft, scrollTop);
|
|
1107
1121
|
}
|
|
1108
|
-
function
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1122
|
+
function updateViewportContainerScroll(board, left, top, isFromViewportChange = true) {
|
|
1123
|
+
const viewportContainer = PlaitBoard.getViewportContainer(board);
|
|
1124
|
+
if (viewportContainer.scrollLeft !== left || viewportContainer.scrollTop !== top) {
|
|
1125
|
+
viewportContainer.scrollLeft = left;
|
|
1126
|
+
viewportContainer.scrollTop = top;
|
|
1127
|
+
isFromViewportChange && setIsFromViewportChange(board, true);
|
|
1128
|
+
}
|
|
1111
1129
|
}
|
|
1112
|
-
function
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
text.textContent = textContent;
|
|
1118
|
-
return text;
|
|
1130
|
+
function initializeViewportContainer(board) {
|
|
1131
|
+
const { width, height } = getViewportContainerRect(board);
|
|
1132
|
+
const viewportContainer = PlaitBoard.getViewportContainer(board);
|
|
1133
|
+
viewportContainer.style.width = `${width}px`;
|
|
1134
|
+
viewportContainer.style.height = `${height}px`;
|
|
1119
1135
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
}
|
|
1136
|
+
function initializeViewBox(board) {
|
|
1137
|
+
const zoom = board.viewport.zoom;
|
|
1138
|
+
const viewBox = getViewBox(board, zoom);
|
|
1139
|
+
setSVGViewBox(board, viewBox);
|
|
1140
|
+
}
|
|
1141
|
+
function initializeViewportOffset(board) {
|
|
1142
|
+
if (!board.viewport?.origination) {
|
|
1143
|
+
const zoom = board.viewport.zoom;
|
|
1144
|
+
const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1145
|
+
const viewBox = getViewBox(board, zoom);
|
|
1146
|
+
const centerX = viewBox[0] + viewBox[2] / 2;
|
|
1147
|
+
const centerY = viewBox[1] + viewBox[3] / 2;
|
|
1148
|
+
const origination = [centerX - viewportContainerRect.width / 2 / zoom, centerY - viewportContainerRect.height / 2 / zoom];
|
|
1149
|
+
updateViewportOrigination(board, origination);
|
|
1150
|
+
updateViewportOffset(board);
|
|
1151
|
+
return;
|
|
1137
1152
|
}
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1153
|
+
updateViewportOffset(board);
|
|
1154
|
+
}
|
|
1155
|
+
const updateViewportOrigination = (board, origination) => {
|
|
1156
|
+
BOARD_TO_VIEWPORT_ORIGINATION.set(board, origination);
|
|
1142
1157
|
};
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1158
|
+
const clearViewportOrigination = (board) => {
|
|
1159
|
+
BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
|
|
1145
1160
|
};
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const SELECTION_BORDER_COLOR = '#6698FF';
|
|
1152
|
-
const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
|
|
1153
|
-
const Selection = {
|
|
1154
|
-
isCollapsed(selection) {
|
|
1155
|
-
if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
|
|
1156
|
-
return true;
|
|
1157
|
-
}
|
|
1158
|
-
else {
|
|
1159
|
-
return false;
|
|
1160
|
-
}
|
|
1161
|
+
const getViewportOrigination = (board) => {
|
|
1162
|
+
const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
|
|
1163
|
+
if (origination) {
|
|
1164
|
+
return origination;
|
|
1161
1165
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
const getHitElements = (board, selection, match = () => true) => {
|
|
1165
|
-
const realSelection = selection || board.selection;
|
|
1166
|
-
const selectedElements = [];
|
|
1167
|
-
const isCollapsed = realSelection && realSelection.ranges.length === 1 && Selection.isCollapsed(realSelection.ranges[0]);
|
|
1168
|
-
depthFirstRecursion(board, node => {
|
|
1169
|
-
if (selectedElements.length > 0 && isCollapsed) {
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
if (!PlaitBoard.isBoard(node) &&
|
|
1173
|
-
match(node) &&
|
|
1174
|
-
realSelection &&
|
|
1175
|
-
realSelection.ranges.some(range => {
|
|
1176
|
-
return board.isHitSelection(node, range);
|
|
1177
|
-
})) {
|
|
1178
|
-
selectedElements.push(node);
|
|
1179
|
-
}
|
|
1180
|
-
}, getIsRecursionFunc(board), true);
|
|
1181
|
-
return selectedElements;
|
|
1182
|
-
};
|
|
1183
|
-
const getHitElementOfRoot = (board, rootElements, range) => {
|
|
1184
|
-
const newRootElements = [...rootElements].reverse();
|
|
1185
|
-
return newRootElements.find(item => {
|
|
1186
|
-
return board.isHitSelection(item, range);
|
|
1187
|
-
});
|
|
1188
|
-
};
|
|
1189
|
-
const isHitElements = (board, elements, ranges) => {
|
|
1190
|
-
let isIntersectionElements = false;
|
|
1191
|
-
if (elements.length) {
|
|
1192
|
-
elements.map(item => {
|
|
1193
|
-
if (!isIntersectionElements) {
|
|
1194
|
-
isIntersectionElements = ranges.some(range => {
|
|
1195
|
-
return board.isHitSelection(item, range);
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
});
|
|
1166
|
+
else {
|
|
1167
|
+
return board.viewport.origination;
|
|
1199
1168
|
}
|
|
1200
|
-
return isIntersectionElements;
|
|
1201
|
-
};
|
|
1202
|
-
const cacheSelectedElements = (board, selectedElements) => {
|
|
1203
|
-
BOARD_TO_SELECTED_ELEMENT.set(board, selectedElements);
|
|
1204
|
-
};
|
|
1205
|
-
const getSelectedElements = (board) => {
|
|
1206
|
-
return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
|
|
1207
1169
|
};
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
cacheSelectedElements(board, [...selectedElements, element]);
|
|
1170
|
+
const isFromScrolling = (board) => {
|
|
1171
|
+
return !!IS_FROM_SCROLLING.get(board);
|
|
1211
1172
|
};
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
const newSelectedElements = selectedElements.filter(value => value !== element);
|
|
1215
|
-
cacheSelectedElements(board, newSelectedElements);
|
|
1173
|
+
const setIsFromScrolling = (board, state) => {
|
|
1174
|
+
IS_FROM_SCROLLING.set(board, state);
|
|
1216
1175
|
};
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1176
|
+
const isFromViewportChange = (board) => {
|
|
1177
|
+
return !!IS_FROM_VIEWPORT_CHANGE.get(board);
|
|
1219
1178
|
};
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
return !!selectedElements.find(value => value === element);
|
|
1179
|
+
const setIsFromViewportChange = (board, state) => {
|
|
1180
|
+
IS_FROM_VIEWPORT_CHANGE.set(board, state);
|
|
1223
1181
|
};
|
|
1182
|
+
function scrollToRectangle(board, client) { }
|
|
1224
1183
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1184
|
+
let timerId = null;
|
|
1185
|
+
const throttleRAF = (fn) => {
|
|
1186
|
+
const scheduleFunc = () => {
|
|
1187
|
+
timerId = requestAnimationFrame(() => {
|
|
1188
|
+
timerId = null;
|
|
1189
|
+
fn();
|
|
1190
|
+
});
|
|
1191
|
+
};
|
|
1192
|
+
if (timerId !== null) {
|
|
1193
|
+
cancelAnimationFrame(timerId);
|
|
1194
|
+
timerId = null;
|
|
1234
1195
|
}
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
if (this.element) {
|
|
1246
|
-
ELEMENT_TO_COMPONENT.set(this.element, this);
|
|
1247
|
-
}
|
|
1248
|
-
if (this.initialized) {
|
|
1249
|
-
this.cdr.markForCheck();
|
|
1250
|
-
if (hasOnContextChanged(this)) {
|
|
1251
|
-
this.onContextChanged(value, previousContext);
|
|
1252
|
-
}
|
|
1196
|
+
scheduleFunc();
|
|
1197
|
+
};
|
|
1198
|
+
const debounce = (func, wait, options) => {
|
|
1199
|
+
let timerSubscription = null;
|
|
1200
|
+
return () => {
|
|
1201
|
+
if (timerSubscription && !timerSubscription.closed) {
|
|
1202
|
+
timerSubscription.unsubscribe();
|
|
1203
|
+
timerSubscription = timer(wait).subscribe(() => {
|
|
1204
|
+
func();
|
|
1205
|
+
});
|
|
1253
1206
|
}
|
|
1254
1207
|
else {
|
|
1255
|
-
if (
|
|
1256
|
-
|
|
1257
|
-
this.rootG = createG();
|
|
1258
|
-
this.rootG.append(this.g);
|
|
1259
|
-
}
|
|
1260
|
-
else {
|
|
1261
|
-
this.g = createG();
|
|
1208
|
+
if (options?.leading) {
|
|
1209
|
+
func();
|
|
1262
1210
|
}
|
|
1211
|
+
timerSubscription = timer(wait).subscribe();
|
|
1263
1212
|
}
|
|
1213
|
+
};
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
const getMovingElements = (board) => {
|
|
1217
|
+
return BOARD_TO_MOVING_ELEMENT.get(board) || [];
|
|
1218
|
+
};
|
|
1219
|
+
const addMovingElements = (board, elements) => {
|
|
1220
|
+
const movingElements = getMovingElements(board);
|
|
1221
|
+
const newElements = elements.filter(item => !movingElements.find(movingElement => movingElement.key === item.key));
|
|
1222
|
+
cacheMovingElements(board, [...movingElements, ...newElements]);
|
|
1223
|
+
};
|
|
1224
|
+
const removeMovingElements = (board) => {
|
|
1225
|
+
BOARD_TO_MOVING_ELEMENT.delete(board);
|
|
1226
|
+
};
|
|
1227
|
+
const cacheMovingElements = (board, elements) => {
|
|
1228
|
+
BOARD_TO_MOVING_ELEMENT.set(board, elements);
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
function cloneCSSStyle(nativeNode, clonedNode) {
|
|
1232
|
+
const targetStyle = clonedNode.style;
|
|
1233
|
+
if (!targetStyle) {
|
|
1234
|
+
return;
|
|
1264
1235
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
return this.context && this.context.element;
|
|
1270
|
-
}
|
|
1271
|
-
get board() {
|
|
1272
|
-
return this.context && this.context.board;
|
|
1273
|
-
}
|
|
1274
|
-
get selected() {
|
|
1275
|
-
return this.context && this.context.selected;
|
|
1276
|
-
}
|
|
1277
|
-
get effect() {
|
|
1278
|
-
return this.context && this.context.effect;
|
|
1279
|
-
}
|
|
1280
|
-
constructor(cdr) {
|
|
1281
|
-
this.cdr = cdr;
|
|
1282
|
-
this.initialized = false;
|
|
1283
|
-
}
|
|
1284
|
-
ngOnInit() {
|
|
1285
|
-
if (this.element.type) {
|
|
1286
|
-
(this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
|
|
1287
|
-
}
|
|
1288
|
-
this.initialized = true;
|
|
1236
|
+
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
1237
|
+
if (sourceStyle.cssText) {
|
|
1238
|
+
targetStyle.cssText = sourceStyle.cssText;
|
|
1239
|
+
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
1289
1240
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
(this.rootG || this.g).remove();
|
|
1241
|
+
else {
|
|
1242
|
+
Array.from(sourceStyle).forEach(name => {
|
|
1243
|
+
let value = sourceStyle.getPropertyValue(name);
|
|
1244
|
+
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
1245
|
+
});
|
|
1296
1246
|
}
|
|
1297
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1298
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
|
|
1299
|
-
}
|
|
1300
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
|
|
1301
|
-
type: Directive
|
|
1302
|
-
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { context: [{
|
|
1303
|
-
type: Input
|
|
1304
|
-
}] } });
|
|
1305
|
-
const ELEMENT_TO_COMPONENT = new WeakMap();
|
|
1306
|
-
|
|
1307
|
-
function transformPoints(board, points) {
|
|
1308
|
-
const newPoints = points.map(point => {
|
|
1309
|
-
return transformPoint(board, point);
|
|
1310
|
-
});
|
|
1311
|
-
return newPoints;
|
|
1312
1247
|
}
|
|
1313
|
-
function
|
|
1314
|
-
const
|
|
1315
|
-
const
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1248
|
+
function createCanvas(width, height, fillStyle) {
|
|
1249
|
+
const canvas = document.createElement('canvas');
|
|
1250
|
+
const ctx = canvas.getContext('2d');
|
|
1251
|
+
canvas.width = width;
|
|
1252
|
+
canvas.height = height;
|
|
1253
|
+
canvas.style.width = `${width}px`;
|
|
1254
|
+
canvas.style.height = `${height}px`;
|
|
1255
|
+
ctx.strokeStyle = '#ffffff';
|
|
1256
|
+
ctx.fillStyle = fillStyle;
|
|
1257
|
+
ctx.fillRect(0, 0, width, height);
|
|
1258
|
+
return {
|
|
1259
|
+
canvas,
|
|
1260
|
+
ctx
|
|
1261
|
+
};
|
|
1320
1262
|
}
|
|
1321
|
-
function
|
|
1322
|
-
|
|
1323
|
-
const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
|
|
1324
|
-
const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
|
|
1325
|
-
return distances === 0;
|
|
1263
|
+
function isElementNode(node) {
|
|
1264
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
1326
1265
|
}
|
|
1327
|
-
function
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1266
|
+
function cloneSvg(board, options) {
|
|
1267
|
+
const elementHostBox = getRectangleByElements(board, board.children, true);
|
|
1268
|
+
const { width, height, x, y } = elementHostBox;
|
|
1269
|
+
const { padding = 4, inlineStyleClassNames } = options;
|
|
1270
|
+
const sourceSvg = PlaitBoard.getHost(board);
|
|
1271
|
+
const cloneSvgElement = sourceSvg.cloneNode(true);
|
|
1272
|
+
cloneSvgElement.style.width = `${width}px`;
|
|
1273
|
+
cloneSvgElement.style.height = `${height}px`;
|
|
1274
|
+
cloneSvgElement.style.backgroundColor = '';
|
|
1275
|
+
cloneSvgElement.setAttribute('width', `${width}`);
|
|
1276
|
+
cloneSvgElement.setAttribute('height', `${height}`);
|
|
1277
|
+
cloneSvgElement.setAttribute('viewBox', [x - padding, y - padding, width + 2 * padding, height + 2 * padding].join(','));
|
|
1278
|
+
if (inlineStyleClassNames) {
|
|
1279
|
+
const sourceNodes = Array.from(sourceSvg.querySelectorAll(inlineStyleClassNames));
|
|
1280
|
+
const cloneNodes = Array.from(cloneSvgElement.querySelectorAll(inlineStyleClassNames));
|
|
1281
|
+
sourceNodes.forEach((node, index) => {
|
|
1282
|
+
const cloneNode = cloneNodes[index];
|
|
1283
|
+
const childElements = Array.from(node.querySelectorAll('*')).filter(isElementNode);
|
|
1284
|
+
const cloneChildElements = Array.from(cloneNode.querySelectorAll('*')).filter(isElementNode);
|
|
1285
|
+
sourceNodes.push(...childElements);
|
|
1286
|
+
cloneNodes.push(...cloneChildElements);
|
|
1287
|
+
});
|
|
1288
|
+
sourceNodes.forEach((node, index) => {
|
|
1289
|
+
const cloneNode = cloneNodes[index];
|
|
1290
|
+
cloneCSSStyle(node, cloneNode);
|
|
1291
|
+
});
|
|
1333
1292
|
}
|
|
1334
|
-
return
|
|
1293
|
+
return cloneSvgElement;
|
|
1335
1294
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
return newForeignObject;
|
|
1295
|
+
function loadImage(src) {
|
|
1296
|
+
return new Promise((resolve, reject) => {
|
|
1297
|
+
const img = new Image();
|
|
1298
|
+
img.onload = () => resolve(img);
|
|
1299
|
+
img.onerror = () => reject(new Error('Failed to load image'));
|
|
1300
|
+
img.src = src;
|
|
1301
|
+
});
|
|
1344
1302
|
}
|
|
1345
|
-
function
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
foreignObject.setAttribute('width', `${width}`);
|
|
1349
|
-
foreignObject.setAttribute('height', `${height}`);
|
|
1350
|
-
foreignObject.setAttribute('x', `${x}`);
|
|
1351
|
-
foreignObject.setAttribute('y', `${y}`);
|
|
1303
|
+
async function toImage(board, options) {
|
|
1304
|
+
if (!board) {
|
|
1305
|
+
return undefined;
|
|
1352
1306
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1307
|
+
const elementHostBox = getRectangleByElements(board, board.children, true);
|
|
1308
|
+
const { ratio = 2, fillStyle = 'transparent' } = options;
|
|
1309
|
+
const { width, height } = elementHostBox;
|
|
1310
|
+
const ratioWidth = width * ratio;
|
|
1311
|
+
const ratioHeight = height * ratio;
|
|
1312
|
+
const cloneSvgElement = cloneSvg(board, options);
|
|
1313
|
+
const { canvas, ctx } = createCanvas(ratioWidth, ratioHeight, fillStyle);
|
|
1314
|
+
const svgStr = new XMLSerializer().serializeToString(cloneSvgElement);
|
|
1315
|
+
const imgSrc = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;
|
|
1316
|
+
try {
|
|
1317
|
+
const img = await loadImage(imgSrc);
|
|
1318
|
+
ctx.drawImage(img, 0, 0, ratioWidth, ratioHeight);
|
|
1319
|
+
const url = canvas.toDataURL('image/png');
|
|
1320
|
+
return url;
|
|
1321
|
+
}
|
|
1322
|
+
catch (error) {
|
|
1323
|
+
console.error('Error converting SVG to image:', error);
|
|
1324
|
+
return undefined;
|
|
1358
1325
|
}
|
|
1359
1326
|
}
|
|
1327
|
+
function downloadImage(url, name) {
|
|
1328
|
+
const a = document.createElement('a');
|
|
1329
|
+
a.href = url;
|
|
1330
|
+
a.download = name;
|
|
1331
|
+
a.click();
|
|
1332
|
+
a.remove();
|
|
1333
|
+
}
|
|
1360
1334
|
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
|
|
1370
|
-
// "modern" Edge was released at 79.x
|
|
1371
|
-
const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
|
|
1372
|
-
const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
|
|
1373
|
-
// Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
|
|
1374
|
-
const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
|
|
1375
|
-
|
|
1376
|
-
/**
|
|
1377
|
-
* Check whether to merge an operation into the previous operation.
|
|
1378
|
-
*/
|
|
1379
|
-
const shouldMerge = (op, prev) => {
|
|
1380
|
-
if (op.type === 'set_viewport' && op.type === prev?.type) {
|
|
1381
|
-
return true;
|
|
1335
|
+
const getClipboardByKey = (key) => {
|
|
1336
|
+
return `application/x-plait-${key}-fragment`;
|
|
1337
|
+
};
|
|
1338
|
+
const setClipboardData = (data, elements) => {
|
|
1339
|
+
const result = [...elements];
|
|
1340
|
+
const pluginContextResult = getDataFromClipboard(data);
|
|
1341
|
+
if (pluginContextResult) {
|
|
1342
|
+
result.push(...pluginContextResult);
|
|
1382
1343
|
}
|
|
1383
|
-
|
|
1344
|
+
const stringObj = JSON.stringify(result);
|
|
1345
|
+
const encoded = window.btoa(encodeURIComponent(stringObj));
|
|
1346
|
+
data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
|
|
1384
1347
|
};
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1348
|
+
const setClipboardDataByText = (data, text) => {
|
|
1349
|
+
const pluginContextResult = getTextFromClipboard(data);
|
|
1350
|
+
data?.setData(`text/plain`, text + '\n' + pluginContextResult);
|
|
1351
|
+
};
|
|
1352
|
+
const setClipboardDataByMedia = (data, media, key) => {
|
|
1353
|
+
const stringObj = JSON.stringify(media);
|
|
1354
|
+
const encoded = window.btoa(encodeURIComponent(stringObj));
|
|
1355
|
+
data?.setData(getClipboardByKey(key), encoded);
|
|
1356
|
+
};
|
|
1357
|
+
const getDataFromClipboard = (data) => {
|
|
1358
|
+
const encoded = data?.getData(`application/${CLIP_BOARD_FORMAT_KEY}`);
|
|
1359
|
+
let nodesData = [];
|
|
1360
|
+
if (encoded) {
|
|
1361
|
+
const decoded = decodeURIComponent(window.atob(encoded));
|
|
1362
|
+
nodesData = JSON.parse(decoded);
|
|
1391
1363
|
}
|
|
1392
|
-
return
|
|
1364
|
+
return nodesData;
|
|
1393
1365
|
};
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
const
|
|
1398
|
-
|
|
1399
|
-
|
|
1366
|
+
const getTextFromClipboard = (data) => {
|
|
1367
|
+
return (data ? data.getData(`text/plain`) : '');
|
|
1368
|
+
};
|
|
1369
|
+
const getClipboardDataByMedia = (data, key) => {
|
|
1370
|
+
const encoded = data?.getData(getClipboardByKey(key));
|
|
1371
|
+
let imageItem = null;
|
|
1372
|
+
if (encoded) {
|
|
1373
|
+
const decoded = decodeURIComponent(window.atob(encoded));
|
|
1374
|
+
imageItem = JSON.parse(decoded);
|
|
1375
|
+
}
|
|
1376
|
+
return imageItem;
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
const isPreventTouchMove = (board) => {
|
|
1380
|
+
return !!IS_PREVENT_TOUCH_MOVE.get(board);
|
|
1381
|
+
};
|
|
1382
|
+
const preventTouchMove = (board, state) => {
|
|
1383
|
+
IS_PREVENT_TOUCH_MOVE.set(board, state);
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
const PlaitElement = {
|
|
1387
|
+
isRootElement(value) {
|
|
1388
|
+
const parent = NODE_TO_PARENT.get(value);
|
|
1389
|
+
if (parent && PlaitBoard.isBoard(parent)) {
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
getComponent(value) {
|
|
1397
|
+
return ELEMENT_TO_COMPONENT.get(value);
|
|
1400
1398
|
}
|
|
1401
|
-
return true;
|
|
1402
1399
|
};
|
|
1403
|
-
|
|
1400
|
+
|
|
1401
|
+
const Path = {
|
|
1404
1402
|
/**
|
|
1405
|
-
* Get
|
|
1403
|
+
* Get a list of ancestor paths for a given path.
|
|
1404
|
+
*
|
|
1405
|
+
* The paths are sorted from shallowest to deepest ancestor. However, if the
|
|
1406
|
+
* `reverse: true` option is passed, they are reversed.
|
|
1406
1407
|
*/
|
|
1407
|
-
|
|
1408
|
-
|
|
1408
|
+
ancestors(path, options = {}) {
|
|
1409
|
+
const { reverse = false } = options;
|
|
1410
|
+
let paths = Path.levels(path, options);
|
|
1411
|
+
if (reverse) {
|
|
1412
|
+
paths = paths.slice(1);
|
|
1413
|
+
}
|
|
1414
|
+
else {
|
|
1415
|
+
paths = paths.slice(0, -1);
|
|
1416
|
+
}
|
|
1417
|
+
return paths;
|
|
1409
1418
|
},
|
|
1410
1419
|
/**
|
|
1411
|
-
* Get
|
|
1420
|
+
* Get a list of paths at every level down to a path. Note: this is the same
|
|
1421
|
+
* as `Path.ancestors`, but including the path itself.
|
|
1422
|
+
*
|
|
1423
|
+
* The paths are sorted from shallowest to deepest. However, if the `reverse:
|
|
1424
|
+
* true` option is passed, they are reversed.
|
|
1412
1425
|
*/
|
|
1413
|
-
|
|
1414
|
-
|
|
1426
|
+
levels(path, options = {}) {
|
|
1427
|
+
const { reverse = false } = options;
|
|
1428
|
+
const list = [];
|
|
1429
|
+
for (let i = 0; i <= path.length; i++) {
|
|
1430
|
+
list.push(path.slice(0, i));
|
|
1431
|
+
}
|
|
1432
|
+
if (reverse) {
|
|
1433
|
+
list.reverse();
|
|
1434
|
+
}
|
|
1435
|
+
return list;
|
|
1436
|
+
},
|
|
1437
|
+
parent(path) {
|
|
1438
|
+
if (path.length === 0) {
|
|
1439
|
+
throw new Error(`Cannot get the parent path of the root path [${path}].`);
|
|
1440
|
+
}
|
|
1441
|
+
return path.slice(0, -1);
|
|
1442
|
+
},
|
|
1443
|
+
next(path) {
|
|
1444
|
+
if (path.length === 0) {
|
|
1445
|
+
throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
|
|
1446
|
+
}
|
|
1447
|
+
const last = path[path.length - 1];
|
|
1448
|
+
return path.slice(0, -1).concat(last + 1);
|
|
1449
|
+
},
|
|
1450
|
+
hasPrevious(path) {
|
|
1451
|
+
return path[path.length - 1] > 0;
|
|
1452
|
+
},
|
|
1453
|
+
previous(path) {
|
|
1454
|
+
if (path.length === 0) {
|
|
1455
|
+
throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
|
|
1456
|
+
}
|
|
1457
|
+
const last = path[path.length - 1];
|
|
1458
|
+
return path.slice(0, -1).concat(last - 1);
|
|
1415
1459
|
},
|
|
1416
1460
|
/**
|
|
1417
|
-
*
|
|
1418
|
-
* the new operations into previous save point in the history.
|
|
1461
|
+
* Check if a path is an ancestor of another.
|
|
1419
1462
|
*/
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
MERGING.set(board, false);
|
|
1423
|
-
fn();
|
|
1424
|
-
MERGING.set(board, prev);
|
|
1463
|
+
isAncestor(path, another) {
|
|
1464
|
+
return path.length < another.length && Path.compare(path, another) === 0;
|
|
1425
1465
|
},
|
|
1426
1466
|
/**
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1467
|
+
* Compare a path to another, returning an integer indicating whether the path
|
|
1468
|
+
* was before, at, or after the other.
|
|
1469
|
+
*
|
|
1470
|
+
* Note: Two paths of unequal length can still receive a `0` result if one is
|
|
1471
|
+
* directly above or below the other. If you want exact matching, use
|
|
1472
|
+
* [[Path.equals]] instead.
|
|
1429
1473
|
*/
|
|
1430
|
-
|
|
1431
|
-
const
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
/**
|
|
1439
|
-
* Hotkey mappings for each platform.
|
|
1440
|
-
*/
|
|
1441
|
-
const HOTKEYS = {
|
|
1442
|
-
bold: 'mod+b',
|
|
1443
|
-
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
1444
|
-
moveBackward: 'left',
|
|
1445
|
-
moveForward: 'right',
|
|
1446
|
-
moveUp: 'up',
|
|
1447
|
-
moveDown: 'down',
|
|
1448
|
-
moveWordBackward: 'ctrl+left',
|
|
1449
|
-
moveWordForward: 'ctrl+right',
|
|
1450
|
-
deleteBackward: 'shift?+backspace',
|
|
1451
|
-
deleteForward: 'shift?+delete',
|
|
1452
|
-
extendBackward: 'shift+left',
|
|
1453
|
-
extendForward: 'shift+right',
|
|
1454
|
-
italic: 'mod+i',
|
|
1455
|
-
splitBlock: 'shift?+enter',
|
|
1456
|
-
undo: 'mod+z'
|
|
1457
|
-
};
|
|
1458
|
-
const APPLE_HOTKEYS = {
|
|
1459
|
-
moveLineBackward: 'opt+up',
|
|
1460
|
-
moveLineForward: 'opt+down',
|
|
1461
|
-
moveWordBackward: 'opt+left',
|
|
1462
|
-
moveWordForward: 'opt+right',
|
|
1463
|
-
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
1464
|
-
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
1465
|
-
deleteLineBackward: 'cmd+shift?+backspace',
|
|
1466
|
-
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
1467
|
-
deleteWordBackward: 'opt+shift?+backspace',
|
|
1468
|
-
deleteWordForward: 'opt+shift?+delete',
|
|
1469
|
-
extendLineBackward: 'opt+shift+up',
|
|
1470
|
-
extendLineForward: 'opt+shift+down',
|
|
1471
|
-
redo: 'cmd+shift+z',
|
|
1472
|
-
transposeCharacter: 'ctrl+t'
|
|
1473
|
-
};
|
|
1474
|
-
const WINDOWS_HOTKEYS = {
|
|
1475
|
-
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
1476
|
-
deleteWordForward: 'ctrl+shift?+delete',
|
|
1477
|
-
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
1478
|
-
};
|
|
1479
|
-
/**
|
|
1480
|
-
* Create a platform-aware hotkey checker.
|
|
1481
|
-
*/
|
|
1482
|
-
const create = (key) => {
|
|
1483
|
-
const generic = HOTKEYS[key];
|
|
1484
|
-
const apple = APPLE_HOTKEYS[key];
|
|
1485
|
-
const windows = WINDOWS_HOTKEYS[key];
|
|
1486
|
-
const isGeneric = generic && isKeyHotkey(generic);
|
|
1487
|
-
const isApple = apple && isKeyHotkey(apple);
|
|
1488
|
-
const isWindows = windows && isKeyHotkey(windows);
|
|
1489
|
-
return (event) => {
|
|
1490
|
-
if (isGeneric && isGeneric(event)) {
|
|
1491
|
-
return true;
|
|
1492
|
-
}
|
|
1493
|
-
if (IS_APPLE && isApple && isApple(event)) {
|
|
1494
|
-
return true;
|
|
1474
|
+
compare(path, another) {
|
|
1475
|
+
const min = Math.min(path.length, another.length);
|
|
1476
|
+
for (let i = 0; i < min; i++) {
|
|
1477
|
+
if (path[i] < another[i])
|
|
1478
|
+
return -1;
|
|
1479
|
+
if (path[i] > another[i])
|
|
1480
|
+
return 1;
|
|
1495
1481
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1482
|
+
return 0;
|
|
1483
|
+
},
|
|
1484
|
+
/**
|
|
1485
|
+
* Check if a path is exactly equal to another.
|
|
1486
|
+
*/
|
|
1487
|
+
equals(path, another) {
|
|
1488
|
+
return path.length === another.length && path.every((n, i) => n === another[i]);
|
|
1489
|
+
},
|
|
1490
|
+
/**
|
|
1491
|
+
* Check if a path ends before one of the indexes in another.
|
|
1492
|
+
*/
|
|
1493
|
+
endsBefore(path, another) {
|
|
1494
|
+
const i = path.length - 1;
|
|
1495
|
+
const as = path.slice(0, i);
|
|
1496
|
+
const bs = another.slice(0, i);
|
|
1497
|
+
const av = path[i];
|
|
1498
|
+
const bv = another[i];
|
|
1499
|
+
return Path.equals(as, bs) && av < bv;
|
|
1500
|
+
},
|
|
1501
|
+
/**
|
|
1502
|
+
* Check if a path is a sibling of another.
|
|
1503
|
+
*/
|
|
1504
|
+
isSibling(path, another) {
|
|
1505
|
+
if (path.length !== another.length) {
|
|
1506
|
+
return false;
|
|
1498
1507
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1508
|
+
const as = path.slice(0, -1);
|
|
1509
|
+
const bs = another.slice(0, -1);
|
|
1510
|
+
const al = path[path.length - 1];
|
|
1511
|
+
const bl = another[another.length - 1];
|
|
1512
|
+
return al !== bl && Path.equals(as, bs);
|
|
1513
|
+
},
|
|
1514
|
+
transform(path, operation) {
|
|
1515
|
+
return produce(path, p => {
|
|
1516
|
+
// PERF: Exit early if the operation is guaranteed not to have an effect.
|
|
1517
|
+
if (!path || path?.length === 0) {
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
if (p === null) {
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
switch (operation.type) {
|
|
1524
|
+
case 'insert_node': {
|
|
1525
|
+
const { path: op } = operation;
|
|
1526
|
+
if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
|
|
1527
|
+
p[op.length - 1] += 1;
|
|
1528
|
+
}
|
|
1529
|
+
break;
|
|
1530
|
+
}
|
|
1531
|
+
case 'remove_node': {
|
|
1532
|
+
const { path: op } = operation;
|
|
1533
|
+
if (Path.equals(op, p) || Path.isAncestor(op, p)) {
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
else if (Path.endsBefore(op, p)) {
|
|
1537
|
+
p[op.length - 1] -= 1;
|
|
1538
|
+
}
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
case 'move_node': {
|
|
1542
|
+
const { path: op, newPath: onp } = operation;
|
|
1543
|
+
// If the old and new path are the same, it's a no-op.
|
|
1544
|
+
if (Path.equals(op, onp)) {
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
if (Path.isAncestor(op, p) || Path.equals(op, p)) {
|
|
1548
|
+
const copy = onp.slice();
|
|
1549
|
+
// op.length <= onp.length is different for slate
|
|
1550
|
+
// resolve drag from [0, 0] to [0, 3] issue
|
|
1551
|
+
if (Path.endsBefore(op, onp) && op.length <= onp.length) {
|
|
1552
|
+
copy[op.length - 1] -= 1;
|
|
1553
|
+
}
|
|
1554
|
+
return copy.concat(p.slice(op.length));
|
|
1555
|
+
}
|
|
1556
|
+
else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
|
|
1557
|
+
if (Path.endsBefore(op, p)) {
|
|
1558
|
+
p[op.length - 1] -= 1;
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
p[op.length - 1] += 1;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
|
|
1565
|
+
if (Path.endsBefore(op, p)) {
|
|
1566
|
+
p[op.length - 1] -= 1;
|
|
1567
|
+
}
|
|
1568
|
+
p[onp.length - 1] += 1;
|
|
1569
|
+
}
|
|
1570
|
+
else if (Path.endsBefore(op, p)) {
|
|
1571
|
+
if (Path.equals(onp, p)) {
|
|
1572
|
+
p[onp.length - 1] += 1;
|
|
1573
|
+
}
|
|
1574
|
+
p[op.length - 1] -= 1;
|
|
1575
|
+
}
|
|
1576
|
+
break;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
return p;
|
|
1580
|
+
});
|
|
1540
1581
|
}
|
|
1541
|
-
|
|
1542
|
-
}
|
|
1582
|
+
};
|
|
1543
1583
|
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1584
|
+
const PlaitNode = {
|
|
1585
|
+
parent: (board, path) => {
|
|
1586
|
+
const parentPath = Path.parent(path);
|
|
1587
|
+
const p = PlaitNode.get(board, parentPath);
|
|
1588
|
+
return p;
|
|
1589
|
+
},
|
|
1590
|
+
/**
|
|
1591
|
+
* Return a generator of all the ancestor nodes above a specific path.
|
|
1592
|
+
*
|
|
1593
|
+
* By default the order is top-down, from highest to lowest ancestor in
|
|
1594
|
+
* the tree, but you can pass the `reverse: true` option to go bottom-up.
|
|
1595
|
+
*/
|
|
1596
|
+
*parents(root, path, options = {}) {
|
|
1597
|
+
for (const p of Path.ancestors(path, options)) {
|
|
1598
|
+
const n = PlaitNode.get(root, p);
|
|
1599
|
+
yield n;
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
get(root, path) {
|
|
1603
|
+
let node = root;
|
|
1604
|
+
for (let i = 0; i < path.length; i++) {
|
|
1605
|
+
const p = path[i];
|
|
1606
|
+
if (!node || !node.children || !node.children[p]) {
|
|
1607
|
+
throw new Error(`Cannot find a descendant at path [${path}]`);
|
|
1608
|
+
}
|
|
1609
|
+
node = node.children[p];
|
|
1556
1610
|
}
|
|
1611
|
+
return node;
|
|
1612
|
+
},
|
|
1613
|
+
last(board, path) {
|
|
1614
|
+
let n = PlaitNode.get(board, path);
|
|
1615
|
+
while (n && n.children && n.children.length > 0) {
|
|
1616
|
+
const i = n.children.length - 1;
|
|
1617
|
+
n = n.children[i];
|
|
1618
|
+
}
|
|
1619
|
+
return n;
|
|
1557
1620
|
}
|
|
1558
|
-
const point1 = [x1 + radius, y1];
|
|
1559
|
-
const point2 = [x2 - radius, y1];
|
|
1560
|
-
const point3 = [x2, y1 + radius];
|
|
1561
|
-
const point4 = [x2, y2 - radius];
|
|
1562
|
-
const point5 = [x2 - radius, y2];
|
|
1563
|
-
const point6 = [x1 + radius, y2];
|
|
1564
|
-
const point7 = [x1, y2 - radius];
|
|
1565
|
-
const point8 = [x1, y1 + radius];
|
|
1566
|
-
return rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options);
|
|
1567
|
-
}
|
|
1568
|
-
const drawRectangle = (board, rectangle, options) => {
|
|
1569
|
-
const roughSVG = PlaitBoard.getRoughSVG(board);
|
|
1570
|
-
const rectangleG = roughSVG.rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options);
|
|
1571
|
-
const paths = rectangleG.querySelectorAll('path');
|
|
1572
|
-
paths.forEach(path => {
|
|
1573
|
-
path.setAttribute('stroke-linecap', 'square');
|
|
1574
|
-
});
|
|
1575
|
-
return rectangleG;
|
|
1576
1621
|
};
|
|
1577
1622
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const rotatePoint = [
|
|
1586
|
-
end[0] > start[0] ? end[0] - rotateWidth : end[0] + rotateWidth,
|
|
1587
|
-
end[1] > start[1] ? end[1] - rotateHeight : end[1] + rotateHeight
|
|
1588
|
-
];
|
|
1589
|
-
const pointRight = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (degree * Math.PI) / 180);
|
|
1590
|
-
const pointLeft = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (-degree * Math.PI) / 180);
|
|
1591
|
-
return { pointLeft, pointRight };
|
|
1592
|
-
}
|
|
1593
|
-
function drawArrow(rs, start, end, options, maxHypotenuseLength = 10, degree = 40) {
|
|
1594
|
-
const { pointLeft, pointRight } = arrowPoints(start, end, maxHypotenuseLength, degree);
|
|
1595
|
-
const arrowLineLeft = rs.linearPath([pointLeft, end], options);
|
|
1596
|
-
const arrowLineRight = rs.linearPath([pointRight, end], options);
|
|
1597
|
-
return [arrowLineLeft, arrowLineRight];
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
function drawCircle(roughSVG, point, diameter, options) {
|
|
1601
|
-
return roughSVG.circle(point[0], point[1], diameter, options);
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
function drawLine(rs, start, end, options) {
|
|
1605
|
-
return rs.linearPath([start, end], options);
|
|
1606
|
-
}
|
|
1607
|
-
function drawLinearPath(points, options) {
|
|
1608
|
-
const g = createG();
|
|
1609
|
-
const path = createPath();
|
|
1610
|
-
let polylinePath = '';
|
|
1611
|
-
points.forEach((point, index) => {
|
|
1612
|
-
if (index === 0) {
|
|
1613
|
-
polylinePath += `M ${point[0]} ${point[1]} `;
|
|
1623
|
+
const isSetViewportOperation = (value) => {
|
|
1624
|
+
return value.type === 'set_viewport';
|
|
1625
|
+
};
|
|
1626
|
+
const inverse = (op) => {
|
|
1627
|
+
switch (op.type) {
|
|
1628
|
+
case 'insert_node': {
|
|
1629
|
+
return { ...op, type: 'remove_node' };
|
|
1614
1630
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1631
|
+
case 'remove_node': {
|
|
1632
|
+
return { ...op, type: 'insert_node' };
|
|
1617
1633
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1634
|
+
case 'move_node': {
|
|
1635
|
+
const { newPath, path } = op;
|
|
1636
|
+
// PERF: in this case the move operation is a no-op anyways.
|
|
1637
|
+
if (Path.equals(newPath, path)) {
|
|
1638
|
+
return op;
|
|
1639
|
+
}
|
|
1640
|
+
// when operation path is [0,0] -> [0,2], should exec Path.transform to get [0,1] -> [0,0]
|
|
1641
|
+
// shoud not return [0,2] -> [0,0] #WIK-8981
|
|
1642
|
+
// if (Path.isSibling(path, newPath)) {
|
|
1643
|
+
// return { ...op, path: newPath, newPath: path };
|
|
1644
|
+
// }
|
|
1645
|
+
// If the move does not happen within a single parent it is possible
|
|
1646
|
+
// for the move to impact the true path to the location where the node
|
|
1647
|
+
// was removed from and where it was inserted. We have to adjust for this
|
|
1648
|
+
// and find the original path. We can accomplish this (only in non-sibling)
|
|
1649
|
+
// moves by looking at the impact of the move operation on the node
|
|
1650
|
+
// after the original move path.
|
|
1651
|
+
const inversePath = Path.transform(path, op);
|
|
1652
|
+
const inverseNewPath = Path.transform(Path.next(path), op);
|
|
1653
|
+
return { ...op, path: inversePath, newPath: inverseNewPath };
|
|
1633
1654
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1655
|
+
case 'set_node': {
|
|
1656
|
+
const { properties, newProperties } = op;
|
|
1657
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
1636
1658
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
timerId = null;
|
|
1657
|
-
}
|
|
1658
|
-
scheduleFunc();
|
|
1659
|
-
};
|
|
1660
|
-
const debounce = (func, wait, options) => {
|
|
1661
|
-
let timerSubscription = null;
|
|
1662
|
-
return () => {
|
|
1663
|
-
if (timerSubscription && !timerSubscription.closed) {
|
|
1664
|
-
timerSubscription.unsubscribe();
|
|
1665
|
-
timerSubscription = timer(wait).subscribe(() => {
|
|
1666
|
-
func();
|
|
1667
|
-
});
|
|
1659
|
+
case 'set_selection': {
|
|
1660
|
+
const { properties, newProperties } = op;
|
|
1661
|
+
if (properties == null) {
|
|
1662
|
+
return {
|
|
1663
|
+
...op,
|
|
1664
|
+
properties: newProperties,
|
|
1665
|
+
newProperties: null
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
else if (newProperties == null) {
|
|
1669
|
+
return {
|
|
1670
|
+
...op,
|
|
1671
|
+
properties: null,
|
|
1672
|
+
newProperties: properties
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
else {
|
|
1676
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
1677
|
+
}
|
|
1668
1678
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1679
|
+
case 'set_viewport': {
|
|
1680
|
+
const { properties, newProperties } = op;
|
|
1681
|
+
if (properties == null) {
|
|
1682
|
+
return {
|
|
1683
|
+
...op,
|
|
1684
|
+
properties: newProperties,
|
|
1685
|
+
newProperties: newProperties
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
else if (newProperties == null) {
|
|
1689
|
+
return {
|
|
1690
|
+
...op,
|
|
1691
|
+
properties: properties,
|
|
1692
|
+
newProperties: properties
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
else {
|
|
1696
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
1672
1697
|
}
|
|
1673
|
-
timerSubscription = timer(wait).subscribe();
|
|
1674
1698
|
}
|
|
1675
|
-
|
|
1676
|
-
};
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1699
|
+
case 'set_theme': {
|
|
1700
|
+
const { properties, newProperties } = op;
|
|
1701
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1680
1704
|
};
|
|
1681
|
-
const
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
cacheMovingElements(board, [...movingElements, ...newElements]);
|
|
1705
|
+
const PlaitOperation = {
|
|
1706
|
+
isSetViewportOperation,
|
|
1707
|
+
inverse
|
|
1685
1708
|
};
|
|
1686
|
-
|
|
1687
|
-
|
|
1709
|
+
|
|
1710
|
+
const Point = {
|
|
1711
|
+
isEquals(point, otherPoint) {
|
|
1712
|
+
return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
|
|
1713
|
+
}
|
|
1688
1714
|
};
|
|
1689
|
-
|
|
1690
|
-
|
|
1715
|
+
|
|
1716
|
+
const Viewport = {
|
|
1717
|
+
isViewport: (value) => {
|
|
1718
|
+
return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
|
|
1719
|
+
},
|
|
1691
1720
|
};
|
|
1692
1721
|
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
if (!targetStyle) {
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
1699
|
-
if (sourceStyle.cssText) {
|
|
1700
|
-
targetStyle.cssText = sourceStyle.cssText;
|
|
1701
|
-
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
1702
|
-
}
|
|
1703
|
-
else {
|
|
1704
|
-
Array.from(sourceStyle).forEach(name => {
|
|
1705
|
-
let value = sourceStyle.getPropertyValue(name);
|
|
1706
|
-
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
1707
|
-
});
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
function createCanvas(width, height, fillStyle) {
|
|
1711
|
-
const canvas = document.createElement('canvas');
|
|
1712
|
-
const ctx = canvas.getContext('2d');
|
|
1713
|
-
canvas.width = width;
|
|
1714
|
-
canvas.height = height;
|
|
1715
|
-
canvas.style.width = `${width}px`;
|
|
1716
|
-
canvas.style.height = `${height}px`;
|
|
1717
|
-
ctx.strokeStyle = '#ffffff';
|
|
1718
|
-
ctx.fillStyle = fillStyle;
|
|
1719
|
-
ctx.fillRect(0, 0, width, height);
|
|
1720
|
-
return {
|
|
1721
|
-
canvas,
|
|
1722
|
-
ctx
|
|
1723
|
-
};
|
|
1724
|
-
}
|
|
1725
|
-
function isElementNode(node) {
|
|
1726
|
-
return node.nodeType === Node.ELEMENT_NODE;
|
|
1727
|
-
}
|
|
1728
|
-
function cloneSvg(board, options) {
|
|
1729
|
-
const elementHostBox = getRectangleByElements(board, board.children, true);
|
|
1730
|
-
const { width, height, x, y } = elementHostBox;
|
|
1731
|
-
const { padding = 4, inlineStyleClassNames } = options;
|
|
1732
|
-
const sourceSvg = PlaitBoard.getHost(board);
|
|
1733
|
-
const cloneSvgElement = sourceSvg.cloneNode(true);
|
|
1734
|
-
cloneSvgElement.style.width = `${width}px`;
|
|
1735
|
-
cloneSvgElement.style.height = `${height}px`;
|
|
1736
|
-
cloneSvgElement.style.backgroundColor = '';
|
|
1737
|
-
cloneSvgElement.setAttribute('width', `${width}`);
|
|
1738
|
-
cloneSvgElement.setAttribute('height', `${height}`);
|
|
1739
|
-
cloneSvgElement.setAttribute('viewBox', [x - padding, y - padding, width + 2 * padding, height + 2 * padding].join(','));
|
|
1740
|
-
if (inlineStyleClassNames) {
|
|
1741
|
-
const sourceNodes = Array.from(sourceSvg.querySelectorAll(inlineStyleClassNames));
|
|
1742
|
-
const cloneNodes = Array.from(cloneSvgElement.querySelectorAll(inlineStyleClassNames));
|
|
1743
|
-
sourceNodes.forEach((node, index) => {
|
|
1744
|
-
const cloneNode = cloneNodes[index];
|
|
1745
|
-
const childElements = Array.from(node.querySelectorAll('*')).filter(isElementNode);
|
|
1746
|
-
const cloneChildElements = Array.from(cloneNode.querySelectorAll('*')).filter(isElementNode);
|
|
1747
|
-
sourceNodes.push(...childElements);
|
|
1748
|
-
cloneNodes.push(...cloneChildElements);
|
|
1749
|
-
});
|
|
1750
|
-
sourceNodes.forEach((node, index) => {
|
|
1751
|
-
const cloneNode = cloneNodes[index];
|
|
1752
|
-
cloneCSSStyle(node, cloneNode);
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
return cloneSvgElement;
|
|
1756
|
-
}
|
|
1757
|
-
function loadImage(src) {
|
|
1758
|
-
return new Promise((resolve, reject) => {
|
|
1759
|
-
const img = new Image();
|
|
1760
|
-
img.onload = () => resolve(img);
|
|
1761
|
-
img.onerror = () => reject(new Error('Failed to load image'));
|
|
1762
|
-
img.src = src;
|
|
1763
|
-
});
|
|
1764
|
-
}
|
|
1765
|
-
async function toImage(board, options) {
|
|
1766
|
-
if (!board) {
|
|
1767
|
-
return undefined;
|
|
1768
|
-
}
|
|
1769
|
-
const elementHostBox = getRectangleByElements(board, board.children, true);
|
|
1770
|
-
const { ratio = 2, fillStyle = 'transparent' } = options;
|
|
1771
|
-
const { width, height } = elementHostBox;
|
|
1772
|
-
const ratioWidth = width * ratio;
|
|
1773
|
-
const ratioHeight = height * ratio;
|
|
1774
|
-
const cloneSvgElement = cloneSvg(board, options);
|
|
1775
|
-
const { canvas, ctx } = createCanvas(ratioWidth, ratioHeight, fillStyle);
|
|
1776
|
-
const svgStr = new XMLSerializer().serializeToString(cloneSvgElement);
|
|
1777
|
-
const imgSrc = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;
|
|
1778
|
-
try {
|
|
1779
|
-
const img = await loadImage(imgSrc);
|
|
1780
|
-
ctx.drawImage(img, 0, 0, ratioWidth, ratioHeight);
|
|
1781
|
-
const url = canvas.toDataURL('image/png');
|
|
1782
|
-
return url;
|
|
1783
|
-
}
|
|
1784
|
-
catch (error) {
|
|
1785
|
-
console.error('Error converting SVG to image:', error);
|
|
1786
|
-
return undefined;
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
function downloadImage(url, name) {
|
|
1790
|
-
const a = document.createElement('a');
|
|
1791
|
-
a.href = url;
|
|
1792
|
-
a.download = name;
|
|
1793
|
-
a.click();
|
|
1794
|
-
a.remove();
|
|
1795
|
-
}
|
|
1722
|
+
const SAVING = new WeakMap();
|
|
1723
|
+
const MERGING = new WeakMap();
|
|
1796
1724
|
|
|
1797
|
-
|
|
1798
|
-
|
|
1725
|
+
var PlaitPluginKey;
|
|
1726
|
+
(function (PlaitPluginKey) {
|
|
1727
|
+
PlaitPluginKey["withSelection"] = "withSelection";
|
|
1728
|
+
})(PlaitPluginKey || (PlaitPluginKey = {}));
|
|
1729
|
+
|
|
1730
|
+
var ThemeColorMode;
|
|
1731
|
+
(function (ThemeColorMode) {
|
|
1732
|
+
ThemeColorMode["default"] = "default";
|
|
1733
|
+
ThemeColorMode["colorful"] = "colorful";
|
|
1734
|
+
ThemeColorMode["soft"] = "soft";
|
|
1735
|
+
ThemeColorMode["retro"] = "retro";
|
|
1736
|
+
ThemeColorMode["dark"] = "dark";
|
|
1737
|
+
ThemeColorMode["starry"] = "starry";
|
|
1738
|
+
})(ThemeColorMode || (ThemeColorMode = {}));
|
|
1739
|
+
const DefaultThemeColor = {
|
|
1740
|
+
mode: ThemeColorMode.default,
|
|
1741
|
+
boardBackground: '#ffffff',
|
|
1742
|
+
textColor: '#333333'
|
|
1799
1743
|
};
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
result.push(...pluginContextResult);
|
|
1805
|
-
}
|
|
1806
|
-
const stringObj = JSON.stringify(result);
|
|
1807
|
-
const encoded = window.btoa(encodeURIComponent(stringObj));
|
|
1808
|
-
data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
|
|
1744
|
+
const ColorfulThemeColor = {
|
|
1745
|
+
mode: ThemeColorMode.colorful,
|
|
1746
|
+
boardBackground: '#ffffff',
|
|
1747
|
+
textColor: '#333333'
|
|
1809
1748
|
};
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
|
|
1749
|
+
const SoftThemeColor = {
|
|
1750
|
+
mode: ThemeColorMode.soft,
|
|
1751
|
+
boardBackground: '#f5f5f5',
|
|
1752
|
+
textColor: '#333333'
|
|
1813
1753
|
};
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1754
|
+
const RetroThemeColor = {
|
|
1755
|
+
mode: ThemeColorMode.retro,
|
|
1756
|
+
boardBackground: '#f9f8ed',
|
|
1757
|
+
textColor: '#333333'
|
|
1818
1758
|
};
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
const decoded = decodeURIComponent(window.atob(encoded));
|
|
1824
|
-
nodesData = JSON.parse(decoded);
|
|
1825
|
-
}
|
|
1826
|
-
return nodesData;
|
|
1759
|
+
const DarkThemeColor = {
|
|
1760
|
+
mode: ThemeColorMode.dark,
|
|
1761
|
+
boardBackground: '#141414',
|
|
1762
|
+
textColor: '#FFFFFF'
|
|
1827
1763
|
};
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1764
|
+
const StarryThemeColor = {
|
|
1765
|
+
mode: ThemeColorMode.starry,
|
|
1766
|
+
boardBackground: '#0d2537',
|
|
1767
|
+
textColor: '#FFFFFF'
|
|
1830
1768
|
};
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1769
|
+
const ThemeColors = [
|
|
1770
|
+
DefaultThemeColor,
|
|
1771
|
+
ColorfulThemeColor,
|
|
1772
|
+
SoftThemeColor,
|
|
1773
|
+
RetroThemeColor,
|
|
1774
|
+
DarkThemeColor,
|
|
1775
|
+
StarryThemeColor
|
|
1776
|
+
];
|
|
1777
|
+
|
|
1778
|
+
function getRectangleByElements(board, elements, recursion) {
|
|
1779
|
+
const boundaryBox = {
|
|
1780
|
+
left: Number.MAX_VALUE,
|
|
1781
|
+
top: Number.MAX_VALUE,
|
|
1782
|
+
right: Number.NEGATIVE_INFINITY,
|
|
1783
|
+
bottom: Number.NEGATIVE_INFINITY
|
|
1784
|
+
};
|
|
1785
|
+
const calcRectangleClient = (node) => {
|
|
1786
|
+
const nodeRectangle = board.getRectangle(node);
|
|
1787
|
+
if (nodeRectangle) {
|
|
1788
|
+
boundaryBox.left = Math.min(boundaryBox.left, nodeRectangle.x);
|
|
1789
|
+
boundaryBox.top = Math.min(boundaryBox.top, nodeRectangle.y);
|
|
1790
|
+
boundaryBox.right = Math.max(boundaryBox.right, nodeRectangle.x + nodeRectangle.width);
|
|
1791
|
+
boundaryBox.bottom = Math.max(boundaryBox.bottom, nodeRectangle.y + nodeRectangle.height);
|
|
1792
|
+
}
|
|
1793
|
+
else {
|
|
1794
|
+
console.error(`can not get rectangle of element:`, node);
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
elements.forEach(element => {
|
|
1798
|
+
if (recursion) {
|
|
1799
|
+
depthFirstRecursion(element, node => calcRectangleClient(node), node => board.isRecursion(node));
|
|
1800
|
+
}
|
|
1801
|
+
else {
|
|
1802
|
+
calcRectangleClient(element);
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
if (boundaryBox.left === Number.MAX_VALUE) {
|
|
1806
|
+
return {
|
|
1807
|
+
x: 0,
|
|
1808
|
+
y: 0,
|
|
1809
|
+
width: 0,
|
|
1810
|
+
height: 0
|
|
1811
|
+
};
|
|
1837
1812
|
}
|
|
1838
|
-
return
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
};
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1813
|
+
return {
|
|
1814
|
+
x: boundaryBox.left,
|
|
1815
|
+
y: boundaryBox.top,
|
|
1816
|
+
width: boundaryBox.right - boundaryBox.left,
|
|
1817
|
+
height: boundaryBox.bottom - boundaryBox.top
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
function getBoardRectangle(board) {
|
|
1821
|
+
return getRectangleByElements(board, board.children, true);
|
|
1822
|
+
}
|
|
1823
|
+
function getElementById(board, id, dataSource) {
|
|
1824
|
+
if (!dataSource) {
|
|
1825
|
+
dataSource = findElements(board, { match: (element) => true, recursion: (element) => true });
|
|
1826
|
+
}
|
|
1827
|
+
let element = dataSource.find((element) => element.id === id);
|
|
1828
|
+
return element;
|
|
1829
|
+
}
|
|
1830
|
+
function findElements(board, options) {
|
|
1831
|
+
let elements = [];
|
|
1832
|
+
depthFirstRecursion(board, node => {
|
|
1833
|
+
if (!PlaitBoard.isBoard(node) && options.match(node)) {
|
|
1834
|
+
elements.push(node);
|
|
1835
|
+
}
|
|
1836
|
+
}, (value) => {
|
|
1837
|
+
if (PlaitBoard.isBoard(value)) {
|
|
1852
1838
|
return true;
|
|
1853
1839
|
}
|
|
1854
1840
|
else {
|
|
1855
|
-
return
|
|
1841
|
+
return getIsRecursionFunc(board)(value) && options.recursion(value);
|
|
1842
|
+
}
|
|
1843
|
+
}, true);
|
|
1844
|
+
return elements;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
const PlaitBoard = {
|
|
1848
|
+
isBoard(value) {
|
|
1849
|
+
const cachedIsBoard = IS_BOARD_CACHE.get(value);
|
|
1850
|
+
if (cachedIsBoard !== undefined) {
|
|
1851
|
+
return cachedIsBoard;
|
|
1856
1852
|
}
|
|
1853
|
+
const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
|
|
1854
|
+
IS_BOARD_CACHE.set(value, isBoard);
|
|
1855
|
+
return isBoard;
|
|
1857
1856
|
},
|
|
1858
|
-
|
|
1859
|
-
|
|
1857
|
+
findPath(board, node) {
|
|
1858
|
+
const path = [];
|
|
1859
|
+
let child = node;
|
|
1860
|
+
while (true) {
|
|
1861
|
+
const parent = NODE_TO_PARENT.get(child);
|
|
1862
|
+
if (parent == null) {
|
|
1863
|
+
if (PlaitBoard.isBoard(child)) {
|
|
1864
|
+
return path;
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
const i = NODE_TO_INDEX.get(child);
|
|
1871
|
+
if (i == null) {
|
|
1872
|
+
break;
|
|
1873
|
+
}
|
|
1874
|
+
path.unshift(i);
|
|
1875
|
+
child = parent;
|
|
1876
|
+
}
|
|
1877
|
+
throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
|
|
1878
|
+
},
|
|
1879
|
+
getHost(board) {
|
|
1880
|
+
return BOARD_TO_HOST.get(board);
|
|
1881
|
+
},
|
|
1882
|
+
getElementHost(board) {
|
|
1883
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.host;
|
|
1884
|
+
},
|
|
1885
|
+
getElementUpperHost(board) {
|
|
1886
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
|
|
1887
|
+
},
|
|
1888
|
+
getElementActiveHost(board) {
|
|
1889
|
+
return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
|
|
1890
|
+
},
|
|
1891
|
+
getRoughSVG(board) {
|
|
1892
|
+
return BOARD_TO_ROUGH_SVG.get(board);
|
|
1893
|
+
},
|
|
1894
|
+
getComponent(board) {
|
|
1895
|
+
return BOARD_TO_COMPONENT.get(board);
|
|
1896
|
+
},
|
|
1897
|
+
getBoardContainer(board) {
|
|
1898
|
+
return PlaitBoard.getComponent(board).nativeElement;
|
|
1899
|
+
},
|
|
1900
|
+
getRectangle(board) {
|
|
1901
|
+
return getRectangleByElements(board, board.children, true);
|
|
1902
|
+
},
|
|
1903
|
+
getViewportContainer(board) {
|
|
1904
|
+
return PlaitBoard.getHost(board).parentElement;
|
|
1905
|
+
},
|
|
1906
|
+
isFocus(board) {
|
|
1907
|
+
return !!board.selection;
|
|
1908
|
+
},
|
|
1909
|
+
isReadonly(board) {
|
|
1910
|
+
return board.options.readonly;
|
|
1911
|
+
},
|
|
1912
|
+
hasBeenTextEditing(board) {
|
|
1913
|
+
return !!IS_TEXT_EDITABLE.get(board);
|
|
1914
|
+
},
|
|
1915
|
+
getPointer(board) {
|
|
1916
|
+
return board.pointer;
|
|
1917
|
+
},
|
|
1918
|
+
isPointer(board, pointer) {
|
|
1919
|
+
return board.pointer === pointer;
|
|
1920
|
+
},
|
|
1921
|
+
isInPointer(board, pointers) {
|
|
1922
|
+
const point = board.pointer;
|
|
1923
|
+
return pointers.includes(point);
|
|
1924
|
+
},
|
|
1925
|
+
getMovingPointInBoard(board) {
|
|
1926
|
+
return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
|
|
1927
|
+
},
|
|
1928
|
+
isMovingPointInBoard(board) {
|
|
1929
|
+
const point = BOARD_TO_MOVING_POINT.get(board);
|
|
1930
|
+
const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1931
|
+
if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
|
|
1932
|
+
return true;
|
|
1933
|
+
}
|
|
1934
|
+
return false;
|
|
1935
|
+
},
|
|
1936
|
+
getThemeColors(board) {
|
|
1937
|
+
return (board.options.themeColors || ThemeColors);
|
|
1860
1938
|
}
|
|
1861
1939
|
};
|
|
1862
1940
|
|
|
1863
|
-
const
|
|
1864
|
-
return value.type === 'set_viewport';
|
|
1865
|
-
};
|
|
1866
|
-
const inverse = (op) => {
|
|
1941
|
+
const applyToDraft = (board, selection, viewport, theme, op) => {
|
|
1867
1942
|
switch (op.type) {
|
|
1868
1943
|
case 'insert_node': {
|
|
1869
|
-
|
|
1944
|
+
const { path, node } = op;
|
|
1945
|
+
const parent = PlaitNode.parent(board, path);
|
|
1946
|
+
const index = path[path.length - 1];
|
|
1947
|
+
if (!parent.children || index > parent.children.length) {
|
|
1948
|
+
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
1949
|
+
}
|
|
1950
|
+
parent.children.splice(index, 0, node);
|
|
1951
|
+
break;
|
|
1870
1952
|
}
|
|
1871
1953
|
case 'remove_node': {
|
|
1872
|
-
|
|
1954
|
+
const { path } = op;
|
|
1955
|
+
const parent = PlaitNode.parent(board, path);
|
|
1956
|
+
const index = path[path.length - 1];
|
|
1957
|
+
if (!parent.children || index > parent.children.length) {
|
|
1958
|
+
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
1959
|
+
}
|
|
1960
|
+
parent.children.splice(index, 1);
|
|
1961
|
+
break;
|
|
1873
1962
|
}
|
|
1874
1963
|
case 'move_node': {
|
|
1875
|
-
const {
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
return op;
|
|
1964
|
+
const { path, newPath } = op;
|
|
1965
|
+
if (Path.isAncestor(path, newPath)) {
|
|
1966
|
+
throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
|
|
1879
1967
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
//
|
|
1884
|
-
//
|
|
1885
|
-
//
|
|
1886
|
-
//
|
|
1887
|
-
//
|
|
1888
|
-
//
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
const
|
|
1892
|
-
const
|
|
1893
|
-
|
|
1968
|
+
const node = PlaitNode.get(board, path);
|
|
1969
|
+
const parent = PlaitNode.parent(board, path);
|
|
1970
|
+
const index = path[path.length - 1];
|
|
1971
|
+
// This is tricky, but since the `path` and `newPath` both refer to
|
|
1972
|
+
// the same snapshot in time, there's a mismatch. After either
|
|
1973
|
+
// removing the original position, the second step's path can be out
|
|
1974
|
+
// of date. So instead of using the `op.newPath` directly, we
|
|
1975
|
+
// transform `op.path` to ascertain what the `newPath` would be after
|
|
1976
|
+
// the operation was applied.
|
|
1977
|
+
parent.children?.splice(index, 1);
|
|
1978
|
+
const truePath = Path.transform(path, op);
|
|
1979
|
+
const newParent = PlaitNode.get(board, Path.parent(truePath));
|
|
1980
|
+
const newIndex = truePath[truePath.length - 1];
|
|
1981
|
+
newParent.children?.splice(newIndex, 0, node);
|
|
1982
|
+
break;
|
|
1894
1983
|
}
|
|
1895
1984
|
case 'set_node': {
|
|
1896
|
-
const { properties, newProperties } = op;
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
case 'set_selection': {
|
|
1900
|
-
const { properties, newProperties } = op;
|
|
1901
|
-
if (properties == null) {
|
|
1902
|
-
return {
|
|
1903
|
-
...op,
|
|
1904
|
-
properties: newProperties,
|
|
1905
|
-
newProperties: null
|
|
1906
|
-
};
|
|
1985
|
+
const { path, properties, newProperties } = op;
|
|
1986
|
+
if (path.length === 0) {
|
|
1987
|
+
throw new Error(`Cannot set properties on the root node!`);
|
|
1907
1988
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
}
|
|
1989
|
+
const node = PlaitNode.get(board, path);
|
|
1990
|
+
for (const key in newProperties) {
|
|
1991
|
+
const value = newProperties[key];
|
|
1992
|
+
if (value == null) {
|
|
1993
|
+
delete node[key];
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
node[key] = value;
|
|
1997
|
+
}
|
|
1914
1998
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1999
|
+
// properties that were previously defined, but are now missing, must be deleted
|
|
2000
|
+
for (const key in properties) {
|
|
2001
|
+
if (!newProperties.hasOwnProperty(key)) {
|
|
2002
|
+
delete node[key];
|
|
2003
|
+
}
|
|
1917
2004
|
}
|
|
2005
|
+
break;
|
|
1918
2006
|
}
|
|
1919
2007
|
case 'set_viewport': {
|
|
1920
|
-
const {
|
|
1921
|
-
if (
|
|
1922
|
-
|
|
1923
|
-
...op,
|
|
1924
|
-
properties: newProperties,
|
|
1925
|
-
newProperties: newProperties
|
|
1926
|
-
};
|
|
2008
|
+
const { newProperties } = op;
|
|
2009
|
+
if (newProperties == null) {
|
|
2010
|
+
viewport = newProperties;
|
|
1927
2011
|
}
|
|
1928
|
-
else
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2012
|
+
else {
|
|
2013
|
+
if (viewport == null) {
|
|
2014
|
+
if (!Viewport.isViewport(newProperties)) {
|
|
2015
|
+
throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
|
|
2016
|
+
}
|
|
2017
|
+
viewport = { ...newProperties };
|
|
2018
|
+
}
|
|
2019
|
+
for (const key in newProperties) {
|
|
2020
|
+
const value = newProperties[key];
|
|
2021
|
+
if (value == null) {
|
|
2022
|
+
delete viewport[key];
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
2025
|
+
viewport[key] = value;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
break;
|
|
2030
|
+
}
|
|
2031
|
+
case 'set_selection': {
|
|
2032
|
+
const { newProperties } = op;
|
|
2033
|
+
if (newProperties == null) {
|
|
2034
|
+
selection = newProperties;
|
|
1934
2035
|
}
|
|
1935
2036
|
else {
|
|
1936
|
-
|
|
2037
|
+
if (selection === null) {
|
|
2038
|
+
selection = op.newProperties;
|
|
2039
|
+
}
|
|
2040
|
+
else {
|
|
2041
|
+
selection.ranges = newProperties.ranges;
|
|
2042
|
+
}
|
|
1937
2043
|
}
|
|
2044
|
+
break;
|
|
1938
2045
|
}
|
|
1939
2046
|
case 'set_theme': {
|
|
1940
|
-
const {
|
|
1941
|
-
|
|
2047
|
+
const { newProperties } = op;
|
|
2048
|
+
theme = newProperties;
|
|
2049
|
+
break;
|
|
1942
2050
|
}
|
|
1943
2051
|
}
|
|
2052
|
+
return { selection, viewport, theme };
|
|
1944
2053
|
};
|
|
1945
|
-
const
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2054
|
+
const GeneralTransforms = {
|
|
2055
|
+
/**
|
|
2056
|
+
* Transform the board by an operation.
|
|
2057
|
+
*/
|
|
2058
|
+
transform(board, op) {
|
|
2059
|
+
board.children = createDraft(board.children);
|
|
2060
|
+
let viewport = board.viewport && createDraft(board.viewport);
|
|
2061
|
+
let selection = board.selection && createDraft(board.selection);
|
|
2062
|
+
let theme = board.theme && createDraft(board.theme);
|
|
2063
|
+
try {
|
|
2064
|
+
const state = applyToDraft(board, selection, viewport, theme, op);
|
|
2065
|
+
viewport = state.viewport;
|
|
2066
|
+
selection = state.selection;
|
|
2067
|
+
theme = state.theme;
|
|
2068
|
+
}
|
|
2069
|
+
finally {
|
|
2070
|
+
board.children = finishDraft(board.children);
|
|
2071
|
+
if (selection) {
|
|
2072
|
+
board.selection = isDraft(selection) ? finishDraft(selection) : selection;
|
|
2073
|
+
}
|
|
2074
|
+
else {
|
|
2075
|
+
board.selection = null;
|
|
2076
|
+
}
|
|
2077
|
+
board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
|
|
2078
|
+
board.theme = isDraft(theme) ? finishDraft(theme) : theme;
|
|
2079
|
+
}
|
|
1953
2080
|
}
|
|
1954
2081
|
};
|
|
1955
2082
|
|
|
1956
|
-
|
|
1957
|
-
const
|
|
1958
|
-
|
|
1959
|
-
var PlaitPluginKey;
|
|
1960
|
-
(function (PlaitPluginKey) {
|
|
1961
|
-
PlaitPluginKey["withSelection"] = "withSelection";
|
|
1962
|
-
})(PlaitPluginKey || (PlaitPluginKey = {}));
|
|
1963
|
-
|
|
1964
|
-
const IS_FROM_SCROLLING = new WeakMap();
|
|
1965
|
-
const IS_FROM_VIEWPORT_CHANGE = new WeakMap();
|
|
1966
|
-
function getViewportContainerRect(board) {
|
|
1967
|
-
const { hideScrollbar } = board.options;
|
|
1968
|
-
const scrollBarWidth = hideScrollbar ? SCROLL_BAR_WIDTH : 0;
|
|
1969
|
-
const viewportRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1970
|
-
return {
|
|
1971
|
-
width: viewportRect.width + scrollBarWidth,
|
|
1972
|
-
height: viewportRect.height + scrollBarWidth
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
function getElementHostBBox(board, zoom) {
|
|
1976
|
-
const childrenRect = getRectangleByElements(board, board.children, true);
|
|
1977
|
-
const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1978
|
-
const containerWidth = viewportContainerRect.width / zoom;
|
|
1979
|
-
const containerHeight = viewportContainerRect.height / zoom;
|
|
1980
|
-
let left;
|
|
1981
|
-
let right;
|
|
1982
|
-
let top;
|
|
1983
|
-
let bottom;
|
|
1984
|
-
if (childrenRect.width < containerWidth) {
|
|
1985
|
-
const centerX = childrenRect.x + childrenRect.width / 2;
|
|
1986
|
-
const halfContainerWidth = containerWidth / 2;
|
|
1987
|
-
left = centerX - halfContainerWidth;
|
|
1988
|
-
right = centerX + halfContainerWidth;
|
|
1989
|
-
}
|
|
1990
|
-
else {
|
|
1991
|
-
left = childrenRect.x;
|
|
1992
|
-
right = childrenRect.x + childrenRect.width;
|
|
1993
|
-
}
|
|
1994
|
-
if (childrenRect.height < containerHeight) {
|
|
1995
|
-
const centerY = childrenRect.y + childrenRect.height / 2;
|
|
1996
|
-
const halfContainerHeight = containerHeight / 2;
|
|
1997
|
-
top = centerY - halfContainerHeight;
|
|
1998
|
-
bottom = centerY + halfContainerHeight;
|
|
1999
|
-
}
|
|
2000
|
-
else {
|
|
2001
|
-
top = childrenRect.y;
|
|
2002
|
-
bottom = childrenRect.y + childrenRect.height;
|
|
2003
|
-
}
|
|
2004
|
-
return {
|
|
2005
|
-
left,
|
|
2006
|
-
right,
|
|
2007
|
-
top,
|
|
2008
|
-
bottom
|
|
2009
|
-
};
|
|
2010
|
-
}
|
|
2011
|
-
/**
|
|
2012
|
-
* 验证缩放比是否符合限制,如果超出限制,则返回合适的缩放比
|
|
2013
|
-
* @param zoom 缩放比
|
|
2014
|
-
* @param minZoom 最小缩放比
|
|
2015
|
-
* @param maxZoom 最大缩放比
|
|
2016
|
-
* @returns 正确的缩放比
|
|
2017
|
-
*/
|
|
2018
|
-
function clampZoomLevel(zoom, minZoom = 0.2, maxZoom = 4) {
|
|
2019
|
-
return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
|
|
2020
|
-
}
|
|
2021
|
-
function getViewBox(board, zoom) {
|
|
2022
|
-
const boardContainerRectangle = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
2023
|
-
const elementHostBBox = getElementHostBBox(board, zoom);
|
|
2024
|
-
const horizontalPadding = boardContainerRectangle.width / 2;
|
|
2025
|
-
const verticalPadding = boardContainerRectangle.height / 2;
|
|
2026
|
-
const viewBox = [
|
|
2027
|
-
elementHostBBox.left - horizontalPadding / zoom,
|
|
2028
|
-
elementHostBBox.top - verticalPadding / zoom,
|
|
2029
|
-
elementHostBBox.right - elementHostBBox.left + (horizontalPadding * 2) / zoom,
|
|
2030
|
-
elementHostBBox.bottom - elementHostBBox.top + (verticalPadding * 2) / zoom
|
|
2031
|
-
];
|
|
2032
|
-
return viewBox;
|
|
2033
|
-
}
|
|
2034
|
-
function getViewBoxCenterPoint(board) {
|
|
2035
|
-
const childrenRectangle = getRectangleByElements(board, board.children, true);
|
|
2036
|
-
return [childrenRectangle.x + childrenRectangle.width / 2, childrenRectangle.y + childrenRectangle.height / 2];
|
|
2083
|
+
function insertNode(board, node, path) {
|
|
2084
|
+
const operation = { type: 'insert_node', node, path };
|
|
2085
|
+
board.apply(operation);
|
|
2037
2086
|
}
|
|
2038
|
-
function
|
|
2039
|
-
const
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2087
|
+
function setNode(board, props, path) {
|
|
2088
|
+
const properties = {};
|
|
2089
|
+
const newProperties = {};
|
|
2090
|
+
const node = PlaitNode.get(board, path);
|
|
2091
|
+
for (const k in props) {
|
|
2092
|
+
if (node[k] !== props[k]) {
|
|
2093
|
+
if (node.hasOwnProperty(k)) {
|
|
2094
|
+
properties[k] = node[k];
|
|
2095
|
+
}
|
|
2096
|
+
if (props[k] != null)
|
|
2097
|
+
newProperties[k] = props[k];
|
|
2098
|
+
}
|
|
2046
2099
|
}
|
|
2100
|
+
const operation = { type: 'set_node', properties, newProperties, path };
|
|
2101
|
+
board.apply(operation);
|
|
2047
2102
|
}
|
|
2048
|
-
function
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
const { zoom } = board.viewport;
|
|
2053
|
-
const viewBox = getViewBox(board, zoom);
|
|
2054
|
-
const scrollLeft = (origination[0] - viewBox[0]) * zoom;
|
|
2055
|
-
const scrollTop = (origination[1] - viewBox[1]) * zoom;
|
|
2056
|
-
updateViewportContainerScroll(board, scrollLeft, scrollTop);
|
|
2103
|
+
function removeNode(board, path) {
|
|
2104
|
+
const node = PlaitNode.get(board, path);
|
|
2105
|
+
const operation = { type: 'remove_node', path, node };
|
|
2106
|
+
board.apply(operation);
|
|
2057
2107
|
}
|
|
2058
|
-
function
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
viewportContainer.scrollLeft = left;
|
|
2062
|
-
viewportContainer.scrollTop = top;
|
|
2063
|
-
isFromViewportChange && setIsFromViewportChange(board, true);
|
|
2064
|
-
}
|
|
2108
|
+
function moveNode(board, path, newPath) {
|
|
2109
|
+
const operation = { type: 'move_node', path, newPath };
|
|
2110
|
+
board.apply(operation);
|
|
2065
2111
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2112
|
+
const NodeTransforms = {
|
|
2113
|
+
insertNode,
|
|
2114
|
+
setNode,
|
|
2115
|
+
removeNode,
|
|
2116
|
+
moveNode
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
function setSelection(board, selection) {
|
|
2120
|
+
const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
|
|
2121
|
+
board.apply(operation);
|
|
2071
2122
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2123
|
+
const SelectionTransforms = {
|
|
2124
|
+
setSelection,
|
|
2125
|
+
setSelectionWithTemporaryElements
|
|
2126
|
+
};
|
|
2127
|
+
function setSelectionWithTemporaryElements(board, elements) {
|
|
2128
|
+
setTimeout(() => {
|
|
2129
|
+
BOARD_TO_TEMPORARY_ELEMENTS.set(board, elements);
|
|
2130
|
+
setSelection(board, { ranges: [] });
|
|
2131
|
+
});
|
|
2076
2132
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
const viewBox = getViewBox(board, zoom);
|
|
2082
|
-
const centerX = viewBox[0] + viewBox[2] / 2;
|
|
2083
|
-
const centerY = viewBox[1] + viewBox[3] / 2;
|
|
2084
|
-
const origination = [centerX - viewportContainerRect.width / 2 / zoom, centerY - viewportContainerRect.height / 2 / zoom];
|
|
2085
|
-
updateViewportOrigination(board, origination);
|
|
2086
|
-
updateViewportOffset(board);
|
|
2087
|
-
return;
|
|
2088
|
-
}
|
|
2089
|
-
updateViewportOffset(board);
|
|
2133
|
+
|
|
2134
|
+
function setViewport(board, viewport) {
|
|
2135
|
+
const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
|
|
2136
|
+
board.apply(operation);
|
|
2090
2137
|
}
|
|
2091
|
-
const
|
|
2092
|
-
|
|
2093
|
-
};
|
|
2094
|
-
const clearViewportOrigination = (board) => {
|
|
2095
|
-
BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
|
|
2096
|
-
};
|
|
2097
|
-
const getViewportOrigination = (board) => {
|
|
2098
|
-
const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
|
|
2099
|
-
if (origination) {
|
|
2100
|
-
return origination;
|
|
2101
|
-
}
|
|
2102
|
-
else {
|
|
2103
|
-
return board.viewport.origination;
|
|
2104
|
-
}
|
|
2105
|
-
};
|
|
2106
|
-
const isFromScrolling = (board) => {
|
|
2107
|
-
return !!IS_FROM_SCROLLING.get(board);
|
|
2108
|
-
};
|
|
2109
|
-
const setIsFromScrolling = (board, state) => {
|
|
2110
|
-
IS_FROM_SCROLLING.set(board, state);
|
|
2111
|
-
};
|
|
2112
|
-
const isFromViewportChange = (board) => {
|
|
2113
|
-
return !!IS_FROM_VIEWPORT_CHANGE.get(board);
|
|
2114
|
-
};
|
|
2115
|
-
const setIsFromViewportChange = (board, state) => {
|
|
2116
|
-
IS_FROM_VIEWPORT_CHANGE.set(board, state);
|
|
2138
|
+
const ViewportTransforms$1 = {
|
|
2139
|
+
setViewport
|
|
2117
2140
|
};
|
|
2118
|
-
function scrollToRectangle(board, client) { }
|
|
2119
2141
|
|
|
2120
2142
|
function setTheme(board, themeColorMode) {
|
|
2121
2143
|
const operation = { type: 'set_theme', properties: board.theme, newProperties: themeColorMode };
|
|
@@ -3298,6 +3320,7 @@ class PlaitBoardComponent {
|
|
|
3298
3320
|
.subscribe((event) => {
|
|
3299
3321
|
const selectedElements = getSelectedElements(this.board);
|
|
3300
3322
|
if (selectedElements.length > 0 && (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
|
|
3323
|
+
event.preventDefault();
|
|
3301
3324
|
this.board.deleteFragment(null);
|
|
3302
3325
|
}
|
|
3303
3326
|
this.board.keydown(event);
|
|
@@ -3653,5 +3676,5 @@ function createModModifierKeys() {
|
|
|
3653
3676
|
* Generated bundle index. Do not edit.
|
|
3654
3677
|
*/
|
|
3655
3678
|
|
|
3656
|
-
export { A, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, 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_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLIP_BOARD_FORMAT_KEY, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DefaultThemeColor, 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, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_PREVENT_TOUCH_MOVE, 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_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, PlaitChildrenElement, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitModule, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, X, Y, Z, ZERO, addMovingElements, addSelectedElement, arrowPoints, cacheMovingElements, cacheSelectedElements, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createFakeEvent, createForeignObject, createG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionOuterG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, getBoardRectangle, getClipboardByKey, getClipboardDataByMedia, getDataFromClipboard, getElementById, getElementHostBBox, getHitElementOfRoot, getHitElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getRealScrollBarWidth, getRectangleByElements, getSelectedElements, getTemporaryElements, getTextFromClipboard, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isDOMElement, isDOMNode, isFromScrolling, isFromViewportChange, isHitElements, isInPlaitBoard, isLineHitLine, isMainPointer, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedElement, isSelectionMoving, isSetViewportOperation, normalizePoint, preventTouchMove, removeMovingElements, removeSelectedElement, rotate, scrollToRectangle, setClipboardData, setClipboardDataByMedia, setClipboardDataByText, setIsFromScrolling, setIsFromViewportChange, setSVGViewBox, setSelectionMoving, shouldClear, shouldMerge, shouldSave, throttleRAF, toImage, toPoint, transformPoint, transformPoints, updateForeignObject, updateForeignObjectWidth, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withMoving, withOptions, withSelection };
|
|
3679
|
+
export { A, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, 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_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLIP_BOARD_FORMAT_KEY, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DefaultThemeColor, 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, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_PREVENT_TOUCH_MOVE, 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_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, PlaitChildrenElement, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitModule, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, X, Y, Z, ZERO, addMovingElements, addSelectedElement, arrowPoints, cacheMovingElements, cacheSelectedElements, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createFakeEvent, createForeignObject, createG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionOuterG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, findElements, getBoardRectangle, getClipboardByKey, getClipboardDataByMedia, getDataFromClipboard, getElementById, getElementHostBBox, getHitElementOfRoot, getHitElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getRealScrollBarWidth, getRectangleByElements, getSelectedElements, getTemporaryElements, getTextFromClipboard, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isDOMElement, isDOMNode, isFromScrolling, isFromViewportChange, isHitElements, isInPlaitBoard, isLineHitLine, isMainPointer, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedElement, isSelectionMoving, isSetViewportOperation, normalizePoint, preventTouchMove, removeMovingElements, removeSelectedElement, rotate, scrollToRectangle, setClipboardData, setClipboardDataByMedia, setClipboardDataByText, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, throttleRAF, toImage, toPoint, transformPoint, transformPoints, updateForeignObject, updateForeignObjectWidth, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withMoving, withOptions, withSelection };
|
|
3657
3680
|
//# sourceMappingURL=plait-core.mjs.map
|