diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-02 13:13:07 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-02 13:13:07 +0100 |
| commit | e7dcb586c0408255a0bf788e355fdb6d50d9b923 (patch) | |
| tree | 74ff0fc0f1ba501a075342db9b4e454450926154 /apps/web/src/hooks/timeline/element/use-element-interaction.ts | |
| parent | 93bea01c9ed3fcbebc64c9cef382a51d24c925ed (diff) | |
| download | opencut-e7dcb586c0408255a0bf788e355fdb6d50d9b923.tar.gz | |
feat: Clip effects, asset sorting, and timeline improvements
Major features and improvements:
* **Clip Effects**:
* Added UI in Properties Panel to manage effects on video/image clips (add, remove, toggle, reorder).
* Implemented dynamic parameter fields for effects.
* Added support for keyframing effect parameters.
* **Assets Panel**:
* Added sorting options: Name, Type, Duration, and File Size.
* Persisted view preferences (grid/list mode, sort order) to local storage.
* Refactored media item rendering and drag interactions.
* **Timeline & Interaction**:
* **Keyframe Dragging**: Added ability to drag keyframes directly on the timeline element.
* **Resizing**: Improved resize logic to respect neighboring clips (prevents overlaps).
* **Visuals**: Implemented tiled background rendering for video/image clips on the timeline.
* **Shortcuts**: Added "Deselect All" action bound to the `Escape` key.
* **Fixes**: Corrected drag-and-drop coordinate calculations when the timeline track area is scrolled.
* **Text Elements**:
* Refactored text background storage to use an explicit `enabled` flag.
* Added `V8toV9` storage migration to update existing projects.
* **Architecture**:
* Moved export state management to `ProjectManager` for better lifecycle handling.
* Refactored `PropertiesPanel` sections to be more composable (custom headers, borders).
Diffstat (limited to 'apps/web/src/hooks/timeline/element/use-element-interaction.ts')
| -rw-r--r-- | apps/web/src/hooks/timeline/element/use-element-interaction.ts | 65 |
1 files changed, 36 insertions, 29 deletions
diff --git a/apps/web/src/hooks/timeline/element/use-element-interaction.ts b/apps/web/src/hooks/timeline/element/use-element-interaction.ts index 3831a86f..ea01c718 100644 --- a/apps/web/src/hooks/timeline/element/use-element-interaction.ts +++ b/apps/web/src/hooks/timeline/element/use-element-interaction.ts @@ -8,6 +8,7 @@ import { } from "react"; import { useEditor } from "@/hooks/use-editor"; import { useShiftKey } from "@/hooks/use-shift-key"; +import { useTimelineStore } from "@/stores/timeline-store"; import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; import { DRAG_THRESHOLD_PX, @@ -38,6 +39,8 @@ interface UseElementInteractionProps { onSnapPointChange?: (snapPoint: SnapPoint | null) => void; } +const MOUSE_BUTTON_RIGHT = 2; + const initialDragState: ElementDragState = { isDragging: false, elementId: null, @@ -162,6 +165,7 @@ export function useElementInteraction({ onSnapPointChange, }: UseElementInteractionProps) { const editor = useEditor(); + const rippleEditingEnabled = useTimelineStore((s) => s.rippleEditingEnabled); const isShiftHeldRef = useShiftKey(); const tracks = editor.timeline.getTracks(); const { @@ -441,27 +445,33 @@ export function useElementInteraction({ return; } - if (dropTarget.isNewTrack) { - const newTrackId = generateUUID(); + if (dropTarget.isNewTrack) { + const newTrackId = generateUUID(); + editor.timeline.moveElement({ + sourceTrackId: dragState.trackId, + targetTrackId: newTrackId, + elementId: dragState.elementId, + newStartTime: snappedTime, + createTrack: { type: sourceTrack.type, index: dropTarget.trackIndex }, + rippleEnabled: rippleEditingEnabled, + }); + selectElement({ trackId: newTrackId, elementId: dragState.elementId }); + } else { + const targetTrack = tracks[dropTarget.trackIndex]; + if (targetTrack) { editor.timeline.moveElement({ sourceTrackId: dragState.trackId, - targetTrackId: newTrackId, + targetTrackId: targetTrack.id, elementId: dragState.elementId, newStartTime: snappedTime, - createTrack: { type: sourceTrack.type, index: dropTarget.trackIndex }, + rippleEnabled: rippleEditingEnabled, }); - } else { - const targetTrack = tracks[dropTarget.trackIndex]; - if (targetTrack) { - editor.timeline.moveElement({ - sourceTrackId: dragState.trackId, - targetTrackId: targetTrack.id, - elementId: dragState.elementId, - newStartTime: snappedTime, - }); + if (targetTrack.id !== dragState.trackId) { + selectElement({ trackId: targetTrack.id, elementId: dragState.elementId }); } } + } endDrag(); onSnapPointChange?.(null); @@ -483,6 +493,8 @@ export function useElementInteraction({ tracksContainerRef, tracksScrollRef, headerRef, + rippleEditingEnabled, + selectElement, ]); useEffect(() => { @@ -508,10 +520,10 @@ export function useElementInteraction({ element: TimelineElement; track: TimelineTrack; }) => { - const isRightClick = event.button === 2; + const isRightClick = event.button === MOUSE_BUTTON_RIGHT; - // right-click: don't stop propagation so ContextMenu can open - if (isRightClick) { + // right-click: don't stop propagation so ContextMenu can open + if (isRightClick) { const alreadySelected = isElementSelected({ trackId: track.id, elementId: element.id, @@ -526,14 +538,12 @@ export function useElementInteraction({ return; } - // left-click: stop propagation for drag operations - event.stopPropagation(); - mouseDownLocationRef.current = { x: event.clientX, y: event.clientY }; + event.stopPropagation(); + mouseDownLocationRef.current = { x: event.clientX, y: event.clientY }; - const isMultiSelect = event.metaKey || event.ctrlKey || event.shiftKey; + const isMultiSelect = event.metaKey || event.ctrlKey || event.shiftKey; - // multi-select: toggle selection - if (isMultiSelect) { + if (isMultiSelect) { handleSelectionClick({ trackId: track.id, elementId: element.id, @@ -541,8 +551,7 @@ export function useElementInteraction({ }); } - // start drag - const clickOffsetTime = getClickOffsetTime({ + const clickOffsetTime = getClickOffsetTime({ clientX: event.clientX, elementRect: event.currentTarget.getBoundingClientRect(), zoomLevel, @@ -570,10 +579,9 @@ export function useElementInteraction({ element: TimelineElement; track: TimelineTrack; }) => { - event.stopPropagation(); + event.stopPropagation(); - // was it a drag or a click? - if (mouseDownLocationRef.current) { + if (mouseDownLocationRef.current) { const deltaX = Math.abs(event.clientX - mouseDownLocationRef.current.x); const deltaY = Math.abs(event.clientY - mouseDownLocationRef.current.y); if (deltaX > DRAG_THRESHOLD_PX || deltaY > DRAG_THRESHOLD_PX) { @@ -585,8 +593,7 @@ export function useElementInteraction({ // modifier keys already handled in mousedown if (event.metaKey || event.ctrlKey || event.shiftKey) return; - // single click: select if not selected - const alreadySelected = isElementSelected({ + const alreadySelected = isElementSelected({ trackId: track.id, elementId: element.id, }); |
