@teachinglab/omd 0.7.4 → 0.7.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/canvas/tools/SelectTool.js +371 -101
- package/package.json +1 -1
|
@@ -41,11 +41,13 @@ export class SelectTool extends Tool {
|
|
|
41
41
|
/** @private - OMD dragging state */
|
|
42
42
|
this.isDraggingOMD = false;
|
|
43
43
|
this.draggedOMDElement = null;
|
|
44
|
+
this.selectedOMDElements = new Set();
|
|
44
45
|
|
|
45
46
|
/** @private - Stroke dragging state */
|
|
46
47
|
this.isDraggingStrokes = false;
|
|
47
48
|
this.dragStartPoint = null;
|
|
48
49
|
this.potentialDeselect = null;
|
|
50
|
+
this.hasSeparatedForDrag = false;
|
|
49
51
|
|
|
50
52
|
// Initialize resize handle manager for OMD visuals
|
|
51
53
|
this.resizeHandleManager = new ResizeHandleManager(canvas);
|
|
@@ -89,6 +91,7 @@ export class SelectTool extends Tool {
|
|
|
89
91
|
if (isSelected) {
|
|
90
92
|
// Already selected - prepare for drag, but don't deselect yet
|
|
91
93
|
this.isDraggingStrokes = true;
|
|
94
|
+
this.hasSeparatedForDrag = false;
|
|
92
95
|
this.dragStartPoint = { x: event.x, y: event.y };
|
|
93
96
|
this.potentialDeselect = segmentSelection;
|
|
94
97
|
|
|
@@ -98,12 +101,16 @@ export class SelectTool extends Tool {
|
|
|
98
101
|
}
|
|
99
102
|
return;
|
|
100
103
|
} else {
|
|
101
|
-
// Clicking on a stroke segment
|
|
102
|
-
|
|
104
|
+
// Clicking on a stroke segment
|
|
105
|
+
if (!event.shiftKey) {
|
|
106
|
+
this.resizeHandleManager.clearSelection();
|
|
107
|
+
this.selectedOMDElements.clear();
|
|
108
|
+
}
|
|
103
109
|
this._handleSegmentClick(segmentSelection, event.shiftKey);
|
|
104
110
|
|
|
105
111
|
// Prepare for drag immediately after selection
|
|
106
112
|
this.isDraggingStrokes = true;
|
|
113
|
+
this.hasSeparatedForDrag = false;
|
|
107
114
|
this.dragStartPoint = { x: event.x, y: event.y };
|
|
108
115
|
|
|
109
116
|
if (this.canvas.eventManager) {
|
|
@@ -111,14 +118,33 @@ export class SelectTool extends Tool {
|
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
} else if (omdElement) {
|
|
114
|
-
// Clicking on an OMD visual
|
|
121
|
+
// Clicking on an OMD visual
|
|
122
|
+
|
|
123
|
+
// Check if already selected
|
|
124
|
+
if (this.selectedOMDElements.has(omdElement)) {
|
|
125
|
+
// Already selected - prepare for drag
|
|
126
|
+
this.isDraggingOMD = true;
|
|
127
|
+
this.draggedOMDElement = omdElement; // Primary drag target
|
|
128
|
+
this.startPoint = { x: event.x, y: event.y };
|
|
129
|
+
|
|
130
|
+
if (this.canvas.eventManager) {
|
|
131
|
+
this.canvas.eventManager.isDrawing = true;
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// New selection
|
|
115
137
|
if (!event.shiftKey) {
|
|
116
138
|
this.selectedSegments.clear();
|
|
117
139
|
this._clearSelectionVisuals();
|
|
140
|
+
this.selectedOMDElements.clear();
|
|
141
|
+
this.resizeHandleManager.clearSelection();
|
|
118
142
|
}
|
|
143
|
+
|
|
144
|
+
this.selectedOMDElements.add(omdElement);
|
|
119
145
|
this.resizeHandleManager.selectElement(omdElement);
|
|
120
146
|
|
|
121
|
-
//
|
|
147
|
+
// Start tracking for potential drag operation
|
|
122
148
|
this.isDraggingOMD = true;
|
|
123
149
|
this.draggedOMDElement = omdElement;
|
|
124
150
|
this.startPoint = { x: event.x, y: event.y };
|
|
@@ -128,11 +154,32 @@ export class SelectTool extends Tool {
|
|
|
128
154
|
this.canvas.eventManager.isDrawing = true;
|
|
129
155
|
}
|
|
130
156
|
|
|
131
|
-
// Don't start box selection - we're either resizing or will be dragging
|
|
132
157
|
return;
|
|
133
158
|
} else {
|
|
159
|
+
// Check if clicking inside existing selection bounds
|
|
160
|
+
const selectionBounds = this._getSelectionBounds();
|
|
161
|
+
if (selectionBounds &&
|
|
162
|
+
event.x >= selectionBounds.x &&
|
|
163
|
+
event.x <= selectionBounds.x + selectionBounds.width &&
|
|
164
|
+
event.y >= selectionBounds.y &&
|
|
165
|
+
event.y <= selectionBounds.y + selectionBounds.height) {
|
|
166
|
+
|
|
167
|
+
// Drag the selection (strokes AND OMD elements)
|
|
168
|
+
this.isDraggingStrokes = true; // We reuse this flag for general dragging
|
|
169
|
+
this.isDraggingOMD = true; // Also set this for OMD elements
|
|
170
|
+
this.hasSeparatedForDrag = false;
|
|
171
|
+
this.dragStartPoint = { x: event.x, y: event.y };
|
|
172
|
+
this.startPoint = { x: event.x, y: event.y }; // For OMD dragging
|
|
173
|
+
|
|
174
|
+
if (this.canvas.eventManager) {
|
|
175
|
+
this.canvas.eventManager.isDrawing = true;
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
134
180
|
// Clicking on empty space - clear all selections and start box selection
|
|
135
181
|
this.resizeHandleManager.clearSelection();
|
|
182
|
+
this.selectedOMDElements.clear();
|
|
136
183
|
this._startBoxSelection(event.x, event.y, event.shiftKey);
|
|
137
184
|
|
|
138
185
|
// CRITICAL: Set startPoint AFTER _startBoxSelection so it doesn't get cleared!
|
|
@@ -156,10 +203,12 @@ export class SelectTool extends Tool {
|
|
|
156
203
|
return;
|
|
157
204
|
}
|
|
158
205
|
|
|
206
|
+
let handled = false;
|
|
207
|
+
|
|
159
208
|
// Handle OMD dragging if in progress
|
|
160
|
-
if (this.isDraggingOMD
|
|
161
|
-
this.
|
|
162
|
-
|
|
209
|
+
if (this.isDraggingOMD) {
|
|
210
|
+
this._dragOMDElements(event.x, event.y);
|
|
211
|
+
handled = true;
|
|
163
212
|
}
|
|
164
213
|
|
|
165
214
|
// Handle stroke dragging if in progress
|
|
@@ -167,32 +216,40 @@ export class SelectTool extends Tool {
|
|
|
167
216
|
const dx = event.x - this.dragStartPoint.x;
|
|
168
217
|
const dy = event.y - this.dragStartPoint.y;
|
|
169
218
|
|
|
170
|
-
if (dx
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const stroke = this.canvas.strokes.get(strokeId);
|
|
179
|
-
if (stroke) {
|
|
180
|
-
stroke.move(dx, dy);
|
|
181
|
-
movedStrokes.add(strokeId);
|
|
219
|
+
if (dx !== 0 || dy !== 0) {
|
|
220
|
+
// If we moved, it's a drag, so cancel potential deselect
|
|
221
|
+
this.potentialDeselect = null;
|
|
222
|
+
|
|
223
|
+
// Separate selected parts if needed
|
|
224
|
+
if (!this.hasSeparatedForDrag) {
|
|
225
|
+
this._separateSelectedParts();
|
|
226
|
+
this.hasSeparatedForDrag = true;
|
|
182
227
|
}
|
|
228
|
+
|
|
229
|
+
// Move all selected strokes
|
|
230
|
+
const movedStrokes = new Set();
|
|
231
|
+
for (const [strokeId, _] of this.selectedSegments) {
|
|
232
|
+
const stroke = this.canvas.strokes.get(strokeId);
|
|
233
|
+
if (stroke) {
|
|
234
|
+
stroke.move(dx, dy);
|
|
235
|
+
movedStrokes.add(strokeId);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.dragStartPoint = { x: event.x, y: event.y };
|
|
240
|
+
this._updateSegmentSelectionVisuals();
|
|
241
|
+
|
|
242
|
+
// Emit event
|
|
243
|
+
this.canvas.emit('strokesMoved', {
|
|
244
|
+
dx, dy,
|
|
245
|
+
strokeIds: Array.from(movedStrokes)
|
|
246
|
+
});
|
|
183
247
|
}
|
|
184
|
-
|
|
185
|
-
this.dragStartPoint = { x: event.x, y: event.y };
|
|
186
|
-
this._updateSegmentSelectionVisuals();
|
|
187
|
-
|
|
188
|
-
// Emit event
|
|
189
|
-
this.canvas.emit('strokesMoved', {
|
|
190
|
-
dx, dy,
|
|
191
|
-
strokeIds: Array.from(movedStrokes)
|
|
192
|
-
});
|
|
193
|
-
return;
|
|
248
|
+
handled = true;
|
|
194
249
|
}
|
|
195
250
|
|
|
251
|
+
if (handled) return;
|
|
252
|
+
|
|
196
253
|
// Handle box selection if in progress
|
|
197
254
|
if (!this.isSelecting || !this.selectionBox) return;
|
|
198
255
|
|
|
@@ -380,6 +437,7 @@ export class SelectTool extends Tool {
|
|
|
380
437
|
this.selectedSegments.clear();
|
|
381
438
|
|
|
382
439
|
// Clear OMD selection
|
|
440
|
+
this.selectedOMDElements.clear();
|
|
383
441
|
this.resizeHandleManager.clearSelection();
|
|
384
442
|
|
|
385
443
|
// Remove selection box if it exists
|
|
@@ -485,19 +543,97 @@ export class SelectTool extends Tool {
|
|
|
485
543
|
}
|
|
486
544
|
|
|
487
545
|
/**
|
|
488
|
-
*
|
|
546
|
+
* Gets the bounding box of the current selection (strokes + OMD).
|
|
547
|
+
* @private
|
|
548
|
+
* @returns {{x: number, y: number, width: number, height: number}|null}
|
|
549
|
+
*/
|
|
550
|
+
_getSelectionBounds() {
|
|
551
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
552
|
+
let hasPoints = false;
|
|
553
|
+
|
|
554
|
+
// 1. Check strokes
|
|
555
|
+
if (this.selectedSegments.size > 0) {
|
|
556
|
+
for (const [strokeId, segmentSet] of this.selectedSegments.entries()) {
|
|
557
|
+
const stroke = this.canvas.strokes.get(strokeId);
|
|
558
|
+
if (!stroke || !stroke.points) continue;
|
|
559
|
+
|
|
560
|
+
for (const segmentIndex of segmentSet) {
|
|
561
|
+
if (segmentIndex >= stroke.points.length - 1) continue;
|
|
562
|
+
const p1 = stroke.points[segmentIndex];
|
|
563
|
+
const p2 = stroke.points[segmentIndex + 1];
|
|
564
|
+
|
|
565
|
+
minX = Math.min(minX, p1.x, p2.x);
|
|
566
|
+
minY = Math.min(minY, p1.y, p2.y);
|
|
567
|
+
maxX = Math.max(maxX, p1.x, p2.x);
|
|
568
|
+
maxY = Math.max(maxY, p1.y, p2.y);
|
|
569
|
+
hasPoints = true;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// 2. Check OMD elements
|
|
575
|
+
if (this.selectedOMDElements.size > 0) {
|
|
576
|
+
for (const element of this.selectedOMDElements) {
|
|
577
|
+
const bbox = this._getOMDElementBounds(element);
|
|
578
|
+
if (bbox) {
|
|
579
|
+
minX = Math.min(minX, bbox.x);
|
|
580
|
+
minY = Math.min(minY, bbox.y);
|
|
581
|
+
maxX = Math.max(maxX, bbox.x + bbox.width);
|
|
582
|
+
maxY = Math.max(maxY, bbox.y + bbox.height);
|
|
583
|
+
hasPoints = true;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!hasPoints) return null;
|
|
589
|
+
|
|
590
|
+
// Add padding to match the visual box
|
|
591
|
+
const padding = 8;
|
|
592
|
+
return {
|
|
593
|
+
x: minX - padding,
|
|
594
|
+
y: minY - padding,
|
|
595
|
+
width: (maxX + padding) - (minX - padding),
|
|
596
|
+
height: (maxY + padding) - (minY - padding)
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Drags all selected OMD elements
|
|
489
602
|
* @private
|
|
490
603
|
* @param {number} x - Current pointer x coordinate
|
|
491
604
|
* @param {number} y - Current pointer y coordinate
|
|
492
605
|
*/
|
|
493
|
-
|
|
494
|
-
if (!this.
|
|
606
|
+
_dragOMDElements(x, y) {
|
|
607
|
+
if (!this.startPoint) return;
|
|
495
608
|
|
|
496
609
|
const dx = x - this.startPoint.x;
|
|
497
610
|
const dy = y - this.startPoint.y;
|
|
498
611
|
|
|
612
|
+
if (dx === 0 && dy === 0) return;
|
|
613
|
+
|
|
614
|
+
for (const element of this.selectedOMDElements) {
|
|
615
|
+
this._moveOMDElement(element, dx, dy);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Update start point for next move
|
|
619
|
+
this.startPoint = { x, y };
|
|
620
|
+
|
|
621
|
+
// Update resize handles if we have a single selection
|
|
622
|
+
if (this.selectedOMDElements.size === 1) {
|
|
623
|
+
const element = this.selectedOMDElements.values().next().value;
|
|
624
|
+
this.resizeHandleManager.updateIfSelected(element);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
this._updateSegmentSelectionVisuals();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Moves a single OMD element
|
|
632
|
+
* @private
|
|
633
|
+
*/
|
|
634
|
+
_moveOMDElement(element, dx, dy) {
|
|
499
635
|
// Parse current transform
|
|
500
|
-
const currentTransform =
|
|
636
|
+
const currentTransform = element.getAttribute('transform') || '';
|
|
501
637
|
const translateMatch = currentTransform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
|
|
502
638
|
const scaleMatch = currentTransform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
|
|
503
639
|
|
|
@@ -517,13 +653,40 @@ export class SelectTool extends Tool {
|
|
|
517
653
|
newTransform += ` scale(${scaleX}, ${scaleY})`;
|
|
518
654
|
}
|
|
519
655
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
656
|
+
element.setAttribute('transform', newTransform);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Gets the bounds of an OMD element including transform
|
|
661
|
+
* @private
|
|
662
|
+
*/
|
|
663
|
+
_getOMDElementBounds(item) {
|
|
664
|
+
try {
|
|
665
|
+
const bbox = item.getBBox();
|
|
666
|
+
const transform = item.getAttribute('transform') || '';
|
|
667
|
+
let offsetX = 0, offsetY = 0, scaleX = 1, scaleY = 1;
|
|
668
|
+
|
|
669
|
+
const translateMatch = transform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
|
|
670
|
+
if (translateMatch) {
|
|
671
|
+
offsetX = parseFloat(translateMatch[1]) || 0;
|
|
672
|
+
offsetY = parseFloat(translateMatch[2]) || 0;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const scaleMatch = transform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
|
|
676
|
+
if (scaleMatch) {
|
|
677
|
+
scaleX = parseFloat(scaleMatch[1]) || 1;
|
|
678
|
+
scaleY = scaleMatch[2] ? parseFloat(scaleMatch[2]) : scaleX;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return {
|
|
682
|
+
x: offsetX + (bbox.x * scaleX),
|
|
683
|
+
y: offsetY + (bbox.y * scaleY),
|
|
684
|
+
width: bbox.width * scaleX,
|
|
685
|
+
height: bbox.height * scaleY
|
|
686
|
+
};
|
|
687
|
+
} catch (e) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
527
690
|
}
|
|
528
691
|
|
|
529
692
|
/**
|
|
@@ -702,6 +865,7 @@ export class SelectTool extends Tool {
|
|
|
702
865
|
const height = parseFloat(this.selectionBox.getAttribute('height'));
|
|
703
866
|
const selectionBounds = new BoundingBox(x, y, width, height);
|
|
704
867
|
|
|
868
|
+
// 1. Select strokes
|
|
705
869
|
for (const [id, stroke] of this.canvas.strokes) {
|
|
706
870
|
if (!stroke.points || stroke.points.length < 2) continue;
|
|
707
871
|
for (let i = 0; i < stroke.points.length - 1; i++) {
|
|
@@ -715,6 +879,36 @@ export class SelectTool extends Tool {
|
|
|
715
879
|
}
|
|
716
880
|
}
|
|
717
881
|
}
|
|
882
|
+
|
|
883
|
+
// 2. Select OMD elements
|
|
884
|
+
const omdLayer = this.canvas.drawingLayer?.querySelector('.omd-layer');
|
|
885
|
+
if (omdLayer) {
|
|
886
|
+
const omdItems = omdLayer.querySelectorAll('.omd-item');
|
|
887
|
+
for (const item of omdItems) {
|
|
888
|
+
if (item?.dataset?.locked === 'true') continue;
|
|
889
|
+
|
|
890
|
+
const itemBounds = this._getOMDElementBounds(item);
|
|
891
|
+
if (itemBounds) {
|
|
892
|
+
// Check intersection
|
|
893
|
+
const intersects = !(itemBounds.x > x + width ||
|
|
894
|
+
itemBounds.x + itemBounds.width < x ||
|
|
895
|
+
itemBounds.y > y + height ||
|
|
896
|
+
itemBounds.y + itemBounds.height < y);
|
|
897
|
+
|
|
898
|
+
if (intersects) {
|
|
899
|
+
this.selectedOMDElements.add(item);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Update resize handles
|
|
906
|
+
if (this.selectedOMDElements.size === 1 && this.selectedSegments.size === 0) {
|
|
907
|
+
this.resizeHandleManager.selectElement(this.selectedOMDElements.values().next().value);
|
|
908
|
+
} else {
|
|
909
|
+
this.resizeHandleManager.clearSelection();
|
|
910
|
+
}
|
|
911
|
+
|
|
718
912
|
this._updateSegmentSelectionVisuals();
|
|
719
913
|
}
|
|
720
914
|
|
|
@@ -775,37 +969,26 @@ export class SelectTool extends Tool {
|
|
|
775
969
|
selectionLayer.removeChild(selectionLayer.firstChild);
|
|
776
970
|
}
|
|
777
971
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
for (const [strokeId, segmentSet] of this.selectedSegments.entries()) {
|
|
782
|
-
const stroke = this.canvas.strokes.get(strokeId);
|
|
783
|
-
if (!stroke || !stroke.points) continue;
|
|
972
|
+
const bounds = this._getSelectionBounds();
|
|
973
|
+
if (!bounds) return;
|
|
784
974
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
highlight.classList.add('selection-highlight');
|
|
803
|
-
|
|
804
|
-
selectionLayer.appendChild(highlight);
|
|
805
|
-
highlightCount++;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
975
|
+
// Create bounding box element
|
|
976
|
+
const box = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
977
|
+
box.setAttribute('x', bounds.x);
|
|
978
|
+
box.setAttribute('y', bounds.y);
|
|
979
|
+
box.setAttribute('width', bounds.width);
|
|
980
|
+
box.setAttribute('height', bounds.height);
|
|
981
|
+
box.setAttribute('fill', 'none');
|
|
982
|
+
box.setAttribute('stroke', '#007bff'); // Selection color
|
|
983
|
+
box.setAttribute('stroke-width', '1.5');
|
|
984
|
+
box.setAttribute('stroke-dasharray', '6, 4'); // Dotted/Dashed
|
|
985
|
+
box.setAttribute('stroke-opacity', '0.6'); // Light
|
|
986
|
+
box.setAttribute('rx', '8'); // Rounded corners
|
|
987
|
+
box.setAttribute('ry', '8');
|
|
988
|
+
box.style.pointerEvents = 'none';
|
|
989
|
+
box.classList.add('selection-bounds');
|
|
990
|
+
|
|
991
|
+
selectionLayer.appendChild(box);
|
|
809
992
|
}
|
|
810
993
|
|
|
811
994
|
/**
|
|
@@ -819,12 +1002,13 @@ export class SelectTool extends Tool {
|
|
|
819
1002
|
}
|
|
820
1003
|
|
|
821
1004
|
/**
|
|
822
|
-
* Selects all segments on the canvas.
|
|
1005
|
+
* Selects all segments and OMD elements on the canvas.
|
|
823
1006
|
* @private
|
|
824
1007
|
*/
|
|
825
1008
|
_selectAllSegments() {
|
|
826
1009
|
// Clear current selection
|
|
827
1010
|
this.selectedSegments.clear();
|
|
1011
|
+
this.selectedOMDElements.clear();
|
|
828
1012
|
|
|
829
1013
|
let totalSegments = 0;
|
|
830
1014
|
|
|
@@ -843,6 +1027,17 @@ export class SelectTool extends Tool {
|
|
|
843
1027
|
}
|
|
844
1028
|
}
|
|
845
1029
|
|
|
1030
|
+
// Select all OMD elements
|
|
1031
|
+
const omdLayer = this.canvas.drawingLayer?.querySelector('.omd-layer');
|
|
1032
|
+
if (omdLayer) {
|
|
1033
|
+
const omdItems = omdLayer.querySelectorAll('.omd-item');
|
|
1034
|
+
for (const item of omdItems) {
|
|
1035
|
+
if (item?.dataset?.locked !== 'true') {
|
|
1036
|
+
this.selectedOMDElements.add(item);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
846
1041
|
// Update visuals
|
|
847
1042
|
this._updateSegmentSelectionVisuals();
|
|
848
1043
|
|
|
@@ -854,42 +1049,55 @@ export class SelectTool extends Tool {
|
|
|
854
1049
|
}
|
|
855
1050
|
|
|
856
1051
|
/**
|
|
857
|
-
* Deletes all currently selected segments
|
|
1052
|
+
* Deletes all currently selected items (segments and OMD elements).
|
|
858
1053
|
* @private
|
|
859
1054
|
*/
|
|
860
1055
|
_deleteSelectedSegments() {
|
|
861
|
-
|
|
1056
|
+
let changed = false;
|
|
862
1057
|
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1058
|
+
// Delete OMD elements
|
|
1059
|
+
if (this.selectedOMDElements.size > 0) {
|
|
1060
|
+
for (const element of this.selectedOMDElements) {
|
|
1061
|
+
element.remove();
|
|
1062
|
+
}
|
|
1063
|
+
this.selectedOMDElements.clear();
|
|
1064
|
+
this.resizeHandleManager.clearSelection();
|
|
1065
|
+
changed = true;
|
|
1066
|
+
}
|
|
869
1067
|
|
|
870
|
-
|
|
1068
|
+
if (this.selectedSegments.size > 0) {
|
|
1069
|
+
// Process each stroke that has selected segments
|
|
1070
|
+
const strokesToProcess = Array.from(this.selectedSegments.entries());
|
|
871
1071
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (selectedCount >= totalSegments * 0.8) {
|
|
877
|
-
// Delete entire stroke
|
|
878
|
-
this.canvas.removeStroke(strokeId);
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
1072
|
+
for (const [strokeId, segmentIndices] of strokesToProcess) {
|
|
1073
|
+
const stroke = this.canvas.strokes.get(strokeId);
|
|
1074
|
+
if (!stroke || !stroke.points || stroke.points.length < 2) continue;
|
|
881
1075
|
|
|
882
|
-
|
|
883
|
-
|
|
1076
|
+
const sortedIndices = Array.from(segmentIndices).sort((a, b) => a - b);
|
|
1077
|
+
|
|
1078
|
+
// If all or most segments are selected, just delete the whole stroke
|
|
1079
|
+
const totalSegments = stroke.points.length - 1;
|
|
1080
|
+
const selectedCount = sortedIndices.length;
|
|
1081
|
+
|
|
1082
|
+
if (selectedCount >= totalSegments * 0.8) {
|
|
1083
|
+
// Delete entire stroke
|
|
1084
|
+
this.canvas.removeStroke(strokeId);
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Split the stroke, keeping only unselected segments
|
|
1089
|
+
this._splitStrokeKeepingUnselected(stroke, sortedIndices);
|
|
1090
|
+
}
|
|
1091
|
+
changed = true;
|
|
884
1092
|
}
|
|
885
1093
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
1094
|
+
if (changed) {
|
|
1095
|
+
// Clear selection and update UI
|
|
1096
|
+
this.clearSelection();
|
|
1097
|
+
|
|
1098
|
+
// Emit deletion event
|
|
1099
|
+
this.canvas.emit('selectionDeleted');
|
|
1100
|
+
}
|
|
893
1101
|
}
|
|
894
1102
|
|
|
895
1103
|
/**
|
|
@@ -925,6 +1133,66 @@ export class SelectTool extends Tool {
|
|
|
925
1133
|
});
|
|
926
1134
|
}
|
|
927
1135
|
|
|
1136
|
+
/**
|
|
1137
|
+
* Separates selected segments into new strokes so they can be moved independently.
|
|
1138
|
+
* @private
|
|
1139
|
+
*/
|
|
1140
|
+
_separateSelectedParts() {
|
|
1141
|
+
const newSelection = new Map();
|
|
1142
|
+
const strokesToProcess = Array.from(this.selectedSegments.entries());
|
|
1143
|
+
|
|
1144
|
+
for (const [strokeId, selectedIndices] of strokesToProcess) {
|
|
1145
|
+
const stroke = this.canvas.strokes.get(strokeId);
|
|
1146
|
+
if (!stroke || !stroke.points || stroke.points.length < 2) continue;
|
|
1147
|
+
|
|
1148
|
+
const totalSegments = stroke.points.length - 1;
|
|
1149
|
+
|
|
1150
|
+
// If fully selected, just keep it as is
|
|
1151
|
+
if (selectedIndices.size === totalSegments) {
|
|
1152
|
+
newSelection.set(strokeId, selectedIndices);
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// It's a partial selection - we need to split
|
|
1157
|
+
const sortedSelectedIndices = Array.from(selectedIndices).sort((a, b) => a - b);
|
|
1158
|
+
|
|
1159
|
+
// Determine unselected indices
|
|
1160
|
+
const unselectedIndices = [];
|
|
1161
|
+
for (let i = 0; i < totalSegments; i++) {
|
|
1162
|
+
if (!selectedIndices.has(i)) {
|
|
1163
|
+
unselectedIndices.push(i);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Group segments
|
|
1168
|
+
const selectedGroups = this._groupConsecutiveSegments(sortedSelectedIndices);
|
|
1169
|
+
const unselectedGroups = this._groupConsecutiveSegments(unselectedIndices);
|
|
1170
|
+
|
|
1171
|
+
// Create new strokes for selected parts
|
|
1172
|
+
selectedGroups.forEach(group => {
|
|
1173
|
+
const newStroke = this._createStrokeFromSegments(stroke, group);
|
|
1174
|
+
if (newStroke) {
|
|
1175
|
+
// Add to new selection (all segments selected)
|
|
1176
|
+
const newIndices = new Set();
|
|
1177
|
+
for (let i = 0; i < newStroke.points.length - 1; i++) {
|
|
1178
|
+
newIndices.add(i);
|
|
1179
|
+
}
|
|
1180
|
+
newSelection.set(newStroke.id, newIndices);
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
// Create new strokes for unselected parts (don't add to selection)
|
|
1185
|
+
unselectedGroups.forEach(group => {
|
|
1186
|
+
this._createStrokeFromSegments(stroke, group);
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
// Remove original stroke
|
|
1190
|
+
this.canvas.removeStroke(strokeId);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
this.selectedSegments = newSelection;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
928
1196
|
/**
|
|
929
1197
|
* Groups consecutive segment indices into separate arrays.
|
|
930
1198
|
* @private
|
|
@@ -958,9 +1226,10 @@ export class SelectTool extends Tool {
|
|
|
958
1226
|
/**
|
|
959
1227
|
* Creates a new stroke from a group of segment indices.
|
|
960
1228
|
* @private
|
|
1229
|
+
* @returns {Stroke|null} The newly created stroke
|
|
961
1230
|
*/
|
|
962
1231
|
_createStrokeFromSegments(originalStroke, segmentIndices) {
|
|
963
|
-
if (segmentIndices.length === 0) return;
|
|
1232
|
+
if (segmentIndices.length === 0) return null;
|
|
964
1233
|
|
|
965
1234
|
// Collect points for the new stroke
|
|
966
1235
|
const newPoints = [];
|
|
@@ -974,7 +1243,7 @@ export class SelectTool extends Tool {
|
|
|
974
1243
|
}
|
|
975
1244
|
|
|
976
1245
|
// Only create stroke if we have at least 2 points
|
|
977
|
-
if (newPoints.length < 2) return;
|
|
1246
|
+
if (newPoints.length < 2) return null;
|
|
978
1247
|
|
|
979
1248
|
// Create new stroke with same properties
|
|
980
1249
|
const newStroke = new Stroke({
|
|
@@ -991,6 +1260,7 @@ export class SelectTool extends Tool {
|
|
|
991
1260
|
|
|
992
1261
|
newStroke.finish();
|
|
993
1262
|
this.canvas.addStroke(newStroke);
|
|
1263
|
+
return newStroke;
|
|
994
1264
|
}
|
|
995
1265
|
|
|
996
1266
|
}
|