@plait/core 0.24.0-next.5 → 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/element.mjs +25 -7
- package/fesm2022/plait-core.mjs +1835 -1817
- package/fesm2022/plait-core.mjs.map +1 -1
- package/package.json +1 -1
- 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,210 +64,487 @@ 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
|
-
|
|
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];
|
|
291
|
+
}
|
|
292
|
+
function createG() {
|
|
293
|
+
const newG = document.createElementNS(NS, 'g');
|
|
294
|
+
return newG;
|
|
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]}`);
|
|
309
|
+
}
|
|
310
|
+
return rect;
|
|
311
|
+
}
|
|
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');
|
|
322
|
+
}
|
|
323
|
+
function createSVG() {
|
|
324
|
+
const svg = document.createElementNS(NS, 'svg');
|
|
325
|
+
return svg;
|
|
326
|
+
}
|
|
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;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Check if a DOM node is an element node.
|
|
337
|
+
*/
|
|
338
|
+
const isDOMElement = (value) => {
|
|
339
|
+
return isDOMNode(value) && value.nodeType === 1;
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Check if a value is a DOM node.
|
|
343
|
+
*/
|
|
344
|
+
const isDOMNode = (value) => {
|
|
345
|
+
return value instanceof window.Node;
|
|
346
|
+
};
|
|
347
|
+
const hasInputOrTextareaTarget = (target) => {
|
|
348
|
+
if (isDOMElement(target)) {
|
|
349
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
};
|
|
355
|
+
const isSecondaryPointer = (event) => {
|
|
356
|
+
return event.button === POINTER_BUTTON.SECONDARY;
|
|
357
|
+
};
|
|
358
|
+
const isMainPointer = (event) => {
|
|
359
|
+
return event.button === POINTER_BUTTON.MAIN;
|
|
360
|
+
};
|
|
361
|
+
|
|
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);
|
|
379
|
+
}
|
|
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);
|
|
389
|
+
}
|
|
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();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
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');
|
|
424
|
+
}
|
|
425
|
+
this.initialized = true;
|
|
426
|
+
}
|
|
427
|
+
ngOnDestroy() {
|
|
428
|
+
if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
|
|
429
|
+
ELEMENT_TO_COMPONENT.delete(this.element);
|
|
430
|
+
}
|
|
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 }); }
|
|
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();
|
|
443
|
+
|
|
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
|
+
}
|
|
457
|
+
},
|
|
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;
|
|
467
|
+
},
|
|
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
|
+
};
|
|
475
|
+
},
|
|
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);
|
|
481
|
+
},
|
|
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
|
+
];
|
|
489
|
+
},
|
|
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
|
+
}
|
|
261
548
|
else if (param > 1) {
|
|
262
549
|
xx = x2;
|
|
263
550
|
yy = y2;
|
|
@@ -317,173 +604,142 @@ const isLineHitLine = (a, b, c, d) => {
|
|
|
317
604
|
const crossProduct = (v1, v2) => v1[0] * v2[1] - v1[1] * v2[0];
|
|
318
605
|
const ab = [b[0] - a[0], b[1] - a[1]];
|
|
319
606
|
const ac = [c[0] - a[0], c[1] - a[1]];
|
|
320
|
-
const ad = [d[0] - a[0], d[1] - a[1]];
|
|
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;
|
|
325
|
-
};
|
|
326
|
-
const isPolylineHitRectangle = (points, rectangle) => {
|
|
327
|
-
const rectanglePoints = RectangleClient.getCornerPoints(rectangle);
|
|
328
|
-
for (let i = 1; i < points.length; i++) {
|
|
329
|
-
const isIntersect = isLineHitLine(points[i], points[i - 1], rectanglePoints[0], rectanglePoints[1]) ||
|
|
330
|
-
isLineHitLine(points[i], points[i - 1], rectanglePoints[1], rectanglePoints[2]) ||
|
|
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;
|
|
338
|
-
};
|
|
339
|
-
//https://stackoverflow.com/questions/22521982/check-if-point-is-inside-a-polygon
|
|
340
|
-
const isPointInPolygon = (point, points) => {
|
|
341
|
-
// ray-casting algorithm based on
|
|
342
|
-
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
343
|
-
const x = point[0], y = point[1];
|
|
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;
|
|
351
|
-
}
|
|
352
|
-
return inside;
|
|
353
|
-
};
|
|
354
|
-
const isPointInEllipse = (point, center, rx, ry, rotation = 0) => {
|
|
355
|
-
const cosAngle = Math.cos(rotation);
|
|
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;
|
|
360
|
-
};
|
|
361
|
-
const isPointInRoundRectangle = (point, rectangle, radius) => {
|
|
362
|
-
const { x: rectX, y: rectY, width, height } = rectangle;
|
|
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;
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
const PlaitBoard = {
|
|
389
|
-
isBoard(value) {
|
|
390
|
-
const cachedIsBoard = IS_BOARD_CACHE.get(value);
|
|
391
|
-
if (cachedIsBoard !== undefined) {
|
|
392
|
-
return cachedIsBoard;
|
|
393
|
-
}
|
|
394
|
-
const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
|
|
395
|
-
IS_BOARD_CACHE.set(value, isBoard);
|
|
396
|
-
return isBoard;
|
|
397
|
-
},
|
|
398
|
-
findPath(board, node) {
|
|
399
|
-
const path = [];
|
|
400
|
-
let child = node;
|
|
401
|
-
while (true) {
|
|
402
|
-
const parent = NODE_TO_PARENT.get(child);
|
|
403
|
-
if (parent == null) {
|
|
404
|
-
if (PlaitBoard.isBoard(child)) {
|
|
405
|
-
return path;
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
const i = NODE_TO_INDEX.get(child);
|
|
412
|
-
if (i == null) {
|
|
413
|
-
break;
|
|
414
|
-
}
|
|
415
|
-
path.unshift(i);
|
|
416
|
-
child = parent;
|
|
417
|
-
}
|
|
418
|
-
throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
|
|
419
|
-
},
|
|
420
|
-
getHost(board) {
|
|
421
|
-
return BOARD_TO_HOST.get(board);
|
|
422
|
-
},
|
|
423
|
-
getElementHost(board) {
|
|
424
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.host;
|
|
425
|
-
},
|
|
426
|
-
getElementUpperHost(board) {
|
|
427
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
|
|
428
|
-
},
|
|
429
|
-
getElementActiveHost(board) {
|
|
430
|
-
return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
|
|
431
|
-
},
|
|
432
|
-
getRoughSVG(board) {
|
|
433
|
-
return BOARD_TO_ROUGH_SVG.get(board);
|
|
434
|
-
},
|
|
435
|
-
getComponent(board) {
|
|
436
|
-
return BOARD_TO_COMPONENT.get(board);
|
|
437
|
-
},
|
|
438
|
-
getBoardContainer(board) {
|
|
439
|
-
return PlaitBoard.getComponent(board).nativeElement;
|
|
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) {
|
|
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) {
|
|
473
621
|
return true;
|
|
474
622
|
}
|
|
475
|
-
return false;
|
|
476
|
-
},
|
|
477
|
-
getThemeColors(board) {
|
|
478
|
-
return (board.options.themeColors || ThemeColors);
|
|
479
623
|
}
|
|
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;
|
|
480
673
|
};
|
|
481
674
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
}
|
|
704
|
+
|
|
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}`);
|
|
720
|
+
}
|
|
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
|
+
}
|
|
728
|
+
|
|
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);
|
|
487
743
|
|
|
488
744
|
function isNullOrUndefined(value) {
|
|
489
745
|
return value === null || value === undefined;
|
|
@@ -502,1625 +758,1386 @@ function normalizePoint(point) {
|
|
|
502
758
|
: point;
|
|
503
759
|
}
|
|
504
760
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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;
|
|
767
|
+
}
|
|
768
|
+
return false;
|
|
509
769
|
};
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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;
|
|
546
|
-
},
|
|
547
|
-
parent(path) {
|
|
548
|
-
if (path.length === 0) {
|
|
549
|
-
throw new Error(`Cannot get the parent path of the root path [${path}].`);
|
|
550
|
-
}
|
|
551
|
-
return path.slice(0, -1);
|
|
552
|
-
},
|
|
553
|
-
next(path) {
|
|
554
|
-
if (path.length === 0) {
|
|
555
|
-
throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
|
|
556
|
-
}
|
|
557
|
-
const last = path[path.length - 1];
|
|
558
|
-
return path.slice(0, -1).concat(last + 1);
|
|
559
|
-
},
|
|
560
|
-
hasPrevious(path) {
|
|
561
|
-
return path[path.length - 1] > 0;
|
|
562
|
-
},
|
|
563
|
-
previous(path) {
|
|
564
|
-
if (path.length === 0) {
|
|
565
|
-
throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
|
|
566
|
-
}
|
|
567
|
-
const last = path[path.length - 1];
|
|
568
|
-
return path.slice(0, -1).concat(last - 1);
|
|
569
|
-
},
|
|
570
|
-
/**
|
|
571
|
-
* Check if a path is an ancestor of another.
|
|
572
|
-
*/
|
|
573
|
-
isAncestor(path, another) {
|
|
574
|
-
return path.length < another.length && Path.compare(path, another) === 0;
|
|
575
|
-
},
|
|
576
|
-
/**
|
|
577
|
-
* Compare a path to another, returning an integer indicating whether the path
|
|
578
|
-
* was before, at, or after the other.
|
|
579
|
-
*
|
|
580
|
-
* Note: Two paths of unequal length can still receive a `0` result if one is
|
|
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;
|
|
591
|
-
}
|
|
592
|
-
return 0;
|
|
593
|
-
},
|
|
594
|
-
/**
|
|
595
|
-
* Check if a path is exactly equal to another.
|
|
596
|
-
*/
|
|
597
|
-
equals(path, another) {
|
|
598
|
-
return path.length === another.length && path.every((n, i) => n === another[i]);
|
|
599
|
-
},
|
|
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 = {
|
|
600
789
|
/**
|
|
601
|
-
*
|
|
790
|
+
* Get the saving flag's current value.
|
|
602
791
|
*/
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const as = path.slice(0, i);
|
|
606
|
-
const bs = another.slice(0, i);
|
|
607
|
-
const av = path[i];
|
|
608
|
-
const bv = another[i];
|
|
609
|
-
return Path.equals(as, bs) && av < bv;
|
|
792
|
+
isSaving(board) {
|
|
793
|
+
return SAVING.get(board);
|
|
610
794
|
},
|
|
611
795
|
/**
|
|
612
|
-
*
|
|
796
|
+
* Get the merge flag's current value.
|
|
613
797
|
*/
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
return false;
|
|
617
|
-
}
|
|
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
|
-
}
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
const PlaitNode = {
|
|
695
|
-
parent: (board, path) => {
|
|
696
|
-
const parentPath = Path.parent(path);
|
|
697
|
-
const p = PlaitNode.get(board, parentPath);
|
|
698
|
-
return p;
|
|
798
|
+
isMerging(board) {
|
|
799
|
+
return MERGING.get(board);
|
|
699
800
|
},
|
|
700
801
|
/**
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
* By default the order is top-down, from highest to lowest ancestor in
|
|
704
|
-
* the tree, but you can pass the `reverse: true` option to go bottom-up.
|
|
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.
|
|
705
804
|
*/
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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;
|
|
805
|
+
withoutMerging(board, fn) {
|
|
806
|
+
const prev = PlaitHistoryBoard.isMerging(board);
|
|
807
|
+
MERGING.set(board, false);
|
|
808
|
+
fn();
|
|
809
|
+
MERGING.set(board, prev);
|
|
722
810
|
},
|
|
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;
|
|
730
|
-
}
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
const applyToDraft = (board, selection, viewport, theme, op) => {
|
|
734
|
-
switch (op.type) {
|
|
735
|
-
case 'insert_node': {
|
|
736
|
-
const { path, node } = op;
|
|
737
|
-
const parent = PlaitNode.parent(board, path);
|
|
738
|
-
const index = path[path.length - 1];
|
|
739
|
-
if (!parent.children || index > parent.children.length) {
|
|
740
|
-
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
741
|
-
}
|
|
742
|
-
parent.children.splice(index, 0, node);
|
|
743
|
-
break;
|
|
744
|
-
}
|
|
745
|
-
case 'remove_node': {
|
|
746
|
-
const { path } = op;
|
|
747
|
-
const parent = PlaitNode.parent(board, path);
|
|
748
|
-
const index = path[path.length - 1];
|
|
749
|
-
if (!parent.children || index > parent.children.length) {
|
|
750
|
-
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
751
|
-
}
|
|
752
|
-
parent.children.splice(index, 1);
|
|
753
|
-
break;
|
|
754
|
-
}
|
|
755
|
-
case 'move_node': {
|
|
756
|
-
const { path, newPath } = op;
|
|
757
|
-
if (Path.isAncestor(path, newPath)) {
|
|
758
|
-
throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
|
|
759
|
-
}
|
|
760
|
-
const node = PlaitNode.get(board, path);
|
|
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;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
return { selection, viewport, theme };
|
|
845
|
-
};
|
|
846
|
-
const GeneralTransforms = {
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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);
|
|
1121
|
+
}
|
|
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
|
+
}
|
|
1115
1129
|
}
|
|
1116
|
-
function
|
|
1117
|
-
const
|
|
1118
|
-
|
|
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
|
-
function
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
text.setAttribute('fill', fill);
|
|
1125
|
-
text.textContent = textContent;
|
|
1126
|
-
return text;
|
|
1136
|
+
function initializeViewBox(board) {
|
|
1137
|
+
const zoom = board.viewport.zoom;
|
|
1138
|
+
const viewBox = getViewBox(board, zoom);
|
|
1139
|
+
setSVGViewBox(board, viewBox);
|
|
1127
1140
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
};
|
|
1140
|
-
const hasInputOrTextareaTarget = (target) => {
|
|
1141
|
-
if (isDOMElement(target)) {
|
|
1142
|
-
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
1143
|
-
return true;
|
|
1144
|
-
}
|
|
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;
|
|
1145
1152
|
}
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1153
|
+
updateViewportOffset(board);
|
|
1154
|
+
}
|
|
1155
|
+
const updateViewportOrigination = (board, origination) => {
|
|
1156
|
+
BOARD_TO_VIEWPORT_ORIGINATION.set(board, origination);
|
|
1150
1157
|
};
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1158
|
+
const clearViewportOrigination = (board) => {
|
|
1159
|
+
BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
|
|
1153
1160
|
};
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
const SELECTION_BORDER_COLOR = '#6698FF';
|
|
1160
|
-
const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
|
|
1161
|
-
const Selection = {
|
|
1162
|
-
isCollapsed(selection) {
|
|
1163
|
-
if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
|
|
1164
|
-
return true;
|
|
1165
|
-
}
|
|
1166
|
-
else {
|
|
1167
|
-
return false;
|
|
1168
|
-
}
|
|
1161
|
+
const getViewportOrigination = (board) => {
|
|
1162
|
+
const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
|
|
1163
|
+
if (origination) {
|
|
1164
|
+
return origination;
|
|
1169
1165
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
const getHitElements = (board, selection, match = () => true) => {
|
|
1173
|
-
const realSelection = selection || board.selection;
|
|
1174
|
-
const selectedElements = [];
|
|
1175
|
-
const isCollapsed = realSelection && realSelection.ranges.length === 1 && Selection.isCollapsed(realSelection.ranges[0]);
|
|
1176
|
-
depthFirstRecursion(board, node => {
|
|
1177
|
-
if (selectedElements.length > 0 && isCollapsed) {
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
if (!PlaitBoard.isBoard(node) &&
|
|
1181
|
-
match(node) &&
|
|
1182
|
-
realSelection &&
|
|
1183
|
-
realSelection.ranges.some(range => {
|
|
1184
|
-
return board.isHitSelection(node, range);
|
|
1185
|
-
})) {
|
|
1186
|
-
selectedElements.push(node);
|
|
1187
|
-
}
|
|
1188
|
-
}, getIsRecursionFunc(board), true);
|
|
1189
|
-
return selectedElements;
|
|
1190
|
-
};
|
|
1191
|
-
const getHitElementOfRoot = (board, rootElements, range) => {
|
|
1192
|
-
const newRootElements = [...rootElements].reverse();
|
|
1193
|
-
return newRootElements.find(item => {
|
|
1194
|
-
return board.isHitSelection(item, range);
|
|
1195
|
-
});
|
|
1196
|
-
};
|
|
1197
|
-
const isHitElements = (board, elements, ranges) => {
|
|
1198
|
-
let isIntersectionElements = false;
|
|
1199
|
-
if (elements.length) {
|
|
1200
|
-
elements.map(item => {
|
|
1201
|
-
if (!isIntersectionElements) {
|
|
1202
|
-
isIntersectionElements = ranges.some(range => {
|
|
1203
|
-
return board.isHitSelection(item, range);
|
|
1204
|
-
});
|
|
1205
|
-
}
|
|
1206
|
-
});
|
|
1166
|
+
else {
|
|
1167
|
+
return board.viewport.origination;
|
|
1207
1168
|
}
|
|
1208
|
-
return isIntersectionElements;
|
|
1209
|
-
};
|
|
1210
|
-
const cacheSelectedElements = (board, selectedElements) => {
|
|
1211
|
-
BOARD_TO_SELECTED_ELEMENT.set(board, selectedElements);
|
|
1212
|
-
};
|
|
1213
|
-
const getSelectedElements = (board) => {
|
|
1214
|
-
return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
|
|
1215
1169
|
};
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
cacheSelectedElements(board, [...selectedElements, element]);
|
|
1170
|
+
const isFromScrolling = (board) => {
|
|
1171
|
+
return !!IS_FROM_SCROLLING.get(board);
|
|
1219
1172
|
};
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
const newSelectedElements = selectedElements.filter(value => value !== element);
|
|
1223
|
-
cacheSelectedElements(board, newSelectedElements);
|
|
1173
|
+
const setIsFromScrolling = (board, state) => {
|
|
1174
|
+
IS_FROM_SCROLLING.set(board, state);
|
|
1224
1175
|
};
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1176
|
+
const isFromViewportChange = (board) => {
|
|
1177
|
+
return !!IS_FROM_VIEWPORT_CHANGE.get(board);
|
|
1227
1178
|
};
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
return !!selectedElements.find(value => value === element);
|
|
1179
|
+
const setIsFromViewportChange = (board, state) => {
|
|
1180
|
+
IS_FROM_VIEWPORT_CHANGE.set(board, state);
|
|
1231
1181
|
};
|
|
1182
|
+
function scrollToRectangle(board, client) { }
|
|
1232
1183
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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;
|
|
1242
1195
|
}
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
if (
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
if (this.element) {
|
|
1254
|
-
ELEMENT_TO_COMPONENT.set(this.element, this);
|
|
1255
|
-
}
|
|
1256
|
-
if (this.initialized) {
|
|
1257
|
-
this.cdr.markForCheck();
|
|
1258
|
-
if (hasOnContextChanged(this)) {
|
|
1259
|
-
this.onContextChanged(value, previousContext);
|
|
1260
|
-
}
|
|
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
|
+
});
|
|
1261
1206
|
}
|
|
1262
1207
|
else {
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1265
|
-
this.rootG = createG();
|
|
1266
|
-
this.rootG.append(this.g);
|
|
1267
|
-
}
|
|
1268
|
-
else {
|
|
1269
|
-
this.g = createG();
|
|
1208
|
+
if (options?.leading) {
|
|
1209
|
+
func();
|
|
1270
1210
|
}
|
|
1211
|
+
timerSubscription = timer(wait).subscribe();
|
|
1271
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;
|
|
1272
1235
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
return this.context && this.context.element;
|
|
1278
|
-
}
|
|
1279
|
-
get board() {
|
|
1280
|
-
return this.context && this.context.board;
|
|
1281
|
-
}
|
|
1282
|
-
get selected() {
|
|
1283
|
-
return this.context && this.context.selected;
|
|
1284
|
-
}
|
|
1285
|
-
get effect() {
|
|
1286
|
-
return this.context && this.context.effect;
|
|
1287
|
-
}
|
|
1288
|
-
constructor(cdr) {
|
|
1289
|
-
this.cdr = cdr;
|
|
1290
|
-
this.initialized = false;
|
|
1291
|
-
}
|
|
1292
|
-
ngOnInit() {
|
|
1293
|
-
if (this.element.type) {
|
|
1294
|
-
(this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
|
|
1295
|
-
}
|
|
1296
|
-
this.initialized = true;
|
|
1236
|
+
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
1237
|
+
if (sourceStyle.cssText) {
|
|
1238
|
+
targetStyle.cssText = sourceStyle.cssText;
|
|
1239
|
+
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
1297
1240
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
(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
|
+
});
|
|
1304
1246
|
}
|
|
1305
|
-
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 }); }
|
|
1306
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
|
|
1307
|
-
}
|
|
1308
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
|
|
1309
|
-
type: Directive
|
|
1310
|
-
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { context: [{
|
|
1311
|
-
type: Input
|
|
1312
|
-
}] } });
|
|
1313
|
-
const ELEMENT_TO_COMPONENT = new WeakMap();
|
|
1314
|
-
|
|
1315
|
-
function transformPoints(board, points) {
|
|
1316
|
-
const newPoints = points.map(point => {
|
|
1317
|
-
return transformPoint(board, point);
|
|
1318
|
-
});
|
|
1319
|
-
return newPoints;
|
|
1320
1247
|
}
|
|
1321
|
-
function
|
|
1322
|
-
const
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
+
};
|
|
1328
1262
|
}
|
|
1329
|
-
function
|
|
1330
|
-
|
|
1331
|
-
const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
|
|
1332
|
-
const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
|
|
1333
|
-
return distances === 0;
|
|
1263
|
+
function isElementNode(node) {
|
|
1264
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
1334
1265
|
}
|
|
1335
|
-
function
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
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
|
+
});
|
|
1341
1292
|
}
|
|
1342
|
-
return
|
|
1293
|
+
return cloneSvgElement;
|
|
1343
1294
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
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
|
+
});
|
|
1352
1302
|
}
|
|
1353
|
-
function
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
foreignObject.setAttribute('width', `${width}`);
|
|
1357
|
-
foreignObject.setAttribute('height', `${height}`);
|
|
1358
|
-
foreignObject.setAttribute('x', `${x}`);
|
|
1359
|
-
foreignObject.setAttribute('y', `${y}`);
|
|
1303
|
+
async function toImage(board, options) {
|
|
1304
|
+
if (!board) {
|
|
1305
|
+
return undefined;
|
|
1360
1306
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
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;
|
|
1366
1325
|
}
|
|
1367
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
|
+
}
|
|
1368
1334
|
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
|
|
1378
|
-
// "modern" Edge was released at 79.x
|
|
1379
|
-
const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
|
|
1380
|
-
const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
|
|
1381
|
-
// Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
|
|
1382
|
-
const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
|
|
1383
|
-
|
|
1384
|
-
/**
|
|
1385
|
-
* Check whether to merge an operation into the previous operation.
|
|
1386
|
-
*/
|
|
1387
|
-
const shouldMerge = (op, prev) => {
|
|
1388
|
-
if (op.type === 'set_viewport' && op.type === prev?.type) {
|
|
1389
|
-
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);
|
|
1390
1343
|
}
|
|
1391
|
-
|
|
1344
|
+
const stringObj = JSON.stringify(result);
|
|
1345
|
+
const encoded = window.btoa(encodeURIComponent(stringObj));
|
|
1346
|
+
data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
|
|
1392
1347
|
};
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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);
|
|
1399
1363
|
}
|
|
1400
|
-
return
|
|
1364
|
+
return nodesData;
|
|
1401
1365
|
};
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
const
|
|
1406
|
-
|
|
1407
|
-
|
|
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);
|
|
1408
1398
|
}
|
|
1409
|
-
return true;
|
|
1410
1399
|
};
|
|
1411
|
-
|
|
1400
|
+
|
|
1401
|
+
const Path = {
|
|
1412
1402
|
/**
|
|
1413
|
-
* 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.
|
|
1414
1407
|
*/
|
|
1415
|
-
|
|
1416
|
-
|
|
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;
|
|
1417
1418
|
},
|
|
1418
1419
|
/**
|
|
1419
|
-
* 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.
|
|
1420
1425
|
*/
|
|
1421
|
-
|
|
1422
|
-
|
|
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);
|
|
1423
1459
|
},
|
|
1424
1460
|
/**
|
|
1425
|
-
*
|
|
1426
|
-
* the new operations into previous save point in the history.
|
|
1461
|
+
* Check if a path is an ancestor of another.
|
|
1427
1462
|
*/
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
MERGING.set(board, false);
|
|
1431
|
-
fn();
|
|
1432
|
-
MERGING.set(board, prev);
|
|
1463
|
+
isAncestor(path, another) {
|
|
1464
|
+
return path.length < another.length && Path.compare(path, another) === 0;
|
|
1433
1465
|
},
|
|
1434
1466
|
/**
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
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.
|
|
1437
1473
|
*/
|
|
1438
|
-
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
/**
|
|
1447
|
-
* Hotkey mappings for each platform.
|
|
1448
|
-
*/
|
|
1449
|
-
const HOTKEYS = {
|
|
1450
|
-
bold: 'mod+b',
|
|
1451
|
-
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
1452
|
-
moveBackward: 'left',
|
|
1453
|
-
moveForward: 'right',
|
|
1454
|
-
moveUp: 'up',
|
|
1455
|
-
moveDown: 'down',
|
|
1456
|
-
moveWordBackward: 'ctrl+left',
|
|
1457
|
-
moveWordForward: 'ctrl+right',
|
|
1458
|
-
deleteBackward: 'shift?+backspace',
|
|
1459
|
-
deleteForward: 'shift?+delete',
|
|
1460
|
-
extendBackward: 'shift+left',
|
|
1461
|
-
extendForward: 'shift+right',
|
|
1462
|
-
italic: 'mod+i',
|
|
1463
|
-
splitBlock: 'shift?+enter',
|
|
1464
|
-
undo: 'mod+z'
|
|
1465
|
-
};
|
|
1466
|
-
const APPLE_HOTKEYS = {
|
|
1467
|
-
moveLineBackward: 'opt+up',
|
|
1468
|
-
moveLineForward: 'opt+down',
|
|
1469
|
-
moveWordBackward: 'opt+left',
|
|
1470
|
-
moveWordForward: 'opt+right',
|
|
1471
|
-
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
1472
|
-
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
1473
|
-
deleteLineBackward: 'cmd+shift?+backspace',
|
|
1474
|
-
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
1475
|
-
deleteWordBackward: 'opt+shift?+backspace',
|
|
1476
|
-
deleteWordForward: 'opt+shift?+delete',
|
|
1477
|
-
extendLineBackward: 'opt+shift+up',
|
|
1478
|
-
extendLineForward: 'opt+shift+down',
|
|
1479
|
-
redo: 'cmd+shift+z',
|
|
1480
|
-
transposeCharacter: 'ctrl+t'
|
|
1481
|
-
};
|
|
1482
|
-
const WINDOWS_HOTKEYS = {
|
|
1483
|
-
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
1484
|
-
deleteWordForward: 'ctrl+shift?+delete',
|
|
1485
|
-
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
1486
|
-
};
|
|
1487
|
-
/**
|
|
1488
|
-
* Create a platform-aware hotkey checker.
|
|
1489
|
-
*/
|
|
1490
|
-
const create = (key) => {
|
|
1491
|
-
const generic = HOTKEYS[key];
|
|
1492
|
-
const apple = APPLE_HOTKEYS[key];
|
|
1493
|
-
const windows = WINDOWS_HOTKEYS[key];
|
|
1494
|
-
const isGeneric = generic && isKeyHotkey(generic);
|
|
1495
|
-
const isApple = apple && isKeyHotkey(apple);
|
|
1496
|
-
const isWindows = windows && isKeyHotkey(windows);
|
|
1497
|
-
return (event) => {
|
|
1498
|
-
if (isGeneric && isGeneric(event)) {
|
|
1499
|
-
return true;
|
|
1500
|
-
}
|
|
1501
|
-
if (IS_APPLE && isApple && isApple(event)) {
|
|
1502
|
-
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;
|
|
1503
1481
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
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;
|
|
1506
1507
|
}
|
|
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
|
-
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
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
|
+
});
|
|
1548
1581
|
}
|
|
1549
|
-
|
|
1550
|
-
}
|
|
1582
|
+
};
|
|
1551
1583
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
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];
|
|
1564
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;
|
|
1565
1620
|
}
|
|
1566
|
-
const point1 = [x1 + radius, y1];
|
|
1567
|
-
const point2 = [x2 - radius, y1];
|
|
1568
|
-
const point3 = [x2, y1 + radius];
|
|
1569
|
-
const point4 = [x2, y2 - radius];
|
|
1570
|
-
const point5 = [x2 - radius, y2];
|
|
1571
|
-
const point6 = [x1 + radius, y2];
|
|
1572
|
-
const point7 = [x1, y2 - radius];
|
|
1573
|
-
const point8 = [x1, y1 + radius];
|
|
1574
|
-
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);
|
|
1575
|
-
}
|
|
1576
|
-
const drawRectangle = (board, rectangle, options) => {
|
|
1577
|
-
const roughSVG = PlaitBoard.getRoughSVG(board);
|
|
1578
|
-
const rectangleG = roughSVG.rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options);
|
|
1579
|
-
setStrokeLinecap(rectangleG, 'round');
|
|
1580
|
-
return rectangleG;
|
|
1581
1621
|
};
|
|
1582
1622
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
const rotatePoint = [
|
|
1591
|
-
end[0] > start[0] ? end[0] - rotateWidth : end[0] + rotateWidth,
|
|
1592
|
-
end[1] > start[1] ? end[1] - rotateHeight : end[1] + rotateHeight
|
|
1593
|
-
];
|
|
1594
|
-
const pointRight = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (degree * Math.PI) / 180);
|
|
1595
|
-
const pointLeft = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (-degree * Math.PI) / 180);
|
|
1596
|
-
return { pointLeft, pointRight };
|
|
1597
|
-
}
|
|
1598
|
-
function drawArrow(rs, start, end, options, maxHypotenuseLength = 10, degree = 40) {
|
|
1599
|
-
const { pointLeft, pointRight } = arrowPoints(start, end, maxHypotenuseLength, degree);
|
|
1600
|
-
const arrowLineLeft = rs.linearPath([pointLeft, end], options);
|
|
1601
|
-
const arrowLineRight = rs.linearPath([pointRight, end], options);
|
|
1602
|
-
return [arrowLineLeft, arrowLineRight];
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
function drawCircle(roughSVG, point, diameter, options) {
|
|
1606
|
-
return roughSVG.circle(point[0], point[1], diameter, options);
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
function drawLine(rs, start, end, options) {
|
|
1610
|
-
return rs.linearPath([start, end], options);
|
|
1611
|
-
}
|
|
1612
|
-
function drawLinearPath(points, options) {
|
|
1613
|
-
const g = createG();
|
|
1614
|
-
const path = createPath();
|
|
1615
|
-
let polylinePath = '';
|
|
1616
|
-
points.forEach((point, index) => {
|
|
1617
|
-
if (index === 0) {
|
|
1618
|
-
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' };
|
|
1619
1630
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1631
|
+
case 'remove_node': {
|
|
1632
|
+
return { ...op, type: 'insert_node' };
|
|
1622
1633
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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 };
|
|
1638
1654
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1655
|
+
case 'set_node': {
|
|
1656
|
+
const { properties, newProperties } = op;
|
|
1657
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
1641
1658
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
timerId = null;
|
|
1662
|
-
}
|
|
1663
|
-
scheduleFunc();
|
|
1664
|
-
};
|
|
1665
|
-
const debounce = (func, wait, options) => {
|
|
1666
|
-
let timerSubscription = null;
|
|
1667
|
-
return () => {
|
|
1668
|
-
if (timerSubscription && !timerSubscription.closed) {
|
|
1669
|
-
timerSubscription.unsubscribe();
|
|
1670
|
-
timerSubscription = timer(wait).subscribe(() => {
|
|
1671
|
-
func();
|
|
1672
|
-
});
|
|
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
|
+
}
|
|
1673
1678
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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 };
|
|
1677
1697
|
}
|
|
1678
|
-
timerSubscription = timer(wait).subscribe();
|
|
1679
1698
|
}
|
|
1680
|
-
|
|
1681
|
-
};
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1699
|
+
case 'set_theme': {
|
|
1700
|
+
const { properties, newProperties } = op;
|
|
1701
|
+
return { ...op, properties: newProperties, newProperties: properties };
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1685
1704
|
};
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
cacheMovingElements(board, [...movingElements, ...newElements]);
|
|
1705
|
+
const PlaitOperation = {
|
|
1706
|
+
isSetViewportOperation,
|
|
1707
|
+
inverse
|
|
1690
1708
|
};
|
|
1691
|
-
|
|
1692
|
-
|
|
1709
|
+
|
|
1710
|
+
const Point = {
|
|
1711
|
+
isEquals(point, otherPoint) {
|
|
1712
|
+
return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
|
|
1713
|
+
}
|
|
1693
1714
|
};
|
|
1694
|
-
|
|
1695
|
-
|
|
1715
|
+
|
|
1716
|
+
const Viewport = {
|
|
1717
|
+
isViewport: (value) => {
|
|
1718
|
+
return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
|
|
1719
|
+
},
|
|
1696
1720
|
};
|
|
1697
1721
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
if (!targetStyle) {
|
|
1701
|
-
return;
|
|
1702
|
-
}
|
|
1703
|
-
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
1704
|
-
if (sourceStyle.cssText) {
|
|
1705
|
-
targetStyle.cssText = sourceStyle.cssText;
|
|
1706
|
-
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
1707
|
-
}
|
|
1708
|
-
else {
|
|
1709
|
-
Array.from(sourceStyle).forEach(name => {
|
|
1710
|
-
let value = sourceStyle.getPropertyValue(name);
|
|
1711
|
-
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
function createCanvas(width, height, fillStyle) {
|
|
1716
|
-
const canvas = document.createElement('canvas');
|
|
1717
|
-
const ctx = canvas.getContext('2d');
|
|
1718
|
-
canvas.width = width;
|
|
1719
|
-
canvas.height = height;
|
|
1720
|
-
canvas.style.width = `${width}px`;
|
|
1721
|
-
canvas.style.height = `${height}px`;
|
|
1722
|
-
ctx.strokeStyle = '#ffffff';
|
|
1723
|
-
ctx.fillStyle = fillStyle;
|
|
1724
|
-
ctx.fillRect(0, 0, width, height);
|
|
1725
|
-
return {
|
|
1726
|
-
canvas,
|
|
1727
|
-
ctx
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
function isElementNode(node) {
|
|
1731
|
-
return node.nodeType === Node.ELEMENT_NODE;
|
|
1732
|
-
}
|
|
1733
|
-
function cloneSvg(board, options) {
|
|
1734
|
-
const elementHostBox = getRectangleByElements(board, board.children, true);
|
|
1735
|
-
const { width, height, x, y } = elementHostBox;
|
|
1736
|
-
const { padding = 4, inlineStyleClassNames } = options;
|
|
1737
|
-
const sourceSvg = PlaitBoard.getHost(board);
|
|
1738
|
-
const cloneSvgElement = sourceSvg.cloneNode(true);
|
|
1739
|
-
cloneSvgElement.style.width = `${width}px`;
|
|
1740
|
-
cloneSvgElement.style.height = `${height}px`;
|
|
1741
|
-
cloneSvgElement.style.backgroundColor = '';
|
|
1742
|
-
cloneSvgElement.setAttribute('width', `${width}`);
|
|
1743
|
-
cloneSvgElement.setAttribute('height', `${height}`);
|
|
1744
|
-
cloneSvgElement.setAttribute('viewBox', [x - padding, y - padding, width + 2 * padding, height + 2 * padding].join(','));
|
|
1745
|
-
if (inlineStyleClassNames) {
|
|
1746
|
-
const sourceNodes = Array.from(sourceSvg.querySelectorAll(inlineStyleClassNames));
|
|
1747
|
-
const cloneNodes = Array.from(cloneSvgElement.querySelectorAll(inlineStyleClassNames));
|
|
1748
|
-
sourceNodes.forEach((node, index) => {
|
|
1749
|
-
const cloneNode = cloneNodes[index];
|
|
1750
|
-
const childElements = Array.from(node.querySelectorAll('*')).filter(isElementNode);
|
|
1751
|
-
const cloneChildElements = Array.from(cloneNode.querySelectorAll('*')).filter(isElementNode);
|
|
1752
|
-
sourceNodes.push(...childElements);
|
|
1753
|
-
cloneNodes.push(...cloneChildElements);
|
|
1754
|
-
});
|
|
1755
|
-
sourceNodes.forEach((node, index) => {
|
|
1756
|
-
const cloneNode = cloneNodes[index];
|
|
1757
|
-
cloneCSSStyle(node, cloneNode);
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
return cloneSvgElement;
|
|
1761
|
-
}
|
|
1762
|
-
function loadImage(src) {
|
|
1763
|
-
return new Promise((resolve, reject) => {
|
|
1764
|
-
const img = new Image();
|
|
1765
|
-
img.onload = () => resolve(img);
|
|
1766
|
-
img.onerror = () => reject(new Error('Failed to load image'));
|
|
1767
|
-
img.src = src;
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
async function toImage(board, options) {
|
|
1771
|
-
if (!board) {
|
|
1772
|
-
return undefined;
|
|
1773
|
-
}
|
|
1774
|
-
const elementHostBox = getRectangleByElements(board, board.children, true);
|
|
1775
|
-
const { ratio = 2, fillStyle = 'transparent' } = options;
|
|
1776
|
-
const { width, height } = elementHostBox;
|
|
1777
|
-
const ratioWidth = width * ratio;
|
|
1778
|
-
const ratioHeight = height * ratio;
|
|
1779
|
-
const cloneSvgElement = cloneSvg(board, options);
|
|
1780
|
-
const { canvas, ctx } = createCanvas(ratioWidth, ratioHeight, fillStyle);
|
|
1781
|
-
const svgStr = new XMLSerializer().serializeToString(cloneSvgElement);
|
|
1782
|
-
const imgSrc = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;
|
|
1783
|
-
try {
|
|
1784
|
-
const img = await loadImage(imgSrc);
|
|
1785
|
-
ctx.drawImage(img, 0, 0, ratioWidth, ratioHeight);
|
|
1786
|
-
const url = canvas.toDataURL('image/png');
|
|
1787
|
-
return url;
|
|
1788
|
-
}
|
|
1789
|
-
catch (error) {
|
|
1790
|
-
console.error('Error converting SVG to image:', error);
|
|
1791
|
-
return undefined;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
function downloadImage(url, name) {
|
|
1795
|
-
const a = document.createElement('a');
|
|
1796
|
-
a.href = url;
|
|
1797
|
-
a.download = name;
|
|
1798
|
-
a.click();
|
|
1799
|
-
a.remove();
|
|
1800
|
-
}
|
|
1722
|
+
const SAVING = new WeakMap();
|
|
1723
|
+
const MERGING = new WeakMap();
|
|
1801
1724
|
|
|
1802
|
-
|
|
1803
|
-
|
|
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'
|
|
1804
1743
|
};
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
result.push(...pluginContextResult);
|
|
1810
|
-
}
|
|
1811
|
-
const stringObj = JSON.stringify(result);
|
|
1812
|
-
const encoded = window.btoa(encodeURIComponent(stringObj));
|
|
1813
|
-
data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
|
|
1744
|
+
const ColorfulThemeColor = {
|
|
1745
|
+
mode: ThemeColorMode.colorful,
|
|
1746
|
+
boardBackground: '#ffffff',
|
|
1747
|
+
textColor: '#333333'
|
|
1814
1748
|
};
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
|
|
1749
|
+
const SoftThemeColor = {
|
|
1750
|
+
mode: ThemeColorMode.soft,
|
|
1751
|
+
boardBackground: '#f5f5f5',
|
|
1752
|
+
textColor: '#333333'
|
|
1818
1753
|
};
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1754
|
+
const RetroThemeColor = {
|
|
1755
|
+
mode: ThemeColorMode.retro,
|
|
1756
|
+
boardBackground: '#f9f8ed',
|
|
1757
|
+
textColor: '#333333'
|
|
1823
1758
|
};
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
const decoded = decodeURIComponent(window.atob(encoded));
|
|
1829
|
-
nodesData = JSON.parse(decoded);
|
|
1830
|
-
}
|
|
1831
|
-
return nodesData;
|
|
1759
|
+
const DarkThemeColor = {
|
|
1760
|
+
mode: ThemeColorMode.dark,
|
|
1761
|
+
boardBackground: '#141414',
|
|
1762
|
+
textColor: '#FFFFFF'
|
|
1832
1763
|
};
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1764
|
+
const StarryThemeColor = {
|
|
1765
|
+
mode: ThemeColorMode.starry,
|
|
1766
|
+
boardBackground: '#0d2537',
|
|
1767
|
+
textColor: '#FFFFFF'
|
|
1835
1768
|
};
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
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
|
+
};
|
|
1842
1812
|
}
|
|
1843
|
-
return
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
};
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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)) {
|
|
1857
1838
|
return true;
|
|
1858
1839
|
}
|
|
1859
1840
|
else {
|
|
1860
|
-
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;
|
|
1861
1852
|
}
|
|
1853
|
+
const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
|
|
1854
|
+
IS_BOARD_CACHE.set(value, isBoard);
|
|
1855
|
+
return isBoard;
|
|
1862
1856
|
},
|
|
1863
|
-
|
|
1864
|
-
|
|
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);
|
|
1865
1938
|
}
|
|
1866
1939
|
};
|
|
1867
1940
|
|
|
1868
|
-
const
|
|
1869
|
-
return value.type === 'set_viewport';
|
|
1870
|
-
};
|
|
1871
|
-
const inverse = (op) => {
|
|
1941
|
+
const applyToDraft = (board, selection, viewport, theme, op) => {
|
|
1872
1942
|
switch (op.type) {
|
|
1873
1943
|
case 'insert_node': {
|
|
1874
|
-
|
|
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;
|
|
1875
1952
|
}
|
|
1876
1953
|
case 'remove_node': {
|
|
1877
|
-
|
|
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;
|
|
1878
1962
|
}
|
|
1879
1963
|
case 'move_node': {
|
|
1880
|
-
const {
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
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.`);
|
|
1884
1967
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
//
|
|
1889
|
-
//
|
|
1890
|
-
//
|
|
1891
|
-
//
|
|
1892
|
-
//
|
|
1893
|
-
//
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
const
|
|
1897
|
-
const
|
|
1898
|
-
|
|
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;
|
|
1899
1983
|
}
|
|
1900
1984
|
case 'set_node': {
|
|
1901
|
-
const { properties, newProperties } = op;
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
case 'set_selection': {
|
|
1905
|
-
const { properties, newProperties } = op;
|
|
1906
|
-
if (properties == null) {
|
|
1907
|
-
return {
|
|
1908
|
-
...op,
|
|
1909
|
-
properties: newProperties,
|
|
1910
|
-
newProperties: null
|
|
1911
|
-
};
|
|
1985
|
+
const { path, properties, newProperties } = op;
|
|
1986
|
+
if (path.length === 0) {
|
|
1987
|
+
throw new Error(`Cannot set properties on the root node!`);
|
|
1912
1988
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
}
|
|
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
|
+
}
|
|
1919
1998
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
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
|
+
}
|
|
1922
2004
|
}
|
|
2005
|
+
break;
|
|
1923
2006
|
}
|
|
1924
2007
|
case 'set_viewport': {
|
|
1925
|
-
const {
|
|
1926
|
-
if (
|
|
1927
|
-
|
|
1928
|
-
...op,
|
|
1929
|
-
properties: newProperties,
|
|
1930
|
-
newProperties: newProperties
|
|
1931
|
-
};
|
|
2008
|
+
const { newProperties } = op;
|
|
2009
|
+
if (newProperties == null) {
|
|
2010
|
+
viewport = newProperties;
|
|
1932
2011
|
}
|
|
1933
|
-
else
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
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;
|
|
1939
2035
|
}
|
|
1940
2036
|
else {
|
|
1941
|
-
|
|
2037
|
+
if (selection === null) {
|
|
2038
|
+
selection = op.newProperties;
|
|
2039
|
+
}
|
|
2040
|
+
else {
|
|
2041
|
+
selection.ranges = newProperties.ranges;
|
|
2042
|
+
}
|
|
1942
2043
|
}
|
|
2044
|
+
break;
|
|
1943
2045
|
}
|
|
1944
2046
|
case 'set_theme': {
|
|
1945
|
-
const {
|
|
1946
|
-
|
|
2047
|
+
const { newProperties } = op;
|
|
2048
|
+
theme = newProperties;
|
|
2049
|
+
break;
|
|
1947
2050
|
}
|
|
1948
2051
|
}
|
|
2052
|
+
return { selection, viewport, theme };
|
|
1949
2053
|
};
|
|
1950
|
-
const
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
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
|
+
}
|
|
1958
2080
|
}
|
|
1959
2081
|
};
|
|
1960
2082
|
|
|
1961
|
-
|
|
1962
|
-
const
|
|
1963
|
-
|
|
1964
|
-
var PlaitPluginKey;
|
|
1965
|
-
(function (PlaitPluginKey) {
|
|
1966
|
-
PlaitPluginKey["withSelection"] = "withSelection";
|
|
1967
|
-
})(PlaitPluginKey || (PlaitPluginKey = {}));
|
|
1968
|
-
|
|
1969
|
-
const IS_FROM_SCROLLING = new WeakMap();
|
|
1970
|
-
const IS_FROM_VIEWPORT_CHANGE = new WeakMap();
|
|
1971
|
-
function getViewportContainerRect(board) {
|
|
1972
|
-
const { hideScrollbar } = board.options;
|
|
1973
|
-
const scrollBarWidth = hideScrollbar ? SCROLL_BAR_WIDTH : 0;
|
|
1974
|
-
const viewportRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1975
|
-
return {
|
|
1976
|
-
width: viewportRect.width + scrollBarWidth,
|
|
1977
|
-
height: viewportRect.height + scrollBarWidth
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
function getElementHostBBox(board, zoom) {
|
|
1981
|
-
const childrenRect = getRectangleByElements(board, board.children, true);
|
|
1982
|
-
const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
1983
|
-
const containerWidth = viewportContainerRect.width / zoom;
|
|
1984
|
-
const containerHeight = viewportContainerRect.height / zoom;
|
|
1985
|
-
let left;
|
|
1986
|
-
let right;
|
|
1987
|
-
let top;
|
|
1988
|
-
let bottom;
|
|
1989
|
-
if (childrenRect.width < containerWidth) {
|
|
1990
|
-
const centerX = childrenRect.x + childrenRect.width / 2;
|
|
1991
|
-
const halfContainerWidth = containerWidth / 2;
|
|
1992
|
-
left = centerX - halfContainerWidth;
|
|
1993
|
-
right = centerX + halfContainerWidth;
|
|
1994
|
-
}
|
|
1995
|
-
else {
|
|
1996
|
-
left = childrenRect.x;
|
|
1997
|
-
right = childrenRect.x + childrenRect.width;
|
|
1998
|
-
}
|
|
1999
|
-
if (childrenRect.height < containerHeight) {
|
|
2000
|
-
const centerY = childrenRect.y + childrenRect.height / 2;
|
|
2001
|
-
const halfContainerHeight = containerHeight / 2;
|
|
2002
|
-
top = centerY - halfContainerHeight;
|
|
2003
|
-
bottom = centerY + halfContainerHeight;
|
|
2004
|
-
}
|
|
2005
|
-
else {
|
|
2006
|
-
top = childrenRect.y;
|
|
2007
|
-
bottom = childrenRect.y + childrenRect.height;
|
|
2008
|
-
}
|
|
2009
|
-
return {
|
|
2010
|
-
left,
|
|
2011
|
-
right,
|
|
2012
|
-
top,
|
|
2013
|
-
bottom
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
/**
|
|
2017
|
-
* 验证缩放比是否符合限制,如果超出限制,则返回合适的缩放比
|
|
2018
|
-
* @param zoom 缩放比
|
|
2019
|
-
* @param minZoom 最小缩放比
|
|
2020
|
-
* @param maxZoom 最大缩放比
|
|
2021
|
-
* @returns 正确的缩放比
|
|
2022
|
-
*/
|
|
2023
|
-
function clampZoomLevel(zoom, minZoom = 0.2, maxZoom = 4) {
|
|
2024
|
-
return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
|
|
2025
|
-
}
|
|
2026
|
-
function getViewBox(board, zoom) {
|
|
2027
|
-
const boardContainerRectangle = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
|
|
2028
|
-
const elementHostBBox = getElementHostBBox(board, zoom);
|
|
2029
|
-
const horizontalPadding = boardContainerRectangle.width / 2;
|
|
2030
|
-
const verticalPadding = boardContainerRectangle.height / 2;
|
|
2031
|
-
const viewBox = [
|
|
2032
|
-
elementHostBBox.left - horizontalPadding / zoom,
|
|
2033
|
-
elementHostBBox.top - verticalPadding / zoom,
|
|
2034
|
-
elementHostBBox.right - elementHostBBox.left + (horizontalPadding * 2) / zoom,
|
|
2035
|
-
elementHostBBox.bottom - elementHostBBox.top + (verticalPadding * 2) / zoom
|
|
2036
|
-
];
|
|
2037
|
-
return viewBox;
|
|
2038
|
-
}
|
|
2039
|
-
function getViewBoxCenterPoint(board) {
|
|
2040
|
-
const childrenRectangle = getRectangleByElements(board, board.children, true);
|
|
2041
|
-
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);
|
|
2042
2086
|
}
|
|
2043
|
-
function
|
|
2044
|
-
const
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
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
|
+
}
|
|
2051
2099
|
}
|
|
2100
|
+
const operation = { type: 'set_node', properties, newProperties, path };
|
|
2101
|
+
board.apply(operation);
|
|
2052
2102
|
}
|
|
2053
|
-
function
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
const { zoom } = board.viewport;
|
|
2058
|
-
const viewBox = getViewBox(board, zoom);
|
|
2059
|
-
const scrollLeft = (origination[0] - viewBox[0]) * zoom;
|
|
2060
|
-
const scrollTop = (origination[1] - viewBox[1]) * zoom;
|
|
2061
|
-
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);
|
|
2062
2107
|
}
|
|
2063
|
-
function
|
|
2064
|
-
const
|
|
2065
|
-
|
|
2066
|
-
viewportContainer.scrollLeft = left;
|
|
2067
|
-
viewportContainer.scrollTop = top;
|
|
2068
|
-
isFromViewportChange && setIsFromViewportChange(board, true);
|
|
2069
|
-
}
|
|
2108
|
+
function moveNode(board, path, newPath) {
|
|
2109
|
+
const operation = { type: 'move_node', path, newPath };
|
|
2110
|
+
board.apply(operation);
|
|
2070
2111
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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);
|
|
2076
2122
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
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
|
+
});
|
|
2081
2132
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
const viewBox = getViewBox(board, zoom);
|
|
2087
|
-
const centerX = viewBox[0] + viewBox[2] / 2;
|
|
2088
|
-
const centerY = viewBox[1] + viewBox[3] / 2;
|
|
2089
|
-
const origination = [centerX - viewportContainerRect.width / 2 / zoom, centerY - viewportContainerRect.height / 2 / zoom];
|
|
2090
|
-
updateViewportOrigination(board, origination);
|
|
2091
|
-
updateViewportOffset(board);
|
|
2092
|
-
return;
|
|
2093
|
-
}
|
|
2094
|
-
updateViewportOffset(board);
|
|
2133
|
+
|
|
2134
|
+
function setViewport(board, viewport) {
|
|
2135
|
+
const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
|
|
2136
|
+
board.apply(operation);
|
|
2095
2137
|
}
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
};
|
|
2099
|
-
const clearViewportOrigination = (board) => {
|
|
2100
|
-
BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
|
|
2101
|
-
};
|
|
2102
|
-
const getViewportOrigination = (board) => {
|
|
2103
|
-
const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
|
|
2104
|
-
if (origination) {
|
|
2105
|
-
return origination;
|
|
2106
|
-
}
|
|
2107
|
-
else {
|
|
2108
|
-
return board.viewport.origination;
|
|
2109
|
-
}
|
|
2110
|
-
};
|
|
2111
|
-
const isFromScrolling = (board) => {
|
|
2112
|
-
return !!IS_FROM_SCROLLING.get(board);
|
|
2113
|
-
};
|
|
2114
|
-
const setIsFromScrolling = (board, state) => {
|
|
2115
|
-
IS_FROM_SCROLLING.set(board, state);
|
|
2116
|
-
};
|
|
2117
|
-
const isFromViewportChange = (board) => {
|
|
2118
|
-
return !!IS_FROM_VIEWPORT_CHANGE.get(board);
|
|
2119
|
-
};
|
|
2120
|
-
const setIsFromViewportChange = (board, state) => {
|
|
2121
|
-
IS_FROM_VIEWPORT_CHANGE.set(board, state);
|
|
2138
|
+
const ViewportTransforms$1 = {
|
|
2139
|
+
setViewport
|
|
2122
2140
|
};
|
|
2123
|
-
function scrollToRectangle(board, client) { }
|
|
2124
2141
|
|
|
2125
2142
|
function setTheme(board, themeColorMode) {
|
|
2126
2143
|
const operation = { type: 'set_theme', properties: board.theme, newProperties: themeColorMode };
|
|
@@ -3303,6 +3320,7 @@ class PlaitBoardComponent {
|
|
|
3303
3320
|
.subscribe((event) => {
|
|
3304
3321
|
const selectedElements = getSelectedElements(this.board);
|
|
3305
3322
|
if (selectedElements.length > 0 && (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
|
|
3323
|
+
event.preventDefault();
|
|
3306
3324
|
this.board.deleteFragment(null);
|
|
3307
3325
|
}
|
|
3308
3326
|
this.board.keydown(event);
|
|
@@ -3658,5 +3676,5 @@ function createModModifierKeys() {
|
|
|
3658
3676
|
* Generated bundle index. Do not edit.
|
|
3659
3677
|
*/
|
|
3660
3678
|
|
|
3661
|
-
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, setPathStrokeLinecap, setSVGViewBox, setSelectionMoving, setStrokeLinecap, 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 };
|
|
3662
3680
|
//# sourceMappingURL=plait-core.mjs.map
|