1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | import * as go from '../release/go-module.js';
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 | export class DrawCommandHandler extends go.CommandHandler {
|
35 | constructor() {
|
36 | super(...arguments);
|
37 | this._arrowKeyBehavior = 'move';
|
38 | this._pasteOffset = new go.Point(10, 10);
|
39 | this._lastPasteOffset = new go.Point(0, 0);
|
40 | }
|
41 | |
42 |
|
43 |
|
44 |
|
45 |
|
46 | get arrowKeyBehavior() { return this._arrowKeyBehavior; }
|
47 | set arrowKeyBehavior(val) {
|
48 | if (val !== 'move' && val !== 'select' && val !== 'scroll' && val !== 'none') {
|
49 | throw new Error('DrawCommandHandler.arrowKeyBehavior must be either "move", "select", "scroll", or "none", not: ' + val);
|
50 | }
|
51 | this._arrowKeyBehavior = val;
|
52 | }
|
53 | |
54 |
|
55 |
|
56 | get pasteOffset() { return this._pasteOffset; }
|
57 | set pasteOffset(val) {
|
58 | if (!(val instanceof go.Point))
|
59 | throw new Error('DrawCommandHandler.pasteOffset must be a Point, not: ' + val);
|
60 | this._pasteOffset.set(val);
|
61 | }
|
62 | |
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | canAlignSelection() {
|
71 | const diagram = this.diagram;
|
72 | if (diagram.isReadOnly || diagram.isModelReadOnly)
|
73 | return false;
|
74 | if (diagram.selection.count < 2)
|
75 | return false;
|
76 | return true;
|
77 | }
|
78 | |
79 |
|
80 |
|
81 | alignLeft() {
|
82 | const diagram = this.diagram;
|
83 | diagram.startTransaction('aligning left');
|
84 | let minPosition = Infinity;
|
85 | diagram.selection.each((current) => {
|
86 | if (current instanceof go.Link)
|
87 | return;
|
88 | minPosition = Math.min(current.position.x, minPosition);
|
89 | });
|
90 | diagram.selection.each((current) => {
|
91 | if (current instanceof go.Link)
|
92 | return;
|
93 | current.move(new go.Point(minPosition, current.position.y));
|
94 | });
|
95 | diagram.commitTransaction('aligning left');
|
96 | }
|
97 | |
98 |
|
99 |
|
100 | alignRight() {
|
101 | const diagram = this.diagram;
|
102 | diagram.startTransaction('aligning right');
|
103 | let maxPosition = -Infinity;
|
104 | diagram.selection.each((current) => {
|
105 | if (current instanceof go.Link)
|
106 | return;
|
107 | const rightSideLoc = current.actualBounds.x + current.actualBounds.width;
|
108 | maxPosition = Math.max(rightSideLoc, maxPosition);
|
109 | });
|
110 | diagram.selection.each((current) => {
|
111 | if (current instanceof go.Link)
|
112 | return;
|
113 | current.move(new go.Point(maxPosition - current.actualBounds.width, current.position.y));
|
114 | });
|
115 | diagram.commitTransaction('aligning right');
|
116 | }
|
117 | |
118 |
|
119 |
|
120 | alignTop() {
|
121 | const diagram = this.diagram;
|
122 | diagram.startTransaction('alignTop');
|
123 | let minPosition = Infinity;
|
124 | diagram.selection.each((current) => {
|
125 | if (current instanceof go.Link)
|
126 | return;
|
127 | minPosition = Math.min(current.position.y, minPosition);
|
128 | });
|
129 | diagram.selection.each((current) => {
|
130 | if (current instanceof go.Link)
|
131 | return;
|
132 | current.move(new go.Point(current.position.x, minPosition));
|
133 | });
|
134 | diagram.commitTransaction('alignTop');
|
135 | }
|
136 | |
137 |
|
138 |
|
139 | alignBottom() {
|
140 | const diagram = this.diagram;
|
141 | diagram.startTransaction('aligning bottom');
|
142 | let maxPosition = -Infinity;
|
143 | diagram.selection.each((current) => {
|
144 | if (current instanceof go.Link)
|
145 | return;
|
146 | const bottomSideLoc = current.actualBounds.y + current.actualBounds.height;
|
147 | maxPosition = Math.max(bottomSideLoc, maxPosition);
|
148 | });
|
149 | diagram.selection.each((current) => {
|
150 | if (current instanceof go.Link)
|
151 | return;
|
152 | current.move(new go.Point(current.actualBounds.x, maxPosition - current.actualBounds.height));
|
153 | });
|
154 | diagram.commitTransaction('aligning bottom');
|
155 | }
|
156 | |
157 |
|
158 |
|
159 | alignCenterX() {
|
160 | const diagram = this.diagram;
|
161 | const firstSelection = diagram.selection.first();
|
162 | if (!firstSelection)
|
163 | return;
|
164 | diagram.startTransaction('aligning Center X');
|
165 | const centerX = firstSelection.actualBounds.x + firstSelection.actualBounds.width / 2;
|
166 | diagram.selection.each((current) => {
|
167 | if (current instanceof go.Link)
|
168 | return;
|
169 | current.move(new go.Point(centerX - current.actualBounds.width / 2, current.actualBounds.y));
|
170 | });
|
171 | diagram.commitTransaction('aligning Center X');
|
172 | }
|
173 | |
174 |
|
175 |
|
176 | alignCenterY() {
|
177 | const diagram = this.diagram;
|
178 | const firstSelection = diagram.selection.first();
|
179 | if (!firstSelection)
|
180 | return;
|
181 | diagram.startTransaction('aligning Center Y');
|
182 | const centerY = firstSelection.actualBounds.y + firstSelection.actualBounds.height / 2;
|
183 | diagram.selection.each((current) => {
|
184 | if (current instanceof go.Link)
|
185 | return;
|
186 | current.move(new go.Point(current.actualBounds.x, centerY - current.actualBounds.height / 2));
|
187 | });
|
188 | diagram.commitTransaction('aligning Center Y');
|
189 | }
|
190 | |
191 |
|
192 |
|
193 |
|
194 | alignColumn(distance) {
|
195 | const diagram = this.diagram;
|
196 | diagram.startTransaction('align Column');
|
197 | if (distance === undefined)
|
198 | distance = 0;
|
199 | distance = parseFloat(distance.toString());
|
200 | const selectedParts = new Array();
|
201 | diagram.selection.each((current) => {
|
202 | if (current instanceof go.Link)
|
203 | return;
|
204 | selectedParts.push(current);
|
205 | });
|
206 | for (let i = 0; i < selectedParts.length - 1; i++) {
|
207 | const current = selectedParts[i];
|
208 |
|
209 | const curBottomSideLoc = current.actualBounds.y + current.actualBounds.height + distance;
|
210 | const next = selectedParts[i + 1];
|
211 | next.move(new go.Point(current.actualBounds.x, curBottomSideLoc));
|
212 | }
|
213 | diagram.commitTransaction('align Column');
|
214 | }
|
215 | |
216 |
|
217 |
|
218 |
|
219 | alignRow(distance) {
|
220 | if (distance === undefined)
|
221 | distance = 0;
|
222 | distance = parseFloat(distance.toString());
|
223 | const diagram = this.diagram;
|
224 | diagram.startTransaction('align Row');
|
225 | const selectedParts = new Array();
|
226 | diagram.selection.each((current) => {
|
227 | if (current instanceof go.Link)
|
228 | return;
|
229 | selectedParts.push(current);
|
230 | });
|
231 | for (let i = 0; i < selectedParts.length - 1; i++) {
|
232 | const current = selectedParts[i];
|
233 |
|
234 | const curRightSideLoc = current.actualBounds.x + current.actualBounds.width + distance;
|
235 | const next = selectedParts[i + 1];
|
236 | next.move(new go.Point(curRightSideLoc, current.actualBounds.y));
|
237 | }
|
238 | diagram.commitTransaction('align Row');
|
239 | }
|
240 | |
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 | canRotate() {
|
248 | const diagram = this.diagram;
|
249 | if (diagram.isReadOnly || diagram.isModelReadOnly)
|
250 | return false;
|
251 | if (diagram.selection.count < 1)
|
252 | return false;
|
253 | return true;
|
254 | }
|
255 | |
256 |
|
257 |
|
258 |
|
259 |
|
260 | rotate(angle) {
|
261 | if (angle === undefined)
|
262 | angle = 90;
|
263 | const diagram = this.diagram;
|
264 | diagram.startTransaction('rotate ' + angle.toString());
|
265 | diagram.selection.each((current) => {
|
266 | if (current instanceof go.Link || current instanceof go.Group)
|
267 | return;
|
268 | current.angle += angle;
|
269 | });
|
270 | diagram.commitTransaction('rotate ' + angle.toString());
|
271 | }
|
272 | |
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 | pullToFront() {
|
279 | const diagram = this.diagram;
|
280 | diagram.startTransaction("pullToFront");
|
281 |
|
282 | const layers = new go.Map();
|
283 | diagram.selection.each(function (part) {
|
284 | if (part.layer !== null)
|
285 | layers.set(part.layer, 0);
|
286 | });
|
287 |
|
288 | layers.iteratorKeys.each(function (layer) {
|
289 | let max = 0;
|
290 | layer.parts.each(function (part) {
|
291 | if (part.isSelected)
|
292 | return;
|
293 | const z = part.zOrder;
|
294 | if (isNaN(z)) {
|
295 | part.zOrder = 0;
|
296 | }
|
297 | else {
|
298 | max = Math.max(max, z);
|
299 | }
|
300 | });
|
301 | layers.set(layer, max);
|
302 | });
|
303 |
|
304 | diagram.selection.each(function (part) {
|
305 | const z = layers.get(part.layer) || 0;
|
306 | DrawCommandHandler._assignZOrder(part, z + 1);
|
307 | });
|
308 | diagram.commitTransaction("pullToFront");
|
309 | }
|
310 | |
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 | pushToBack() {
|
317 | const diagram = this.diagram;
|
318 | diagram.startTransaction("pushToBack");
|
319 |
|
320 | const layers = new go.Map();
|
321 | diagram.selection.each(function (part) {
|
322 | if (part.layer !== null)
|
323 | layers.set(part.layer, 0);
|
324 | });
|
325 |
|
326 | layers.iteratorKeys.each(function (layer) {
|
327 | let min = 0;
|
328 | layer.parts.each(function (part) {
|
329 | if (part.isSelected)
|
330 | return;
|
331 | const z = part.zOrder;
|
332 | if (isNaN(z)) {
|
333 | part.zOrder = 0;
|
334 | }
|
335 | else {
|
336 | min = Math.min(min, z);
|
337 | }
|
338 | });
|
339 | layers.set(layer, min);
|
340 | });
|
341 |
|
342 | diagram.selection.each(function (part) {
|
343 | const z = layers.get(part.layer) || 0;
|
344 | DrawCommandHandler._assignZOrder(part,
|
345 |
|
346 | z - 1 - DrawCommandHandler._findGroupDepth(part));
|
347 | });
|
348 | diagram.commitTransaction("pushToBack");
|
349 | }
|
350 | static _assignZOrder(part, z, root) {
|
351 | if (root === undefined)
|
352 | root = part;
|
353 | if (part.layer === root.layer)
|
354 | part.zOrder = z;
|
355 | if (part instanceof go.Group) {
|
356 | part.memberParts.each(function (m) {
|
357 | DrawCommandHandler._assignZOrder(m, z + 1, root);
|
358 | });
|
359 | }
|
360 | }
|
361 | static _findGroupDepth(part) {
|
362 | if (part instanceof go.Group) {
|
363 | let d = 0;
|
364 | part.memberParts.each(function (m) {
|
365 | d = Math.max(d, DrawCommandHandler._findGroupDepth(m));
|
366 | });
|
367 | return d + 1;
|
368 | }
|
369 | else {
|
370 | return 0;
|
371 | }
|
372 | }
|
373 | |
374 |
|
375 |
|
376 |
|
377 |
|
378 | doKeyDown() {
|
379 | const diagram = this.diagram;
|
380 | const e = diagram.lastInput;
|
381 |
|
382 | if (e.key === 'Up' || e.key === 'Down' || e.key === 'Left' || e.key === 'Right') {
|
383 | const behavior = this.arrowKeyBehavior;
|
384 | if (behavior === 'none') {
|
385 |
|
386 | return;
|
387 | }
|
388 | else if (behavior === 'select') {
|
389 | this._arrowKeySelect();
|
390 | return;
|
391 | }
|
392 | else if (behavior === 'move') {
|
393 | this._arrowKeyMove();
|
394 | return;
|
395 | }
|
396 |
|
397 | }
|
398 |
|
399 | super.doKeyDown();
|
400 | }
|
401 | |
402 |
|
403 |
|
404 | _getAllParts() {
|
405 | const allParts = new Array();
|
406 | this.diagram.nodes.each((node) => { allParts.push(node); });
|
407 | this.diagram.parts.each((part) => { allParts.push(part); });
|
408 |
|
409 | return allParts;
|
410 | }
|
411 | |
412 |
|
413 |
|
414 | _arrowKeyMove() {
|
415 | const diagram = this.diagram;
|
416 | const e = diagram.lastInput;
|
417 |
|
418 | let vdistance = 0;
|
419 | let hdistance = 0;
|
420 |
|
421 | if (e.control || e.meta) {
|
422 | vdistance = 1;
|
423 | hdistance = 1;
|
424 | }
|
425 | else if (diagram.grid !== null) {
|
426 | const cellsize = diagram.grid.gridCellSize;
|
427 | hdistance = cellsize.width;
|
428 | vdistance = cellsize.height;
|
429 | }
|
430 | diagram.startTransaction('arrowKeyMove');
|
431 | diagram.selection.each((part) => {
|
432 | if (e.key === 'Up') {
|
433 | part.move(new go.Point(part.actualBounds.x, part.actualBounds.y - vdistance));
|
434 | }
|
435 | else if (e.key === 'Down') {
|
436 | part.move(new go.Point(part.actualBounds.x, part.actualBounds.y + vdistance));
|
437 | }
|
438 | else if (e.key === 'Left') {
|
439 | part.move(new go.Point(part.actualBounds.x - hdistance, part.actualBounds.y));
|
440 | }
|
441 | else if (e.key === 'Right') {
|
442 | part.move(new go.Point(part.actualBounds.x + hdistance, part.actualBounds.y));
|
443 | }
|
444 | });
|
445 | diagram.commitTransaction('arrowKeyMove');
|
446 | }
|
447 | |
448 |
|
449 |
|
450 | _arrowKeySelect() {
|
451 | const diagram = this.diagram;
|
452 | const e = diagram.lastInput;
|
453 |
|
454 |
|
455 |
|
456 | let nextPart = null;
|
457 | if (e.key === 'Up') {
|
458 | nextPart = this._findNearestPartTowards(270);
|
459 | }
|
460 | else if (e.key === 'Down') {
|
461 | nextPart = this._findNearestPartTowards(90);
|
462 | }
|
463 | else if (e.key === 'Left') {
|
464 | nextPart = this._findNearestPartTowards(180);
|
465 | }
|
466 | else if (e.key === 'Right') {
|
467 | nextPart = this._findNearestPartTowards(0);
|
468 | }
|
469 | if (nextPart !== null) {
|
470 | if (e.shift) {
|
471 | nextPart.isSelected = true;
|
472 | }
|
473 | else if (e.control || e.meta) {
|
474 | nextPart.isSelected = !nextPart.isSelected;
|
475 | }
|
476 | else {
|
477 | diagram.select(nextPart);
|
478 | }
|
479 | }
|
480 | }
|
481 | |
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 | _findNearestPartTowards(dir) {
|
488 | const originalPart = this.diagram.selection.first();
|
489 | if (originalPart === null)
|
490 | return null;
|
491 | const originalPoint = originalPart.actualBounds.center;
|
492 | const allParts = this._getAllParts();
|
493 | let closestDistance = Infinity;
|
494 | let closest = originalPart;
|
495 | for (let i = 0; i < allParts.length; i++) {
|
496 | const nextPart = allParts[i];
|
497 | if (nextPart === originalPart)
|
498 | continue;
|
499 | const nextPoint = nextPart.actualBounds.center;
|
500 | const angle = originalPoint.directionPoint(nextPoint);
|
501 | const anglediff = this._angleCloseness(angle, dir);
|
502 | if (anglediff <= 45) {
|
503 | let distance = originalPoint.distanceSquaredPoint(nextPoint);
|
504 | distance *= 1 + Math.sin(anglediff * Math.PI / 180);
|
505 | if (distance < closestDistance) {
|
506 | closestDistance = distance;
|
507 | closest = nextPart;
|
508 | }
|
509 | }
|
510 | }
|
511 | return closest;
|
512 | }
|
513 | _angleCloseness(a, dir) {
|
514 | return Math.min(Math.abs(dir - a), Math.min(Math.abs(dir + 360 - a), Math.abs(dir - 360 - a)));
|
515 | }
|
516 | |
517 |
|
518 |
|
519 |
|
520 | copyToClipboard(coll) {
|
521 | super.copyToClipboard(coll);
|
522 | this._lastPasteOffset.set(this.pasteOffset);
|
523 | }
|
524 | |
525 |
|
526 |
|
527 |
|
528 | pasteFromClipboard() {
|
529 | const coll = super.pasteFromClipboard();
|
530 | this.diagram.moveParts(coll, this._lastPasteOffset, false);
|
531 | this._lastPasteOffset.add(this.pasteOffset);
|
532 | return coll;
|
533 | }
|
534 | }
|