feat: Ctrl+click deselect + global empty-area sweep
- Ctrl+click on selected item: deselect that item only - Ctrl+click on empty space (via sweep start): deselect all items - Ctrl+drag on empty space BETWEEN or BELOW tracks: creates sweep overlay scanning ALL tracks for intersecting items - onDeselectItem handler + prop wired through WaveformLane - Global onMouseDown on tracks container div for empty-area sweep
This commit is contained in:
+39
-3
@@ -387,6 +387,7 @@ const WaveformLane = ({
|
|||||||
currentTime,
|
currentTime,
|
||||||
getLocalAnchor,
|
getLocalAnchor,
|
||||||
onClearLocalSelection,
|
onClearLocalSelection,
|
||||||
|
onDeselectItem,
|
||||||
onSetSelectionMode,
|
onSetSelectionMode,
|
||||||
onSetSelectionStart,
|
onSetSelectionStart,
|
||||||
onSetSelectionEnd,
|
onSetSelectionEnd,
|
||||||
@@ -1079,7 +1080,11 @@ const WaveformLane = ({
|
|||||||
if (hitItem && !e.altKey && !e.shiftKey) {
|
if (hitItem && !e.altKey && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (onSectionItemDragStart) onSectionItemDragStart(track.id, hitItem.type, hitItem.id, time - hitItem.start, e.ctrlKey);
|
if (e.ctrlKey && selectedItemIds && selectedItemIds.has(hitItem.id)) {
|
||||||
|
if (onDeselectItem) onDeselectItem(hitItem.id);
|
||||||
|
} else {
|
||||||
|
if (onSectionItemDragStart) onSectionItemDragStart(track.id, hitItem.type, hitItem.id, time - hitItem.start, e.ctrlKey);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1134,13 +1139,20 @@ const WaveformLane = ({
|
|||||||
if (clickedClip && (e.altKey || e.ctrlKey)) {
|
if (clickedClip && (e.altKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (e.ctrlKey && selectedItemIds) {
|
||||||
|
var clipCanonicalId = clickedClip.id === 'default' ? 'default_' + track.id : clickedClip.id;
|
||||||
|
if (selectedItemIds.has(clipCanonicalId)) {
|
||||||
|
if (onDeselectItem) onDeselectItem(clipCanonicalId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (onClipDragStart) {
|
if (onClipDragStart) {
|
||||||
onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime, e.ctrlKey);
|
onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime, e.ctrlKey);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Click on empty space: start sweep select
|
// Ctrl+Click on empty space: sweep select (deselect all via onSweepSelectStart)
|
||||||
if (e.ctrlKey && !clickedClip && !hitItem) {
|
if (e.ctrlKey && !clickedClip && !hitItem) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -11620,6 +11632,15 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
}, [zoom]);
|
}, [zoom]);
|
||||||
|
|
||||||
|
// ── Deselect single item ──
|
||||||
|
const handleDeselectItem = itemId => {
|
||||||
|
setSelectedItemIds(prev => {
|
||||||
|
var next = new Set(prev);
|
||||||
|
next.delete(itemId);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// ── Sweep Select ──
|
// ── Sweep Select ──
|
||||||
const handleSweepSelectStart = (trackId, startTime) => {
|
const handleSweepSelectStart = (trackId, startTime) => {
|
||||||
isSweepingRef.current = true;
|
isSweepingRef.current = true;
|
||||||
@@ -11902,7 +11923,9 @@ const App = () => {
|
|||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
if (!isSweepingRef.current) return;
|
if (!isSweepingRef.current) return;
|
||||||
isSweepingRef.current = false;
|
isSweepingRef.current = false;
|
||||||
|
sweepTrackIdRef.current = null;
|
||||||
const sweep = sweepSelectRef.current;
|
const sweep = sweepSelectRef.current;
|
||||||
|
sweepSelectRef.current = null;
|
||||||
if (sweep) {
|
if (sweep) {
|
||||||
const start = Math.min(sweep.startTime, sweep.endTime);
|
const start = Math.min(sweep.startTime, sweep.endTime);
|
||||||
const end = Math.max(sweep.startTime, sweep.endTime);
|
const end = Math.max(sweep.startTime, sweep.endTime);
|
||||||
@@ -16456,7 +16479,19 @@ const App = () => {
|
|||||||
borderRight: '1px solid #f59e0b'
|
borderRight: '1px solid #f59e0b'
|
||||||
}
|
}
|
||||||
})), /*#__PURE__*/React.createElement("div", {
|
})), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex-1 flex flex-col divide-y divide-[#141414] relative bg-[#111111] min-h-full"
|
className: "flex-1 flex flex-col divide-y divide-[#141414] relative bg-[#111111] min-h-full",
|
||||||
|
onMouseDown: e => {
|
||||||
|
if (e.ctrlKey && !e.shiftKey && !e.altKey && e.button === 0) {
|
||||||
|
const wrapper = timelineWrapperRef.current;
|
||||||
|
if (wrapper) {
|
||||||
|
const rect = wrapper.getBoundingClientRect();
|
||||||
|
const sl = wrapper.scrollLeft;
|
||||||
|
const raw = Math.max(0, (e.clientX - rect.left + sl) / zoom - leadInMargin);
|
||||||
|
const time = snapValue !== 'free' ? snapTime(raw, snapValue, bpm) : raw;
|
||||||
|
handleSweepSelectStart(null, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}, activeTracks.map((track, idx) => {
|
}, activeTracks.map((track, idx) => {
|
||||||
const isSelected = selectedTrackId === track.id;
|
const isSelected = selectedTrackId === track.id;
|
||||||
const autoHeight = track.height || (track.isArmed ? 164 : 140);
|
const autoHeight = track.height || (track.isArmed ? 164 : 140);
|
||||||
@@ -16486,6 +16521,7 @@ const App = () => {
|
|||||||
markers: track.markers,
|
markers: track.markers,
|
||||||
onTrackLaneMouseDown: handleTrackLaneMouseDown,
|
onTrackLaneMouseDown: handleTrackLaneMouseDown,
|
||||||
onSweepSelectStart: handleSweepSelectStart,
|
onSweepSelectStart: handleSweepSelectStart,
|
||||||
|
onDeselectItem: handleDeselectItem,
|
||||||
onContextMenu: handleContextMenu,
|
onContextMenu: handleContextMenu,
|
||||||
onClipDragStart: handleClipDragStart,
|
onClipDragStart: handleClipDragStart,
|
||||||
onClipStretchStart: handleClipStretchStart,
|
onClipStretchStart: handleClipStretchStart,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user