@plait/core 0.0.2
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/README.md +1 -0
- package/board/board.component.d.ts +37 -0
- package/core/base/detector.d.ts +7 -0
- package/core/element/element.component.d.ts +27 -0
- package/esm2020/board/board.component.mjs +211 -0
- package/esm2020/core/base/detector.mjs +2 -0
- package/esm2020/core/element/element.component.mjs +70 -0
- package/esm2020/interfaces/board.mjs +2 -0
- package/esm2020/interfaces/cursor.mjs +6 -0
- package/esm2020/interfaces/custom-types.mjs +5 -0
- package/esm2020/interfaces/element-context.mjs +2 -0
- package/esm2020/interfaces/element.mjs +2 -0
- package/esm2020/interfaces/graph.mjs +2 -0
- package/esm2020/interfaces/index.mjs +14 -0
- package/esm2020/interfaces/node.mjs +20 -0
- package/esm2020/interfaces/operation.mjs +7 -0
- package/esm2020/interfaces/path.mjs +139 -0
- package/esm2020/interfaces/plugin.mjs +2 -0
- package/esm2020/interfaces/point.mjs +2 -0
- package/esm2020/interfaces/selection.mjs +2 -0
- package/esm2020/interfaces/viewport.mjs +10 -0
- package/esm2020/plait-core.mjs +5 -0
- package/esm2020/plait.module.mjs +19 -0
- package/esm2020/plugins/create-board.mjs +42 -0
- package/esm2020/plugins/with-board.mjs +13 -0
- package/esm2020/plugins/with-selection.mjs +39 -0
- package/esm2020/public-api.mjs +11 -0
- package/esm2020/transfroms/general.mjs +139 -0
- package/esm2020/transfroms/index.mjs +11 -0
- package/esm2020/transfroms/node.mjs +36 -0
- package/esm2020/transfroms/selection.mjs +8 -0
- package/esm2020/transfroms/viewport.mjs +8 -0
- package/esm2020/utils/board.mjs +25 -0
- package/esm2020/utils/dom.mjs +22 -0
- package/esm2020/utils/environment.mjs +13 -0
- package/esm2020/utils/graph.mjs +11 -0
- package/esm2020/utils/helper.mjs +4 -0
- package/esm2020/utils/hotkeys.mjs +99 -0
- package/esm2020/utils/id-creator.mjs +11 -0
- package/esm2020/utils/index.mjs +10 -0
- package/esm2020/utils/math.mjs +37 -0
- package/esm2020/utils/weak-maps.mjs +6 -0
- package/fesm2015/plait-core.mjs +969 -0
- package/fesm2015/plait-core.mjs.map +1 -0
- package/fesm2020/plait-core.mjs +984 -0
- package/fesm2020/plait-core.mjs.map +1 -0
- package/interfaces/board.d.ts +32 -0
- package/interfaces/cursor.d.ts +5 -0
- package/interfaces/custom-types.d.ts +9 -0
- package/interfaces/element-context.d.ts +4 -0
- package/interfaces/element.d.ts +6 -0
- package/interfaces/graph.d.ts +6 -0
- package/interfaces/index.d.ts +13 -0
- package/interfaces/node.d.ts +13 -0
- package/interfaces/operation.d.ts +39 -0
- package/interfaces/path.d.ts +32 -0
- package/interfaces/plugin.d.ts +2 -0
- package/interfaces/point.d.ts +1 -0
- package/interfaces/selection.d.ts +5 -0
- package/interfaces/viewport.d.ts +13 -0
- package/package.json +31 -0
- package/plait-core.d.ts +5 -0
- package/plait.module.d.ts +9 -0
- package/plugins/create-board.d.ts +3 -0
- package/plugins/with-board.d.ts +2 -0
- package/plugins/with-selection.d.ts +2 -0
- package/public-api.d.ts +7 -0
- package/transfroms/general.d.ts +6 -0
- package/transfroms/index.d.ts +5 -0
- package/transfroms/node.d.ts +14 -0
- package/transfroms/selection.d.ts +7 -0
- package/transfroms/viewport.d.ts +7 -0
- package/utils/board.d.ts +10 -0
- package/utils/dom.d.ts +6 -0
- package/utils/environment.d.ts +7 -0
- package/utils/graph.d.ts +3 -0
- package/utils/helper.d.ts +1 -0
- package/utils/hotkeys.d.ts +32 -0
- package/utils/id-creator.d.ts +1 -0
- package/utils/index.d.ts +9 -0
- package/utils/math.d.ts +2 -0
- package/utils/weak-maps.d.ts +6 -0
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, ChangeDetectionStrategy, Input, EventEmitter, HostBinding, ViewChild, Output, NgModule } from '@angular/core';
|
|
3
|
+
import produce, { createDraft, finishDraft, isDraft } from 'immer';
|
|
4
|
+
import { Subject, fromEvent } from 'rxjs';
|
|
5
|
+
import { takeUntil, filter } from 'rxjs/operators';
|
|
6
|
+
import rough from 'roughjs/bin/rough';
|
|
7
|
+
import * as i2 from '@angular/common';
|
|
8
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
9
|
+
import { isKeyHotkey } from 'is-hotkey';
|
|
10
|
+
|
|
11
|
+
// record richtext type status
|
|
12
|
+
const FLUSHING = new WeakMap();
|
|
13
|
+
const IS_TEXT_EDITABLE = new WeakMap();
|
|
14
|
+
const BOARD_TO_ON_CHANGE = new WeakMap();
|
|
15
|
+
const HOST_TO_ROUGH_SVG = new WeakMap();
|
|
16
|
+
|
|
17
|
+
function isNullOrUndefined(value) {
|
|
18
|
+
return value === null || value === undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Viewport = {
|
|
22
|
+
isViewport: (value) => {
|
|
23
|
+
return (!isNullOrUndefined(value.offsetX) &&
|
|
24
|
+
!isNullOrUndefined(value.offsetY) &&
|
|
25
|
+
!isNullOrUndefined(value.zoom) &&
|
|
26
|
+
!isNullOrUndefined(value.viewBackgroundColor));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Path = {
|
|
31
|
+
parent(path) {
|
|
32
|
+
if (path.length === 0) {
|
|
33
|
+
throw new Error(`Cannot get the parent path of the root path [${path}].`);
|
|
34
|
+
}
|
|
35
|
+
return path.slice(0, -1);
|
|
36
|
+
},
|
|
37
|
+
next(path) {
|
|
38
|
+
if (path.length === 0) {
|
|
39
|
+
throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
|
|
40
|
+
}
|
|
41
|
+
const last = path[path.length - 1];
|
|
42
|
+
return path.slice(0, -1).concat(last + 1);
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* Check if a path is an ancestor of another.
|
|
46
|
+
*/
|
|
47
|
+
isAncestor(path, another) {
|
|
48
|
+
return path.length < another.length && Path.compare(path, another) === 0;
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Compare a path to another, returning an integer indicating whether the path
|
|
52
|
+
* was before, at, or after the other.
|
|
53
|
+
*
|
|
54
|
+
* Note: Two paths of unequal length can still receive a `0` result if one is
|
|
55
|
+
* directly above or below the other. If you want exact matching, use
|
|
56
|
+
* [[Path.equals]] instead.
|
|
57
|
+
*/
|
|
58
|
+
compare(path, another) {
|
|
59
|
+
const min = Math.min(path.length, another.length);
|
|
60
|
+
for (let i = 0; i < min; i++) {
|
|
61
|
+
if (path[i] < another[i])
|
|
62
|
+
return -1;
|
|
63
|
+
if (path[i] > another[i])
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* Check if a path is exactly equal to another.
|
|
70
|
+
*/
|
|
71
|
+
equals(path, another) {
|
|
72
|
+
return path.length === another.length && path.every((n, i) => n === another[i]);
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Check if a path ends before one of the indexes in another.
|
|
76
|
+
*/
|
|
77
|
+
endsBefore(path, another) {
|
|
78
|
+
const i = path.length - 1;
|
|
79
|
+
const as = path.slice(0, i);
|
|
80
|
+
const bs = another.slice(0, i);
|
|
81
|
+
const av = path[i];
|
|
82
|
+
const bv = another[i];
|
|
83
|
+
return Path.equals(as, bs) && av < bv;
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* Check if a path is a sibling of another.
|
|
87
|
+
*/
|
|
88
|
+
isSibling(path, another) {
|
|
89
|
+
if (path.length !== another.length) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const as = path.slice(0, -1);
|
|
93
|
+
const bs = another.slice(0, -1);
|
|
94
|
+
const al = path[path.length - 1];
|
|
95
|
+
const bl = another[another.length - 1];
|
|
96
|
+
return al !== bl && Path.equals(as, bs);
|
|
97
|
+
},
|
|
98
|
+
transform(path, operation) {
|
|
99
|
+
return produce(path, p => {
|
|
100
|
+
// PERF: Exit early if the operation is guaranteed not to have an effect.
|
|
101
|
+
if (!path || path?.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (p === null) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
switch (operation.type) {
|
|
108
|
+
case 'insert_node': {
|
|
109
|
+
const { path: op } = operation;
|
|
110
|
+
if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
|
|
111
|
+
p[op.length - 1] += 1;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'remove_node': {
|
|
116
|
+
const { path: op } = operation;
|
|
117
|
+
if (Path.equals(op, p) || Path.isAncestor(op, p)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
else if (Path.endsBefore(op, p)) {
|
|
121
|
+
p[op.length - 1] -= 1;
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'move_node': {
|
|
126
|
+
const { path: op, newPath: onp } = operation;
|
|
127
|
+
// If the old and new path are the same, it's a no-op.
|
|
128
|
+
if (Path.equals(op, onp)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (Path.isAncestor(op, p) || Path.equals(op, p)) {
|
|
132
|
+
const copy = onp.slice();
|
|
133
|
+
// op.length <= onp.length is different for slate
|
|
134
|
+
// resolve drag from [0, 0] to [0, 3] issue
|
|
135
|
+
if (Path.endsBefore(op, onp) && op.length <= onp.length) {
|
|
136
|
+
copy[op.length - 1] -= 1;
|
|
137
|
+
}
|
|
138
|
+
return copy.concat(p.slice(op.length));
|
|
139
|
+
}
|
|
140
|
+
else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
|
|
141
|
+
if (Path.endsBefore(op, p)) {
|
|
142
|
+
p[op.length - 1] -= 1;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
p[op.length - 1] += 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
|
|
149
|
+
if (Path.endsBefore(op, p)) {
|
|
150
|
+
p[op.length - 1] -= 1;
|
|
151
|
+
}
|
|
152
|
+
p[onp.length - 1] += 1;
|
|
153
|
+
}
|
|
154
|
+
else if (Path.endsBefore(op, p)) {
|
|
155
|
+
if (Path.equals(onp, p)) {
|
|
156
|
+
p[onp.length - 1] += 1;
|
|
157
|
+
}
|
|
158
|
+
p[op.length - 1] -= 1;
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const PlaitNode = {
|
|
169
|
+
parent: (board, path) => {
|
|
170
|
+
const parentPath = Path.parent(path);
|
|
171
|
+
const p = PlaitNode.get(board, parentPath);
|
|
172
|
+
return p;
|
|
173
|
+
},
|
|
174
|
+
get(board, path) {
|
|
175
|
+
let node = board;
|
|
176
|
+
for (let i = 0; i < path.length; i++) {
|
|
177
|
+
const p = path[i];
|
|
178
|
+
if (!node || !node.children || !node.children[p]) {
|
|
179
|
+
throw new Error(`Cannot find a descendant at path [${path}]`);
|
|
180
|
+
}
|
|
181
|
+
node = node.children[p];
|
|
182
|
+
}
|
|
183
|
+
return node;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const applyToDraft = (board, selection, viewport, op) => {
|
|
188
|
+
switch (op.type) {
|
|
189
|
+
case 'insert_node': {
|
|
190
|
+
const { path, node } = op;
|
|
191
|
+
const parent = PlaitNode.parent(board, path);
|
|
192
|
+
const index = path[path.length - 1];
|
|
193
|
+
if (!parent.children || index > parent.children.length) {
|
|
194
|
+
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
195
|
+
}
|
|
196
|
+
parent.children.splice(index, 0, node);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case 'remove_node': {
|
|
200
|
+
const { path } = op;
|
|
201
|
+
const parent = PlaitNode.parent(board, path);
|
|
202
|
+
const index = path[path.length - 1];
|
|
203
|
+
if (!parent.children || index > parent.children.length) {
|
|
204
|
+
throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
|
|
205
|
+
}
|
|
206
|
+
parent.children.splice(index, 1);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'move_node': {
|
|
210
|
+
const { path, newPath } = op;
|
|
211
|
+
if (Path.isAncestor(path, newPath)) {
|
|
212
|
+
throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
|
|
213
|
+
}
|
|
214
|
+
const node = PlaitNode.get(board, path);
|
|
215
|
+
const parent = PlaitNode.parent(board, path);
|
|
216
|
+
const index = path[path.length - 1];
|
|
217
|
+
// This is tricky, but since the `path` and `newPath` both refer to
|
|
218
|
+
// the same snapshot in time, there's a mismatch. After either
|
|
219
|
+
// removing the original position, the second step's path can be out
|
|
220
|
+
// of date. So instead of using the `op.newPath` directly, we
|
|
221
|
+
// transform `op.path` to ascertain what the `newPath` would be after
|
|
222
|
+
// the operation was applied.
|
|
223
|
+
parent.children?.splice(index, 1);
|
|
224
|
+
const truePath = Path.transform(path, op);
|
|
225
|
+
const newParent = PlaitNode.get(board, Path.parent(truePath));
|
|
226
|
+
const newIndex = truePath[truePath.length - 1];
|
|
227
|
+
newParent.children?.splice(newIndex, 0, node);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'set_node': {
|
|
231
|
+
const { path, properties, newProperties } = op;
|
|
232
|
+
if (path.length === 0) {
|
|
233
|
+
throw new Error(`Cannot set properties on the root node!`);
|
|
234
|
+
}
|
|
235
|
+
const node = PlaitNode.get(board, path);
|
|
236
|
+
for (const key in newProperties) {
|
|
237
|
+
const value = newProperties[key];
|
|
238
|
+
if (value == null) {
|
|
239
|
+
delete node[key];
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
node[key] = value;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// properties that were previously defined, but are now missing, must be deleted
|
|
246
|
+
for (const key in properties) {
|
|
247
|
+
if (!newProperties.hasOwnProperty(key)) {
|
|
248
|
+
delete node[key];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case 'set_viewport': {
|
|
254
|
+
const { newProperties } = op;
|
|
255
|
+
if (newProperties == null) {
|
|
256
|
+
viewport = newProperties;
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
if (viewport == null) {
|
|
260
|
+
if (!Viewport.isViewport(newProperties)) {
|
|
261
|
+
throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
|
|
262
|
+
}
|
|
263
|
+
viewport = { ...newProperties };
|
|
264
|
+
}
|
|
265
|
+
for (const key in newProperties) {
|
|
266
|
+
const value = newProperties[key];
|
|
267
|
+
if (value == null) {
|
|
268
|
+
delete viewport[key];
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
viewport[key] = value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case 'set_selection': {
|
|
278
|
+
const { newProperties } = op;
|
|
279
|
+
if (newProperties == null) {
|
|
280
|
+
selection = newProperties;
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
if (selection === null) {
|
|
284
|
+
selection = op.newProperties;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
selection.anchor = newProperties.anchor;
|
|
288
|
+
selection.focus = newProperties.focus;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return { selection, viewport };
|
|
295
|
+
};
|
|
296
|
+
const GeneralTransforms = {
|
|
297
|
+
/**
|
|
298
|
+
* Transform the board by an operation.
|
|
299
|
+
*/
|
|
300
|
+
transform(board, op) {
|
|
301
|
+
board.children = createDraft(board.children);
|
|
302
|
+
let viewport = board.viewport && createDraft(board.viewport);
|
|
303
|
+
let selection = board.selection && createDraft(board.selection);
|
|
304
|
+
try {
|
|
305
|
+
const state = applyToDraft(board, selection, viewport, op);
|
|
306
|
+
viewport = state.viewport;
|
|
307
|
+
selection = state.selection;
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
board.children = finishDraft(board.children);
|
|
311
|
+
if (selection) {
|
|
312
|
+
board.selection = isDraft(selection) ? finishDraft(selection) : selection;
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
board.selection = null;
|
|
316
|
+
}
|
|
317
|
+
board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
function insertNode(board, node, path) {
|
|
323
|
+
const operation = { type: 'insert_node', node, path };
|
|
324
|
+
board.apply(operation);
|
|
325
|
+
}
|
|
326
|
+
function setNode(board, props, path) {
|
|
327
|
+
const properties = {};
|
|
328
|
+
const newProperties = {};
|
|
329
|
+
const node = PlaitNode.get(board, path);
|
|
330
|
+
for (const k in props) {
|
|
331
|
+
if (node[k] !== props[k]) {
|
|
332
|
+
if (node.hasOwnProperty(k)) {
|
|
333
|
+
properties[k] = node[k];
|
|
334
|
+
}
|
|
335
|
+
if (props[k] != null)
|
|
336
|
+
newProperties[k] = props[k];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const operation = { type: 'set_node', properties, newProperties, path };
|
|
340
|
+
board.apply(operation);
|
|
341
|
+
}
|
|
342
|
+
function removeNode(board, path) {
|
|
343
|
+
const operation = { type: 'remove_node', path };
|
|
344
|
+
board.apply(operation);
|
|
345
|
+
}
|
|
346
|
+
function moveNode(board, path, newPath) {
|
|
347
|
+
const operation = { type: 'move_node', path, newPath };
|
|
348
|
+
board.apply(operation);
|
|
349
|
+
}
|
|
350
|
+
const NodeTransforms = {
|
|
351
|
+
insertNode,
|
|
352
|
+
setNode,
|
|
353
|
+
removeNode,
|
|
354
|
+
moveNode
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
function setSelection(board, selection) {
|
|
358
|
+
const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
|
|
359
|
+
board.apply(operation);
|
|
360
|
+
}
|
|
361
|
+
const SelectionTransforms = {
|
|
362
|
+
setSelection
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
function setViewport(board, viewport) {
|
|
366
|
+
const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
|
|
367
|
+
board.apply(operation);
|
|
368
|
+
}
|
|
369
|
+
const ViewportTransforms = {
|
|
370
|
+
setViewport
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const Transforms = {
|
|
374
|
+
...GeneralTransforms,
|
|
375
|
+
...ViewportTransforms,
|
|
376
|
+
...SelectionTransforms,
|
|
377
|
+
...NodeTransforms
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
var BaseCursorStatus;
|
|
381
|
+
(function (BaseCursorStatus) {
|
|
382
|
+
BaseCursorStatus["move"] = "move";
|
|
383
|
+
BaseCursorStatus["select"] = "select";
|
|
384
|
+
})(BaseCursorStatus || (BaseCursorStatus = {}));
|
|
385
|
+
|
|
386
|
+
function createBoard(host, children) {
|
|
387
|
+
const board = {
|
|
388
|
+
host,
|
|
389
|
+
viewport: {
|
|
390
|
+
offsetX: 0,
|
|
391
|
+
offsetY: 0,
|
|
392
|
+
zoom: 1,
|
|
393
|
+
viewBackgroundColor: '#000'
|
|
394
|
+
},
|
|
395
|
+
children,
|
|
396
|
+
operations: [],
|
|
397
|
+
selection: { anchor: [0, -1], focus: [-1, -1] },
|
|
398
|
+
cursor: BaseCursorStatus.select,
|
|
399
|
+
apply: (operation) => {
|
|
400
|
+
board.operations.push(operation);
|
|
401
|
+
Transforms.transform(board, operation);
|
|
402
|
+
if (!FLUSHING.get(board)) {
|
|
403
|
+
FLUSHING.set(board, true);
|
|
404
|
+
Promise.resolve().then(() => {
|
|
405
|
+
FLUSHING.set(board, false);
|
|
406
|
+
board.onChange();
|
|
407
|
+
board.operations = [];
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
onChange: () => { },
|
|
412
|
+
mousedown: (event) => { },
|
|
413
|
+
mouseup: (event) => { },
|
|
414
|
+
mousemove: (event) => { },
|
|
415
|
+
keydown: (event) => { },
|
|
416
|
+
keyup: (event) => { },
|
|
417
|
+
dblclick: (event) => { },
|
|
418
|
+
drawElement: (context) => [],
|
|
419
|
+
redrawElement: (context, changes) => [],
|
|
420
|
+
destroyElement: () => { }
|
|
421
|
+
};
|
|
422
|
+
return board;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function withBoard(board) {
|
|
426
|
+
const { onChange, mouseup } = board;
|
|
427
|
+
board.onChange = () => {
|
|
428
|
+
const onContextChange = BOARD_TO_ON_CHANGE.get(board);
|
|
429
|
+
if (onContextChange) {
|
|
430
|
+
onContextChange();
|
|
431
|
+
}
|
|
432
|
+
onChange();
|
|
433
|
+
};
|
|
434
|
+
return board;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const NS = 'http://www.w3.org/2000/svg';
|
|
438
|
+
function toPoint(x, y, container) {
|
|
439
|
+
const rect = container.getBoundingClientRect();
|
|
440
|
+
return [x - rect.x, y - rect.y];
|
|
441
|
+
}
|
|
442
|
+
function createG() {
|
|
443
|
+
const newG = document.createElementNS(NS, 'g');
|
|
444
|
+
return newG;
|
|
445
|
+
}
|
|
446
|
+
function createSVG() {
|
|
447
|
+
const svg = document.createElementNS(NS, 'svg');
|
|
448
|
+
return svg;
|
|
449
|
+
}
|
|
450
|
+
function createText(x, y, fill, textContent) {
|
|
451
|
+
var text = document.createElementNS(NS, 'text');
|
|
452
|
+
text.setAttribute('x', `${x}`);
|
|
453
|
+
text.setAttribute('y', `${y}`);
|
|
454
|
+
text.setAttribute('fill', fill);
|
|
455
|
+
text.textContent = textContent;
|
|
456
|
+
return text;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function toRectangleClient(points) {
|
|
460
|
+
const xArray = points.map(ele => ele[0]);
|
|
461
|
+
const yArray = points.map(ele => ele[1]);
|
|
462
|
+
const xMin = Math.min(...xArray);
|
|
463
|
+
const xMax = Math.max(...xArray);
|
|
464
|
+
const yMin = Math.min(...yArray);
|
|
465
|
+
const yMax = Math.max(...yArray);
|
|
466
|
+
const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
467
|
+
return rect;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function withSelection(board) {
|
|
471
|
+
const { mousedown, mousemove, mouseup } = board;
|
|
472
|
+
let start = null;
|
|
473
|
+
let end = null;
|
|
474
|
+
board.mousedown = (event) => {
|
|
475
|
+
// avoid select text when double click svg
|
|
476
|
+
if (!(event.target instanceof HTMLElement && event.target.closest('.richtext'))) {
|
|
477
|
+
event.preventDefault();
|
|
478
|
+
}
|
|
479
|
+
if (board.cursor === BaseCursorStatus.select) {
|
|
480
|
+
start = toPoint(event.x, event.y, board.host);
|
|
481
|
+
}
|
|
482
|
+
mousedown(event);
|
|
483
|
+
};
|
|
484
|
+
board.mousemove = (event) => {
|
|
485
|
+
const movedTarget = toPoint(event.x, event.y, board.host);
|
|
486
|
+
if (start) {
|
|
487
|
+
const rectangleClient = toRectangleClient([start, movedTarget]);
|
|
488
|
+
if (start && Math.hypot(rectangleClient.width, rectangleClient.height) > 5) {
|
|
489
|
+
end = movedTarget;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
mousemove(event);
|
|
493
|
+
};
|
|
494
|
+
board.mouseup = (event) => {
|
|
495
|
+
if (start) {
|
|
496
|
+
Transforms.setSelection(board, { anchor: start, focus: start });
|
|
497
|
+
}
|
|
498
|
+
start = null;
|
|
499
|
+
end = null;
|
|
500
|
+
mouseup(event);
|
|
501
|
+
};
|
|
502
|
+
return board;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const isSetViewportOperation = (value) => {
|
|
506
|
+
return value.type === 'set_viewport';
|
|
507
|
+
};
|
|
508
|
+
const PlaitOperation = {
|
|
509
|
+
isSetViewportOperation
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
function transformPoints(board, points) {
|
|
513
|
+
const newPoints = points.map(point => {
|
|
514
|
+
return transformPoint(board, point);
|
|
515
|
+
});
|
|
516
|
+
return newPoints;
|
|
517
|
+
}
|
|
518
|
+
function transformPoint(board, point) {
|
|
519
|
+
const { width, height } = board.host.getBoundingClientRect();
|
|
520
|
+
const viewBox = getViewBox(board);
|
|
521
|
+
let x = (point[0] / width) * viewBox.width + viewBox.minX;
|
|
522
|
+
let y = (point[1] / height) * viewBox.height + viewBox.minY;
|
|
523
|
+
const newPoint = [x - board.viewport.offsetX, y - board.viewport.offsetY];
|
|
524
|
+
return newPoint;
|
|
525
|
+
}
|
|
526
|
+
function getViewBox(board) {
|
|
527
|
+
const { width, height } = board.host.getBoundingClientRect();
|
|
528
|
+
const scaleWidth = (board.viewport.zoom - 1) * width;
|
|
529
|
+
const scaleHeight = (board.viewport.zoom - 1) * height;
|
|
530
|
+
const viewBoxWidth = width - scaleWidth;
|
|
531
|
+
const viewBoxHeight = height - scaleHeight;
|
|
532
|
+
const minX = scaleWidth / 2;
|
|
533
|
+
const minY = scaleHeight / 2;
|
|
534
|
+
return { minX, minY: minY, width: viewBoxWidth, height: viewBoxHeight };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
class PlaitElementComponent {
|
|
538
|
+
constructor(renderer2, viewContainerRef) {
|
|
539
|
+
this.renderer2 = renderer2;
|
|
540
|
+
this.viewContainerRef = viewContainerRef;
|
|
541
|
+
this.initialized = false;
|
|
542
|
+
this.selection = null;
|
|
543
|
+
}
|
|
544
|
+
ngOnInit() {
|
|
545
|
+
this.initialize();
|
|
546
|
+
this.transform(true);
|
|
547
|
+
this.drawElement();
|
|
548
|
+
}
|
|
549
|
+
initialize() {
|
|
550
|
+
this.initialized = true;
|
|
551
|
+
this.groupG = createG();
|
|
552
|
+
this.renderer2.setAttribute(this.groupG, 'plait-element-group', this.index.toString());
|
|
553
|
+
this.host.append(this.groupG);
|
|
554
|
+
}
|
|
555
|
+
transform(first = false) {
|
|
556
|
+
if (first && this.viewport.offsetX === 0 && this.viewport.offsetY === 0) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
this.renderer2.setAttribute(this.groupG, 'transform', `translate(${this.viewport.offsetX} ${this.viewport.offsetY})`);
|
|
560
|
+
}
|
|
561
|
+
drawElement() {
|
|
562
|
+
const gArray = this.board.drawElement({ elementInstance: this });
|
|
563
|
+
gArray.forEach(g => {
|
|
564
|
+
this.groupG.appendChild(g);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
ngOnChanges(changes) {
|
|
568
|
+
const viewport = changes['viewport'];
|
|
569
|
+
if (this.initialized && viewport) {
|
|
570
|
+
this.transform();
|
|
571
|
+
}
|
|
572
|
+
if (this.initialized) {
|
|
573
|
+
this.board.redrawElement({ elementInstance: this }, changes);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
ngOnDestroy() {
|
|
577
|
+
this.board.destroyElement();
|
|
578
|
+
this.groupG.remove();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
PlaitElementComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitElementComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
582
|
+
PlaitElementComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: PlaitElementComponent, selector: "plait-element", inputs: { index: "index", element: "element", board: "board", viewport: "viewport", selection: "selection", host: "host" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
583
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitElementComponent, decorators: [{
|
|
584
|
+
type: Component,
|
|
585
|
+
args: [{
|
|
586
|
+
selector: 'plait-element',
|
|
587
|
+
template: '',
|
|
588
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
589
|
+
}]
|
|
590
|
+
}], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }]; }, propDecorators: { index: [{
|
|
591
|
+
type: Input
|
|
592
|
+
}], element: [{
|
|
593
|
+
type: Input
|
|
594
|
+
}], board: [{
|
|
595
|
+
type: Input
|
|
596
|
+
}], viewport: [{
|
|
597
|
+
type: Input
|
|
598
|
+
}], selection: [{
|
|
599
|
+
type: Input
|
|
600
|
+
}], host: [{
|
|
601
|
+
type: Input
|
|
602
|
+
}] } });
|
|
603
|
+
|
|
604
|
+
class PlaitBoardComponent {
|
|
605
|
+
constructor(cdr, renderer2) {
|
|
606
|
+
this.cdr = cdr;
|
|
607
|
+
this.renderer2 = renderer2;
|
|
608
|
+
this.zoom = 100;
|
|
609
|
+
this.hostClass = `plait-board-container`;
|
|
610
|
+
this.destroy$ = new Subject();
|
|
611
|
+
this.plaitValue = [];
|
|
612
|
+
this.plaitPlugins = [];
|
|
613
|
+
this.plaitChange = new EventEmitter();
|
|
614
|
+
this.plaitBoardInitialized = new EventEmitter();
|
|
615
|
+
this.trackBy = (index, element) => {
|
|
616
|
+
return index;
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
get host() {
|
|
620
|
+
return this.svg.nativeElement;
|
|
621
|
+
}
|
|
622
|
+
ngOnInit() {
|
|
623
|
+
const roughSVG = rough.svg(this.host, { options: { roughness: 0, strokeWidth: 1 } });
|
|
624
|
+
HOST_TO_ROUGH_SVG.set(this.host, roughSVG);
|
|
625
|
+
this.initializePlugins();
|
|
626
|
+
this.initializeEvents();
|
|
627
|
+
this.updateViewport();
|
|
628
|
+
BOARD_TO_ON_CHANGE.set(this.board, () => {
|
|
629
|
+
this.cdr.detectChanges();
|
|
630
|
+
const changeEvent = {
|
|
631
|
+
children: this.board.children,
|
|
632
|
+
operations: this.board.operations,
|
|
633
|
+
viewport: this.board.viewport,
|
|
634
|
+
selection: this.board.selection
|
|
635
|
+
};
|
|
636
|
+
this.plaitChange.emit(changeEvent);
|
|
637
|
+
// update viewBox
|
|
638
|
+
if (this.board.operations.some(op => PlaitOperation.isSetViewportOperation(op))) {
|
|
639
|
+
this.updateViewport();
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
ngAfterViewInit() {
|
|
644
|
+
this.plaitBoardInitialized.emit(this.board);
|
|
645
|
+
}
|
|
646
|
+
initializePlugins() {
|
|
647
|
+
let board = withSelection(withBoard(createBoard(this.host, this.plaitValue)));
|
|
648
|
+
this.plaitPlugins.forEach(plugin => {
|
|
649
|
+
board = plugin(board);
|
|
650
|
+
});
|
|
651
|
+
this.board = board;
|
|
652
|
+
if (this.plaitViewport) {
|
|
653
|
+
this.board.viewport = this.plaitViewport;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
initializeEvents() {
|
|
657
|
+
fromEvent(this.host, 'mousedown')
|
|
658
|
+
.pipe(takeUntil(this.destroy$))
|
|
659
|
+
.subscribe((event) => {
|
|
660
|
+
this.board.mousedown(event);
|
|
661
|
+
});
|
|
662
|
+
fromEvent(this.host, 'mousemove')
|
|
663
|
+
.pipe(takeUntil(this.destroy$))
|
|
664
|
+
.subscribe((event) => {
|
|
665
|
+
this.board.mousemove(event);
|
|
666
|
+
});
|
|
667
|
+
fromEvent(document, 'mouseup')
|
|
668
|
+
.pipe(takeUntil(this.destroy$))
|
|
669
|
+
.subscribe((event) => {
|
|
670
|
+
this.board.mouseup(event);
|
|
671
|
+
});
|
|
672
|
+
fromEvent(this.host, 'dblclick')
|
|
673
|
+
.pipe(takeUntil(this.destroy$))
|
|
674
|
+
.subscribe((event) => {
|
|
675
|
+
this.board.dblclick(event);
|
|
676
|
+
});
|
|
677
|
+
fromEvent(this.host, 'wheel')
|
|
678
|
+
.pipe(takeUntil(this.destroy$))
|
|
679
|
+
.subscribe((event) => {
|
|
680
|
+
event.preventDefault();
|
|
681
|
+
const viewport = this.board.viewport;
|
|
682
|
+
Transforms.setViewport(this.board, {
|
|
683
|
+
...viewport,
|
|
684
|
+
offsetX: viewport?.offsetX - event.deltaX,
|
|
685
|
+
offsetY: viewport?.offsetY - event.deltaY
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
fromEvent(document, 'keydown')
|
|
689
|
+
.pipe(takeUntil(this.destroy$), filter(() => {
|
|
690
|
+
return !IS_TEXT_EDITABLE.get(this.board);
|
|
691
|
+
}))
|
|
692
|
+
.subscribe((event) => {
|
|
693
|
+
this.board?.keydown(event);
|
|
694
|
+
});
|
|
695
|
+
fromEvent(document, 'keyup')
|
|
696
|
+
.pipe(takeUntil(this.destroy$))
|
|
697
|
+
.subscribe((event) => {
|
|
698
|
+
this.board?.keyup(event);
|
|
699
|
+
});
|
|
700
|
+
window.onresize = () => {
|
|
701
|
+
const viewBoxModel = getViewBox(this.board);
|
|
702
|
+
const viewBoxValues = this.host.getAttribute('viewBox')?.split(',');
|
|
703
|
+
this.renderer2.setAttribute(this.host, 'viewBox', `${viewBoxValues[0].trim()}, ${viewBoxValues[1].trim()}, ${viewBoxModel.width}, ${viewBoxModel.height}`);
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
updateViewport() {
|
|
707
|
+
this.zoom = Math.floor(this.board.viewport.zoom * 100);
|
|
708
|
+
const viewBox = getViewBox(this.board);
|
|
709
|
+
this.renderer2.setAttribute(this.host, 'viewBox', `${viewBox.minX}, ${viewBox.minY}, ${viewBox.width}, ${viewBox.height}`);
|
|
710
|
+
}
|
|
711
|
+
// 放大
|
|
712
|
+
zoomIn(event) {
|
|
713
|
+
const viewport = this.board?.viewport;
|
|
714
|
+
Transforms.setViewport(this.board, {
|
|
715
|
+
...viewport,
|
|
716
|
+
zoom: viewport.zoom + 0.1
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
// 缩小
|
|
720
|
+
zoomOut(event) {
|
|
721
|
+
const viewport = this.board?.viewport;
|
|
722
|
+
Transforms.setViewport(this.board, {
|
|
723
|
+
...viewport,
|
|
724
|
+
zoom: viewport.zoom - 0.1
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
resetZoom(event) {
|
|
728
|
+
const viewport = this.board?.viewport;
|
|
729
|
+
Transforms.setViewport(this.board, {
|
|
730
|
+
...viewport,
|
|
731
|
+
zoom: 1
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
ngOnDestroy() {
|
|
735
|
+
this.destroy$.next();
|
|
736
|
+
this.destroy$.complete();
|
|
737
|
+
HOST_TO_ROUGH_SVG.delete(this.host);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
PlaitBoardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitBoardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
|
|
741
|
+
PlaitBoardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: PlaitBoardComponent, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass" } }, viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }], ngImport: i0, template: `
|
|
742
|
+
<svg #svg width="100%" height="100%"></svg>
|
|
743
|
+
<div class="plait-toolbar island zoom-toolbar">
|
|
744
|
+
<button class="item" (mousedown)="zoomOut($event)">-</button>
|
|
745
|
+
<button class="item zoom-value" (mousedown)="resetZoom($event)">{{ zoom }}%</button>
|
|
746
|
+
<button class="item" (mousedown)="zoomIn($event)">+</button>
|
|
747
|
+
</div>
|
|
748
|
+
<plait-element
|
|
749
|
+
*ngFor="let item of board.children; let index = index; trackBy: trackBy"
|
|
750
|
+
[index]="index"
|
|
751
|
+
[element]="item"
|
|
752
|
+
[board]="board"
|
|
753
|
+
[viewport]="board.viewport"
|
|
754
|
+
[selection]="board.selection"
|
|
755
|
+
[host]="host"
|
|
756
|
+
></plait-element>
|
|
757
|
+
<ng-content></ng-content>
|
|
758
|
+
`, isInline: true, components: [{ type: PlaitElementComponent, selector: "plait-element", inputs: ["index", "element", "board", "viewport", "selection", "host"] }], directives: [{ type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
759
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitBoardComponent, decorators: [{
|
|
760
|
+
type: Component,
|
|
761
|
+
args: [{
|
|
762
|
+
selector: 'plait-board',
|
|
763
|
+
template: `
|
|
764
|
+
<svg #svg width="100%" height="100%"></svg>
|
|
765
|
+
<div class="plait-toolbar island zoom-toolbar">
|
|
766
|
+
<button class="item" (mousedown)="zoomOut($event)">-</button>
|
|
767
|
+
<button class="item zoom-value" (mousedown)="resetZoom($event)">{{ zoom }}%</button>
|
|
768
|
+
<button class="item" (mousedown)="zoomIn($event)">+</button>
|
|
769
|
+
</div>
|
|
770
|
+
<plait-element
|
|
771
|
+
*ngFor="let item of board.children; let index = index; trackBy: trackBy"
|
|
772
|
+
[index]="index"
|
|
773
|
+
[element]="item"
|
|
774
|
+
[board]="board"
|
|
775
|
+
[viewport]="board.viewport"
|
|
776
|
+
[selection]="board.selection"
|
|
777
|
+
[host]="host"
|
|
778
|
+
></plait-element>
|
|
779
|
+
<ng-content></ng-content>
|
|
780
|
+
`,
|
|
781
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
782
|
+
}]
|
|
783
|
+
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }]; }, propDecorators: { hostClass: [{
|
|
784
|
+
type: HostBinding,
|
|
785
|
+
args: ['class']
|
|
786
|
+
}], svg: [{
|
|
787
|
+
type: ViewChild,
|
|
788
|
+
args: ['svg', { static: true }]
|
|
789
|
+
}], plaitValue: [{
|
|
790
|
+
type: Input
|
|
791
|
+
}], plaitViewport: [{
|
|
792
|
+
type: Input
|
|
793
|
+
}], plaitPlugins: [{
|
|
794
|
+
type: Input
|
|
795
|
+
}], plaitChange: [{
|
|
796
|
+
type: Output
|
|
797
|
+
}], plaitBoardInitialized: [{
|
|
798
|
+
type: Output
|
|
799
|
+
}] } });
|
|
800
|
+
|
|
801
|
+
class PlaitModule {
|
|
802
|
+
}
|
|
803
|
+
PlaitModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
804
|
+
PlaitModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, declarations: [PlaitBoardComponent, PlaitElementComponent], imports: [BrowserModule], exports: [PlaitBoardComponent, PlaitElementComponent] });
|
|
805
|
+
PlaitModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, imports: [[BrowserModule]] });
|
|
806
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, decorators: [{
|
|
807
|
+
type: NgModule,
|
|
808
|
+
args: [{
|
|
809
|
+
declarations: [PlaitBoardComponent, PlaitElementComponent],
|
|
810
|
+
imports: [BrowserModule],
|
|
811
|
+
exports: [PlaitBoardComponent, PlaitElementComponent]
|
|
812
|
+
}]
|
|
813
|
+
}] });
|
|
814
|
+
|
|
815
|
+
const IS_IOS = typeof navigator !== 'undefined' &&
|
|
816
|
+
typeof window !== 'undefined' &&
|
|
817
|
+
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
|
|
818
|
+
!window.MSStream;
|
|
819
|
+
const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
|
|
820
|
+
const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
|
|
821
|
+
const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
|
|
822
|
+
// "modern" Edge was released at 79.x
|
|
823
|
+
const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
|
|
824
|
+
const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
|
|
825
|
+
// Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
|
|
826
|
+
const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Hotkey mappings for each platform.
|
|
830
|
+
*/
|
|
831
|
+
const HOTKEYS = {
|
|
832
|
+
bold: 'mod+b',
|
|
833
|
+
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
834
|
+
moveBackward: 'left',
|
|
835
|
+
moveForward: 'right',
|
|
836
|
+
moveUp: 'up',
|
|
837
|
+
moveDown: 'down',
|
|
838
|
+
moveWordBackward: 'ctrl+left',
|
|
839
|
+
moveWordForward: 'ctrl+right',
|
|
840
|
+
deleteBackward: 'shift?+backspace',
|
|
841
|
+
deleteForward: 'shift?+delete',
|
|
842
|
+
extendBackward: 'shift+left',
|
|
843
|
+
extendForward: 'shift+right',
|
|
844
|
+
italic: 'mod+i',
|
|
845
|
+
splitBlock: 'shift?+enter',
|
|
846
|
+
undo: 'mod+z'
|
|
847
|
+
};
|
|
848
|
+
const APPLE_HOTKEYS = {
|
|
849
|
+
moveLineBackward: 'opt+up',
|
|
850
|
+
moveLineForward: 'opt+down',
|
|
851
|
+
moveWordBackward: 'opt+left',
|
|
852
|
+
moveWordForward: 'opt+right',
|
|
853
|
+
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
854
|
+
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
855
|
+
deleteLineBackward: 'cmd+shift?+backspace',
|
|
856
|
+
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
857
|
+
deleteWordBackward: 'opt+shift?+backspace',
|
|
858
|
+
deleteWordForward: 'opt+shift?+delete',
|
|
859
|
+
extendLineBackward: 'opt+shift+up',
|
|
860
|
+
extendLineForward: 'opt+shift+down',
|
|
861
|
+
redo: 'cmd+shift+z',
|
|
862
|
+
transposeCharacter: 'ctrl+t'
|
|
863
|
+
};
|
|
864
|
+
const WINDOWS_HOTKEYS = {
|
|
865
|
+
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
866
|
+
deleteWordForward: 'ctrl+shift?+delete',
|
|
867
|
+
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
868
|
+
};
|
|
869
|
+
/**
|
|
870
|
+
* Create a platform-aware hotkey checker.
|
|
871
|
+
*/
|
|
872
|
+
const create = (key) => {
|
|
873
|
+
const generic = HOTKEYS[key];
|
|
874
|
+
const apple = APPLE_HOTKEYS[key];
|
|
875
|
+
const windows = WINDOWS_HOTKEYS[key];
|
|
876
|
+
const isGeneric = generic && isKeyHotkey(generic);
|
|
877
|
+
const isApple = apple && isKeyHotkey(apple);
|
|
878
|
+
const isWindows = windows && isKeyHotkey(windows);
|
|
879
|
+
return (event) => {
|
|
880
|
+
if (isGeneric && isGeneric(event)) {
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
if (IS_APPLE && isApple && isApple(event)) {
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
if (!IS_APPLE && isWindows && isWindows(event)) {
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
return false;
|
|
890
|
+
};
|
|
891
|
+
};
|
|
892
|
+
/**
|
|
893
|
+
* Hotkeys.
|
|
894
|
+
*/
|
|
895
|
+
const hotkeys = {
|
|
896
|
+
isBold: create('bold'),
|
|
897
|
+
isCompose: create('compose'),
|
|
898
|
+
isMoveBackward: create('moveBackward'),
|
|
899
|
+
isMoveForward: create('moveForward'),
|
|
900
|
+
isMoveUp: create('moveUp'),
|
|
901
|
+
isMoveDown: create('moveDown'),
|
|
902
|
+
isDeleteBackward: create('deleteBackward'),
|
|
903
|
+
isDeleteForward: create('deleteForward'),
|
|
904
|
+
isDeleteLineBackward: create('deleteLineBackward'),
|
|
905
|
+
isDeleteLineForward: create('deleteLineForward'),
|
|
906
|
+
isDeleteWordBackward: create('deleteWordBackward'),
|
|
907
|
+
isDeleteWordForward: create('deleteWordForward'),
|
|
908
|
+
isExtendBackward: create('extendBackward'),
|
|
909
|
+
isExtendForward: create('extendForward'),
|
|
910
|
+
isExtendLineBackward: create('extendLineBackward'),
|
|
911
|
+
isExtendLineForward: create('extendLineForward'),
|
|
912
|
+
isItalic: create('italic'),
|
|
913
|
+
isMoveLineBackward: create('moveLineBackward'),
|
|
914
|
+
isMoveLineForward: create('moveLineForward'),
|
|
915
|
+
isMoveWordBackward: create('moveWordBackward'),
|
|
916
|
+
isMoveWordForward: create('moveWordForward'),
|
|
917
|
+
isRedo: create('redo'),
|
|
918
|
+
isSplitBlock: create('splitBlock'),
|
|
919
|
+
isTransposeCharacter: create('transposeCharacter'),
|
|
920
|
+
isUndo: create('undo')
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
function idCreator(length = 5) {
|
|
924
|
+
// remove numeral
|
|
925
|
+
const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
|
|
926
|
+
const maxPosition = $chars.length;
|
|
927
|
+
let key = '';
|
|
928
|
+
for (let i = 0; i < length; i++) {
|
|
929
|
+
key += $chars.charAt(Math.floor(Math.random() * maxPosition));
|
|
930
|
+
}
|
|
931
|
+
return key;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// https://stackoverflow.com/a/6853926/232122
|
|
935
|
+
function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
|
|
936
|
+
const A = x - x1;
|
|
937
|
+
const B = y - y1;
|
|
938
|
+
const C = x2 - x1;
|
|
939
|
+
const D = y2 - y1;
|
|
940
|
+
const dot = A * C + B * D;
|
|
941
|
+
const lenSquare = C * C + D * D;
|
|
942
|
+
let param = -1;
|
|
943
|
+
if (lenSquare !== 0) {
|
|
944
|
+
// in case of 0 length line
|
|
945
|
+
param = dot / lenSquare;
|
|
946
|
+
}
|
|
947
|
+
let xx, yy;
|
|
948
|
+
if (param < 0) {
|
|
949
|
+
xx = x1;
|
|
950
|
+
yy = y1;
|
|
951
|
+
}
|
|
952
|
+
else if (param > 1) {
|
|
953
|
+
xx = x2;
|
|
954
|
+
yy = y2;
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
xx = x1 + param * C;
|
|
958
|
+
yy = y1 + param * D;
|
|
959
|
+
}
|
|
960
|
+
const dx = x - xx;
|
|
961
|
+
const dy = y - yy;
|
|
962
|
+
return Math.hypot(dx, dy);
|
|
963
|
+
}
|
|
964
|
+
function rotate(x1, y1, x2, y2, angle) {
|
|
965
|
+
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
|
966
|
+
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
|
967
|
+
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
|
968
|
+
return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Extendable Custom Types Interface
|
|
973
|
+
*/
|
|
974
|
+
|
|
975
|
+
/*
|
|
976
|
+
* Public API Surface of plait
|
|
977
|
+
*/
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Generated bundle index. Do not edit.
|
|
981
|
+
*/
|
|
982
|
+
|
|
983
|
+
export { BOARD_TO_ON_CHANGE, BaseCursorStatus, FLUSHING, HOST_TO_ROUGH_SVG, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_SAFARI, IS_TEXT_EDITABLE, NS, Path, PlaitBoardComponent, PlaitElementComponent, PlaitModule, PlaitNode, PlaitOperation, Transforms, Viewport, createG, createSVG, createText, distanceBetweenPointAndSegment, getViewBox, hotkeys, idCreator, isNullOrUndefined, isSetViewportOperation, rotate, toPoint, toRectangleClient, transformPoint, transformPoints };
|
|
984
|
+
//# sourceMappingURL=plait-core.mjs.map
|