@teachinglab/omd 0.7.5 → 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 +290 -113
- package/package.json +1 -1
|
@@ -41,6 +41,7 @@ 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;
|
|
@@ -100,8 +101,11 @@ export class SelectTool extends Tool {
|
|
|
100
101
|
}
|
|
101
102
|
return;
|
|
102
103
|
} else {
|
|
103
|
-
// Clicking on a stroke segment
|
|
104
|
-
|
|
104
|
+
// Clicking on a stroke segment
|
|
105
|
+
if (!event.shiftKey) {
|
|
106
|
+
this.resizeHandleManager.clearSelection();
|
|
107
|
+
this.selectedOMDElements.clear();
|
|
108
|
+
}
|
|
105
109
|
this._handleSegmentClick(segmentSelection, event.shiftKey);
|
|
106
110
|
|
|
107
111
|
// Prepare for drag immediately after selection
|
|
@@ -114,14 +118,33 @@ export class SelectTool extends Tool {
|
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
120
|
} else if (omdElement) {
|
|
117
|
-
// 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
|
|
118
137
|
if (!event.shiftKey) {
|
|
119
138
|
this.selectedSegments.clear();
|
|
120
139
|
this._clearSelectionVisuals();
|
|
140
|
+
this.selectedOMDElements.clear();
|
|
141
|
+
this.resizeHandleManager.clearSelection();
|
|
121
142
|
}
|
|
143
|
+
|
|
144
|
+
this.selectedOMDElements.add(omdElement);
|
|
122
145
|
this.resizeHandleManager.selectElement(omdElement);
|
|
123
146
|
|
|
124
|
-
//
|
|
147
|
+
// Start tracking for potential drag operation
|
|
125
148
|
this.isDraggingOMD = true;
|
|
126
149
|
this.draggedOMDElement = omdElement;
|
|
127
150
|
this.startPoint = { x: event.x, y: event.y };
|
|
@@ -131,11 +154,32 @@ export class SelectTool extends Tool {
|
|
|
131
154
|
this.canvas.eventManager.isDrawing = true;
|
|
132
155
|
}
|
|
133
156
|
|
|
134
|
-
// Don't start box selection - we're either resizing or will be dragging
|
|
135
157
|
return;
|
|
136
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
|
+
|
|
137
180
|
// Clicking on empty space - clear all selections and start box selection
|
|
138
181
|
this.resizeHandleManager.clearSelection();
|
|
182
|
+
this.selectedOMDElements.clear();
|
|
139
183
|
this._startBoxSelection(event.x, event.y, event.shiftKey);
|
|
140
184
|
|
|
141
185
|
// CRITICAL: Set startPoint AFTER _startBoxSelection so it doesn't get cleared!
|
|
@@ -159,10 +203,12 @@ export class SelectTool extends Tool {
|
|
|
159
203
|
return;
|
|
160
204
|
}
|
|
161
205
|
|
|
206
|
+
let handled = false;
|
|
207
|
+
|
|
162
208
|
// Handle OMD dragging if in progress
|
|
163
|
-
if (this.isDraggingOMD
|
|
164
|
-
this.
|
|
165
|
-
|
|
209
|
+
if (this.isDraggingOMD) {
|
|
210
|
+
this._dragOMDElements(event.x, event.y);
|
|
211
|
+
handled = true;
|
|
166
212
|
}
|
|
167
213
|
|
|
168
214
|
// Handle stroke dragging if in progress
|
|
@@ -170,38 +216,40 @@ export class SelectTool extends Tool {
|
|
|
170
216
|
const dx = event.x - this.dragStartPoint.x;
|
|
171
217
|
const dy = event.y - this.dragStartPoint.y;
|
|
172
218
|
|
|
173
|
-
if (dx
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.potentialDeselect = null;
|
|
219
|
+
if (dx !== 0 || dy !== 0) {
|
|
220
|
+
// If we moved, it's a drag, so cancel potential deselect
|
|
221
|
+
this.potentialDeselect = null;
|
|
177
222
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Move all selected strokes
|
|
185
|
-
const movedStrokes = new Set();
|
|
186
|
-
for (const [strokeId, _] of this.selectedSegments) {
|
|
187
|
-
const stroke = this.canvas.strokes.get(strokeId);
|
|
188
|
-
if (stroke) {
|
|
189
|
-
stroke.move(dx, dy);
|
|
190
|
-
movedStrokes.add(strokeId);
|
|
223
|
+
// Separate selected parts if needed
|
|
224
|
+
if (!this.hasSeparatedForDrag) {
|
|
225
|
+
this._separateSelectedParts();
|
|
226
|
+
this.hasSeparatedForDrag = true;
|
|
191
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
|
+
});
|
|
192
247
|
}
|
|
193
|
-
|
|
194
|
-
this.dragStartPoint = { x: event.x, y: event.y };
|
|
195
|
-
this._updateSegmentSelectionVisuals();
|
|
196
|
-
|
|
197
|
-
// Emit event
|
|
198
|
-
this.canvas.emit('strokesMoved', {
|
|
199
|
-
dx, dy,
|
|
200
|
-
strokeIds: Array.from(movedStrokes)
|
|
201
|
-
});
|
|
202
|
-
return;
|
|
248
|
+
handled = true;
|
|
203
249
|
}
|
|
204
250
|
|
|
251
|
+
if (handled) return;
|
|
252
|
+
|
|
205
253
|
// Handle box selection if in progress
|
|
206
254
|
if (!this.isSelecting || !this.selectionBox) return;
|
|
207
255
|
|
|
@@ -389,6 +437,7 @@ export class SelectTool extends Tool {
|
|
|
389
437
|
this.selectedSegments.clear();
|
|
390
438
|
|
|
391
439
|
// Clear OMD selection
|
|
440
|
+
this.selectedOMDElements.clear();
|
|
392
441
|
this.resizeHandleManager.clearSelection();
|
|
393
442
|
|
|
394
443
|
// Remove selection box if it exists
|
|
@@ -494,19 +543,97 @@ export class SelectTool extends Tool {
|
|
|
494
543
|
}
|
|
495
544
|
|
|
496
545
|
/**
|
|
497
|
-
*
|
|
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
|
|
498
602
|
* @private
|
|
499
603
|
* @param {number} x - Current pointer x coordinate
|
|
500
604
|
* @param {number} y - Current pointer y coordinate
|
|
501
605
|
*/
|
|
502
|
-
|
|
503
|
-
if (!this.
|
|
606
|
+
_dragOMDElements(x, y) {
|
|
607
|
+
if (!this.startPoint) return;
|
|
504
608
|
|
|
505
609
|
const dx = x - this.startPoint.x;
|
|
506
610
|
const dy = y - this.startPoint.y;
|
|
507
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) {
|
|
508
635
|
// Parse current transform
|
|
509
|
-
const currentTransform =
|
|
636
|
+
const currentTransform = element.getAttribute('transform') || '';
|
|
510
637
|
const translateMatch = currentTransform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
|
|
511
638
|
const scaleMatch = currentTransform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
|
|
512
639
|
|
|
@@ -526,13 +653,40 @@ export class SelectTool extends Tool {
|
|
|
526
653
|
newTransform += ` scale(${scaleX}, ${scaleY})`;
|
|
527
654
|
}
|
|
528
655
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
}
|
|
536
690
|
}
|
|
537
691
|
|
|
538
692
|
/**
|
|
@@ -711,6 +865,7 @@ export class SelectTool extends Tool {
|
|
|
711
865
|
const height = parseFloat(this.selectionBox.getAttribute('height'));
|
|
712
866
|
const selectionBounds = new BoundingBox(x, y, width, height);
|
|
713
867
|
|
|
868
|
+
// 1. Select strokes
|
|
714
869
|
for (const [id, stroke] of this.canvas.strokes) {
|
|
715
870
|
if (!stroke.points || stroke.points.length < 2) continue;
|
|
716
871
|
for (let i = 0; i < stroke.points.length - 1; i++) {
|
|
@@ -724,6 +879,36 @@ export class SelectTool extends Tool {
|
|
|
724
879
|
}
|
|
725
880
|
}
|
|
726
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
|
+
|
|
727
912
|
this._updateSegmentSelectionVisuals();
|
|
728
913
|
}
|
|
729
914
|
|
|
@@ -784,48 +969,15 @@ export class SelectTool extends Tool {
|
|
|
784
969
|
selectionLayer.removeChild(selectionLayer.firstChild);
|
|
785
970
|
}
|
|
786
971
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
790
|
-
let hasSelection = false;
|
|
791
|
-
|
|
792
|
-
// Calculate bounding box of all selected segments
|
|
793
|
-
for (const [strokeId, segmentSet] of this.selectedSegments.entries()) {
|
|
794
|
-
const stroke = this.canvas.strokes.get(strokeId);
|
|
795
|
-
if (!stroke || !stroke.points) continue;
|
|
796
|
-
|
|
797
|
-
for (const segmentIndex of segmentSet) {
|
|
798
|
-
if (segmentIndex >= stroke.points.length - 1) continue;
|
|
799
|
-
|
|
800
|
-
const p1 = stroke.points[segmentIndex];
|
|
801
|
-
const p2 = stroke.points[segmentIndex + 1];
|
|
802
|
-
|
|
803
|
-
minX = Math.min(minX, p1.x, p2.x);
|
|
804
|
-
minY = Math.min(minY, p1.y, p2.y);
|
|
805
|
-
maxX = Math.max(maxX, p1.x, p2.x);
|
|
806
|
-
maxY = Math.max(maxY, p1.y, p2.y);
|
|
807
|
-
hasSelection = true;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
if (!hasSelection) return;
|
|
812
|
-
|
|
813
|
-
// Add padding
|
|
814
|
-
const padding = 8;
|
|
815
|
-
minX -= padding;
|
|
816
|
-
minY -= padding;
|
|
817
|
-
maxX += padding;
|
|
818
|
-
maxY += padding;
|
|
819
|
-
|
|
820
|
-
const width = maxX - minX;
|
|
821
|
-
const height = maxY - minY;
|
|
972
|
+
const bounds = this._getSelectionBounds();
|
|
973
|
+
if (!bounds) return;
|
|
822
974
|
|
|
823
975
|
// Create bounding box element
|
|
824
976
|
const box = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
825
|
-
box.setAttribute('x',
|
|
826
|
-
box.setAttribute('y',
|
|
827
|
-
box.setAttribute('width', width);
|
|
828
|
-
box.setAttribute('height', height);
|
|
977
|
+
box.setAttribute('x', bounds.x);
|
|
978
|
+
box.setAttribute('y', bounds.y);
|
|
979
|
+
box.setAttribute('width', bounds.width);
|
|
980
|
+
box.setAttribute('height', bounds.height);
|
|
829
981
|
box.setAttribute('fill', 'none');
|
|
830
982
|
box.setAttribute('stroke', '#007bff'); // Selection color
|
|
831
983
|
box.setAttribute('stroke-width', '1.5');
|
|
@@ -850,12 +1002,13 @@ export class SelectTool extends Tool {
|
|
|
850
1002
|
}
|
|
851
1003
|
|
|
852
1004
|
/**
|
|
853
|
-
* Selects all segments on the canvas.
|
|
1005
|
+
* Selects all segments and OMD elements on the canvas.
|
|
854
1006
|
* @private
|
|
855
1007
|
*/
|
|
856
1008
|
_selectAllSegments() {
|
|
857
1009
|
// Clear current selection
|
|
858
1010
|
this.selectedSegments.clear();
|
|
1011
|
+
this.selectedOMDElements.clear();
|
|
859
1012
|
|
|
860
1013
|
let totalSegments = 0;
|
|
861
1014
|
|
|
@@ -874,6 +1027,17 @@ export class SelectTool extends Tool {
|
|
|
874
1027
|
}
|
|
875
1028
|
}
|
|
876
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
|
+
|
|
877
1041
|
// Update visuals
|
|
878
1042
|
this._updateSegmentSelectionVisuals();
|
|
879
1043
|
|
|
@@ -885,42 +1049,55 @@ export class SelectTool extends Tool {
|
|
|
885
1049
|
}
|
|
886
1050
|
|
|
887
1051
|
/**
|
|
888
|
-
* Deletes all currently selected segments
|
|
1052
|
+
* Deletes all currently selected items (segments and OMD elements).
|
|
889
1053
|
* @private
|
|
890
1054
|
*/
|
|
891
1055
|
_deleteSelectedSegments() {
|
|
892
|
-
|
|
1056
|
+
let changed = false;
|
|
893
1057
|
|
|
894
|
-
//
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
+
}
|
|
900
1067
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
const totalSegments = stroke.points.length - 1;
|
|
905
|
-
const selectedCount = sortedIndices.length;
|
|
1068
|
+
if (this.selectedSegments.size > 0) {
|
|
1069
|
+
// Process each stroke that has selected segments
|
|
1070
|
+
const strokesToProcess = Array.from(this.selectedSegments.entries());
|
|
906
1071
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
continue;
|
|
911
|
-
}
|
|
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;
|
|
912
1075
|
|
|
913
|
-
|
|
914
|
-
|
|
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;
|
|
915
1092
|
}
|
|
916
1093
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
1094
|
+
if (changed) {
|
|
1095
|
+
// Clear selection and update UI
|
|
1096
|
+
this.clearSelection();
|
|
1097
|
+
|
|
1098
|
+
// Emit deletion event
|
|
1099
|
+
this.canvas.emit('selectionDeleted');
|
|
1100
|
+
}
|
|
924
1101
|
}
|
|
925
1102
|
|
|
926
1103
|
/**
|