@teachinglab/omd 0.7.4 → 0.7.5
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 +114 -21
- package/package.json +1 -1
|
@@ -46,6 +46,7 @@ export class SelectTool extends Tool {
|
|
|
46
46
|
this.isDraggingStrokes = false;
|
|
47
47
|
this.dragStartPoint = null;
|
|
48
48
|
this.potentialDeselect = null;
|
|
49
|
+
this.hasSeparatedForDrag = false;
|
|
49
50
|
|
|
50
51
|
// Initialize resize handle manager for OMD visuals
|
|
51
52
|
this.resizeHandleManager = new ResizeHandleManager(canvas);
|
|
@@ -89,6 +90,7 @@ export class SelectTool extends Tool {
|
|
|
89
90
|
if (isSelected) {
|
|
90
91
|
// Already selected - prepare for drag, but don't deselect yet
|
|
91
92
|
this.isDraggingStrokes = true;
|
|
93
|
+
this.hasSeparatedForDrag = false;
|
|
92
94
|
this.dragStartPoint = { x: event.x, y: event.y };
|
|
93
95
|
this.potentialDeselect = segmentSelection;
|
|
94
96
|
|
|
@@ -104,6 +106,7 @@ export class SelectTool extends Tool {
|
|
|
104
106
|
|
|
105
107
|
// Prepare for drag immediately after selection
|
|
106
108
|
this.isDraggingStrokes = true;
|
|
109
|
+
this.hasSeparatedForDrag = false;
|
|
107
110
|
this.dragStartPoint = { x: event.x, y: event.y };
|
|
108
111
|
|
|
109
112
|
if (this.canvas.eventManager) {
|
|
@@ -171,6 +174,12 @@ export class SelectTool extends Tool {
|
|
|
171
174
|
|
|
172
175
|
// If we moved, it's a drag, so cancel potential deselect
|
|
173
176
|
this.potentialDeselect = null;
|
|
177
|
+
|
|
178
|
+
// Separate selected parts if needed
|
|
179
|
+
if (!this.hasSeparatedForDrag) {
|
|
180
|
+
this._separateSelectedParts();
|
|
181
|
+
this.hasSeparatedForDrag = true;
|
|
182
|
+
}
|
|
174
183
|
|
|
175
184
|
// Move all selected strokes
|
|
176
185
|
const movedStrokes = new Set();
|
|
@@ -775,9 +784,12 @@ export class SelectTool extends Tool {
|
|
|
775
784
|
selectionLayer.removeChild(selectionLayer.firstChild);
|
|
776
785
|
}
|
|
777
786
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
787
|
+
if (this.selectedSegments.size === 0) return;
|
|
788
|
+
|
|
789
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
790
|
+
let hasSelection = false;
|
|
791
|
+
|
|
792
|
+
// Calculate bounding box of all selected segments
|
|
781
793
|
for (const [strokeId, segmentSet] of this.selectedSegments.entries()) {
|
|
782
794
|
const stroke = this.canvas.strokes.get(strokeId);
|
|
783
795
|
if (!stroke || !stroke.points) continue;
|
|
@@ -788,24 +800,43 @@ export class SelectTool extends Tool {
|
|
|
788
800
|
const p1 = stroke.points[segmentIndex];
|
|
789
801
|
const p2 = stroke.points[segmentIndex + 1];
|
|
790
802
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
highlight.setAttribute('y2', p2.y);
|
|
797
|
-
highlight.setAttribute('stroke', omdColor.hiliteColor);
|
|
798
|
-
highlight.setAttribute('stroke-width', '4');
|
|
799
|
-
highlight.setAttribute('stroke-opacity', '0.8');
|
|
800
|
-
highlight.setAttribute('stroke-linecap', 'round');
|
|
801
|
-
highlight.style.pointerEvents = 'none';
|
|
802
|
-
highlight.classList.add('selection-highlight');
|
|
803
|
-
|
|
804
|
-
selectionLayer.appendChild(highlight);
|
|
805
|
-
highlightCount++;
|
|
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;
|
|
806
808
|
}
|
|
807
809
|
}
|
|
808
|
-
|
|
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;
|
|
822
|
+
|
|
823
|
+
// Create bounding box element
|
|
824
|
+
const box = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
825
|
+
box.setAttribute('x', minX);
|
|
826
|
+
box.setAttribute('y', minY);
|
|
827
|
+
box.setAttribute('width', width);
|
|
828
|
+
box.setAttribute('height', height);
|
|
829
|
+
box.setAttribute('fill', 'none');
|
|
830
|
+
box.setAttribute('stroke', '#007bff'); // Selection color
|
|
831
|
+
box.setAttribute('stroke-width', '1.5');
|
|
832
|
+
box.setAttribute('stroke-dasharray', '6, 4'); // Dotted/Dashed
|
|
833
|
+
box.setAttribute('stroke-opacity', '0.6'); // Light
|
|
834
|
+
box.setAttribute('rx', '8'); // Rounded corners
|
|
835
|
+
box.setAttribute('ry', '8');
|
|
836
|
+
box.style.pointerEvents = 'none';
|
|
837
|
+
box.classList.add('selection-bounds');
|
|
838
|
+
|
|
839
|
+
selectionLayer.appendChild(box);
|
|
809
840
|
}
|
|
810
841
|
|
|
811
842
|
/**
|
|
@@ -925,6 +956,66 @@ export class SelectTool extends Tool {
|
|
|
925
956
|
});
|
|
926
957
|
}
|
|
927
958
|
|
|
959
|
+
/**
|
|
960
|
+
* Separates selected segments into new strokes so they can be moved independently.
|
|
961
|
+
* @private
|
|
962
|
+
*/
|
|
963
|
+
_separateSelectedParts() {
|
|
964
|
+
const newSelection = new Map();
|
|
965
|
+
const strokesToProcess = Array.from(this.selectedSegments.entries());
|
|
966
|
+
|
|
967
|
+
for (const [strokeId, selectedIndices] of strokesToProcess) {
|
|
968
|
+
const stroke = this.canvas.strokes.get(strokeId);
|
|
969
|
+
if (!stroke || !stroke.points || stroke.points.length < 2) continue;
|
|
970
|
+
|
|
971
|
+
const totalSegments = stroke.points.length - 1;
|
|
972
|
+
|
|
973
|
+
// If fully selected, just keep it as is
|
|
974
|
+
if (selectedIndices.size === totalSegments) {
|
|
975
|
+
newSelection.set(strokeId, selectedIndices);
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// It's a partial selection - we need to split
|
|
980
|
+
const sortedSelectedIndices = Array.from(selectedIndices).sort((a, b) => a - b);
|
|
981
|
+
|
|
982
|
+
// Determine unselected indices
|
|
983
|
+
const unselectedIndices = [];
|
|
984
|
+
for (let i = 0; i < totalSegments; i++) {
|
|
985
|
+
if (!selectedIndices.has(i)) {
|
|
986
|
+
unselectedIndices.push(i);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Group segments
|
|
991
|
+
const selectedGroups = this._groupConsecutiveSegments(sortedSelectedIndices);
|
|
992
|
+
const unselectedGroups = this._groupConsecutiveSegments(unselectedIndices);
|
|
993
|
+
|
|
994
|
+
// Create new strokes for selected parts
|
|
995
|
+
selectedGroups.forEach(group => {
|
|
996
|
+
const newStroke = this._createStrokeFromSegments(stroke, group);
|
|
997
|
+
if (newStroke) {
|
|
998
|
+
// Add to new selection (all segments selected)
|
|
999
|
+
const newIndices = new Set();
|
|
1000
|
+
for (let i = 0; i < newStroke.points.length - 1; i++) {
|
|
1001
|
+
newIndices.add(i);
|
|
1002
|
+
}
|
|
1003
|
+
newSelection.set(newStroke.id, newIndices);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// Create new strokes for unselected parts (don't add to selection)
|
|
1008
|
+
unselectedGroups.forEach(group => {
|
|
1009
|
+
this._createStrokeFromSegments(stroke, group);
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
// Remove original stroke
|
|
1013
|
+
this.canvas.removeStroke(strokeId);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
this.selectedSegments = newSelection;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
928
1019
|
/**
|
|
929
1020
|
* Groups consecutive segment indices into separate arrays.
|
|
930
1021
|
* @private
|
|
@@ -958,9 +1049,10 @@ export class SelectTool extends Tool {
|
|
|
958
1049
|
/**
|
|
959
1050
|
* Creates a new stroke from a group of segment indices.
|
|
960
1051
|
* @private
|
|
1052
|
+
* @returns {Stroke|null} The newly created stroke
|
|
961
1053
|
*/
|
|
962
1054
|
_createStrokeFromSegments(originalStroke, segmentIndices) {
|
|
963
|
-
if (segmentIndices.length === 0) return;
|
|
1055
|
+
if (segmentIndices.length === 0) return null;
|
|
964
1056
|
|
|
965
1057
|
// Collect points for the new stroke
|
|
966
1058
|
const newPoints = [];
|
|
@@ -974,7 +1066,7 @@ export class SelectTool extends Tool {
|
|
|
974
1066
|
}
|
|
975
1067
|
|
|
976
1068
|
// Only create stroke if we have at least 2 points
|
|
977
|
-
if (newPoints.length < 2) return;
|
|
1069
|
+
if (newPoints.length < 2) return null;
|
|
978
1070
|
|
|
979
1071
|
// Create new stroke with same properties
|
|
980
1072
|
const newStroke = new Stroke({
|
|
@@ -991,6 +1083,7 @@ export class SelectTool extends Tool {
|
|
|
991
1083
|
|
|
992
1084
|
newStroke.finish();
|
|
993
1085
|
this.canvas.addStroke(newStroke);
|
|
1086
|
+
return newStroke;
|
|
994
1087
|
}
|
|
995
1088
|
|
|
996
1089
|
}
|