diff --git a/app/templates/index.html b/app/templates/index.html
index 0fa7309..b769015 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -363,7 +363,7 @@
canvasRef.current.style.cursor = isOverClip ? 'cell' : 'not-allowed';
} else {
// select tool
- canvasRef.current.style.cursor = (isOverClip && e.altKey) ? 'grab' : 'crosshair';
+ canvasRef.current.style.cursor = (isOverClip && (e.altKey || e.ctrlKey)) ? 'grab' : 'crosshair';
}
}}
onMouseDown={(e) => {
@@ -401,7 +401,7 @@
e.preventDefault();
e.stopPropagation();
if (onClipDragStart) {
- onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime);
+ onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime, e.ctrlKey);
}
} else {
onPlayheadSet(time);
@@ -409,12 +409,12 @@
return;
}
- // Check for click drag clip (no Ctrl key required if select tool is in fallback ctrl-mode)
- if (clickedClip && e.ctrlKey) {
+ // Check for click drag clip (Alt to move, Ctrl to duplicate)
+ if (clickedClip && (e.altKey || e.ctrlKey)) {
e.preventDefault();
e.stopPropagation();
if (onClipDragStart) {
- onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime);
+ onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime, e.ctrlKey);
}
return;
}
@@ -1710,25 +1710,64 @@
const captureTrackSnapshotRef = useRef(null);
captureTrackSnapshotRef.current = captureTrackSnapshot;
- const handleClipDragStart = (trackId, clipId, clickOffset) => {
+ const handleClipDragStart = (trackId, clipId, clickOffset, isDuplicate = false) => {
const track = tracks.find(t => t.id === trackId);
if (!track) return;
- const clips = track.clips && track.clips.length > 0 ? track.clips : (track.buffer ? [{
- id: 'default',
+ const existingClips = track.clips && track.clips.length > 0 ? track.clips : (track.buffer ? [{
+ id: 'default_' + track.id,
buffer: track.buffer,
startTime: track.startTime || 0,
name: track.name
}] : []);
- const clip = clips.find(c => c.id === clipId);
+ const clip = existingClips.find(c => c.id === clipId || (clipId === 'default' && c.id === 'default_' + track.id));
if (!clip) return;
const beforeSnap = captureTrackSnapshot(trackId);
+ let targetClipId = clip.id;
+
+ if (isDuplicate) {
+ const cloneId = clip.id + '_copy_' + Date.now();
+ const clone = {
+ ...clip,
+ id: cloneId,
+ name: clip.name + ' (Copy)'
+ };
+
+ setTracks(prev => prev.map(t => {
+ if (t.id === trackId) {
+ const newClips = [...existingClips, clone];
+ return {
+ ...t,
+ clips: newClips,
+ buffer: newClips[0].buffer,
+ startTime: newClips[0].startTime,
+ name: newClips[0].name
+ };
+ }
+ return t;
+ }));
+
+ targetClipId = cloneId;
+ } else {
+ if (!track.clips || track.clips.length === 0) {
+ setTracks(prev => prev.map(t => {
+ if (t.id === trackId) {
+ return {
+ ...t,
+ clips: existingClips
+ };
+ }
+ return t;
+ }));
+ }
+ }
+
setDraggedClip({
trackId: trackId,
- clipId: clipId,
+ clipId: targetClipId,
clickOffset: clickOffset,
buffer: clip.buffer,
name: clip.name,
@@ -2789,7 +2828,7 @@