diff --git a/apps/web/src/components/editor/panels/timeline/index.tsx b/apps/web/src/components/editor/panels/timeline/index.tsx index ae9e19c9..20e9faf1 100644 --- a/apps/web/src/components/editor/panels/timeline/index.tsx +++ b/apps/web/src/components/editor/panels/timeline/index.tsx @@ -61,7 +61,6 @@ import { SELECTED_TRACK_ROW_CLASS } from "./theme"; import { computeTrackExpansionHeight, getTrackExpandedRows, - getExpansionHeight, getPropertyLabel, type ExpandedRow, } from "./expanded-layout"; @@ -75,6 +74,7 @@ import { TimelineBookmarksRow } from "./bookmarks"; import { useBookmarkDrag } from "@/hooks/timeline/use-bookmark-drag"; import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll"; import { useInitialScrollBottom } from "@/hooks/timeline/use-initial-scroll-bottom"; +import { useTimelineResize } from "@/hooks/timeline/use-timeline-resize"; import { useTimelineStore } from "@/stores/timeline-store"; import { useEditor } from "@/hooks/use-editor"; import { useTimelinePlayhead } from "@/hooks/timeline/use-timeline-playhead"; @@ -119,7 +119,9 @@ export function Timeline() { } = useElementSelection(); const editor = useEditor(); const timeline = editor.timeline; - const scene = useEditor((currentEditor) => currentEditor.scenes.getActiveSceneOrNull()); + const scene = useEditor((currentEditor) => + currentEditor.scenes.getActiveSceneOrNull(), + ); const tracks = useMemo( () => scene @@ -140,7 +142,6 @@ export function Timeline() { const playheadRef = useRef(null); const trackLabelsScrollRef = useRef(null); - const [isResizing, setIsResizing] = useState(false); const [currentSnapPoint, setCurrentSnapPoint] = useState( null, ); @@ -148,15 +149,6 @@ export function Timeline() { const handleSnapPointChange = useCallback((snapPoint: SnapPoint | null) => { setCurrentSnapPoint(snapPoint); }, []); - const handleResizeStateChange = useCallback( - ({ isResizing: nextIsResizing }: { isResizing: boolean }) => { - setIsResizing(nextIsResizing); - if (!nextIsResizing) { - setCurrentSnapPoint(null); - } - }, - [], - ); const timelineDuration = timeline.getTotalDuration() || 0; const minZoomLevel = getTimelineZoomMin({ @@ -176,6 +168,10 @@ export function Timeline() { tracksScrollRef, rulerScrollRef, }); + const { isResizing, handleResizeStart } = useTimelineResize({ + zoomLevel, + onSnapPointChange: handleSnapPointChange, + }); const expandedElementIds = useTimelineStore((s) => s.expandedElementIds); @@ -366,7 +362,10 @@ export function Timeline() { const containerWidth = tracksContainerRef.current?.clientWidth || FALLBACK_CONTAINER_WIDTH; - const contentWidth = timelineTimeToPixels({ time: timelineDuration, zoomLevel }); + const contentWidth = timelineTimeToPixels({ + time: timelineDuration, + zoomLevel, + }); const paddingPx = getTimelinePaddingPx({ containerWidth, zoomLevel, @@ -513,7 +512,10 @@ export function Timeline() { TRACKS_CONTAINER_HEIGHT.min, Math.min( TRACKS_CONTAINER_HEIGHT.max, - getTotalTracksHeight({ tracks, getExtraHeight: getTrackExpansionHeight }), + getTotalTracksHeight({ + tracks, + getExtraHeight: getTrackExpansionHeight, + }), ), ) + TIMELINE_CONTENT_TOP_PADDING_PX }px`, @@ -533,14 +535,13 @@ export function Timeline() { }} > {tracks.length > 0 && ( - { @@ -718,8 +719,7 @@ function TimelineTrackRows({ dragState, tracksScrollRef, lastMouseXRef, - onSnapPointChange, - onResizeStateChange, + onResizeStart, onElementMouseDown, onElementClick, onTrackMouseDown, @@ -733,8 +733,9 @@ function TimelineTrackRows({ dragState: ElementDragState; tracksScrollRef: React.RefObject; lastMouseXRef: React.RefObject; - onSnapPointChange: (snapPoint: SnapPoint | null) => void; - onResizeStateChange: (params: { isResizing: boolean }) => void; + onResizeStart: React.ComponentProps< + typeof TimelineTrackContent + >["onResizeStart"]; onElementMouseDown: React.ComponentProps< typeof TimelineTrackContent >["onElementMouseDown"]; @@ -798,8 +799,7 @@ function TimelineTrackRows({
- {rows.map((row, index) => ( + {rows.map((row) => (
diff --git a/apps/web/src/components/editor/panels/timeline/timeline-element.tsx b/apps/web/src/components/editor/panels/timeline/timeline-element.tsx index a3555e94..3ebd3efa 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-element.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-element.tsx @@ -10,7 +10,6 @@ import { } from "@/hooks/timeline/element/use-keyframe-drag"; import { useKeyframeSelection } from "@/hooks/timeline/element/use-keyframe-selection"; import { useKeyframeBoxSelect } from "@/hooks/timeline/element/use-keyframe-box-select"; -import { useTimelineElementResize } from "@/hooks/timeline/element/use-element-resize"; import { SelectionBox } from "@/lib/selection/selection-box"; import { getElementKeyframes } from "@/lib/animation"; import { @@ -86,7 +85,6 @@ import { getExpansionHeight, type ExpandedRow, } from "./expanded-layout"; -import type { SnapPoint } from "@/lib/timeline/snap-utils"; const KEYFRAME_INDICATOR_MIN_WIDTH_PX = 40; const ELEMENT_RING_WIDTH_PX = 1.5; @@ -197,8 +195,12 @@ interface TimelineElementProps { track: TimelineTrack; zoomLevel: number; isSelected: boolean; - onSnapPointChange?: (snapPoint: SnapPoint | null) => void; - onResizeStateChange?: (params: { isResizing: boolean }) => void; + onResizeStart: (params: { + event: React.MouseEvent; + element: TimelineElementType; + track: TimelineTrack; + side: "left" | "right"; + }) => void; onElementMouseDown: ( event: React.MouseEvent, element: TimelineElementType, @@ -216,8 +218,7 @@ export function TimelineElement({ track, zoomLevel, isSelected, - onSnapPointChange, - onResizeStateChange, + onResizeStart, onElementMouseDown, onElementClick, dragState, @@ -231,18 +232,6 @@ export function TimelineElement({ elementId: element.id, fallback: element, }); - const { - currentDuration, - currentStartTime, - handleResizeStart, - isResizing, - } = useTimelineElementResize({ - element, - track, - zoomLevel, - onSnapPointChange, - onResizeStateChange, - }); let mediaAsset: MediaAsset | null = null; @@ -267,13 +256,9 @@ export function TimelineElement({ const elementStartTime = isBeingDragged && dragState.isDragging ? dragState.currentTime + dragTimeOffset - : isResizing - ? currentStartTime - : renderElement.startTime; + : renderElement.startTime; const displayedStartTime = elementStartTime; - const displayedDuration = isResizing - ? currentDuration - : renderElement.duration; + const displayedDuration = renderElement.duration; const elementWidth = timelineTimeToPixels({ time: displayedDuration, zoomLevel, @@ -282,22 +267,6 @@ export function TimelineElement({ time: displayedStartTime, zoomLevel, }); - const handleElementResizeStart = ({ - event, - element, - side, - }: { - event: React.MouseEvent; - element: TimelineElementType; - track: TimelineTrack; - side: "left" | "right"; - }) => { - handleResizeStart({ - event, - elementId: element.id, - side, - }); - }; const keyframeIndicators = isSelected ? getKeyframeIndicators({ keyframes: getElementKeyframes({ animations: element.animations }), @@ -329,9 +298,7 @@ export function TimelineElement({ ); const expandedRows = useMemo( () => - isExpanded - ? getExpandedRows({ animations: element.animations }) - : [], + isExpanded ? getExpandedRows({ animations: element.animations }) : [], [isExpanded, element.animations], ); @@ -422,7 +389,7 @@ export function TimelineElement({ expandedContent={expandedContent} onElementClick={onElementClick} onElementMouseDown={onElementMouseDown} - onResizeStart={handleElementResizeStart} + onResizeStart={onResizeStart} isDropTarget={isDropTarget} /> {isSelected && ( @@ -811,8 +778,7 @@ function ExpandedKeyframeLanes({ [...keyframes] .sort( (a, b) => - a.time - b.time || - a.propertyPath.localeCompare(b.propertyPath), + a.time - b.time || a.propertyPath.localeCompare(b.propertyPath), ) .map((kf) => ({ trackId, @@ -824,6 +790,8 @@ function ExpandedKeyframeLanes({ ); return ( + // biome-ignore lint/a11y/noStaticElementInteractions: expanded keyframe lanes are a pointer-only editing surface + // biome-ignore lint/a11y/useKeyWithClickEvents: expanded keyframe lanes are a pointer-only editing surface
{laneKeyframes.map((kf) => { @@ -849,8 +815,9 @@ function ExpandedKeyframeLanes({ propertyPath: row.propertyPath, keyframeId: kf.id, }; - const isBeingDragged = - keyframeDragState.draggingKeyframeIds.has(kf.id); + const isBeingDragged = keyframeDragState.draggingKeyframeIds.has( + kf.id, + ); const kfLeft = timelineTimeToSnappedPixels({ time: displayedStartTime + kf.time, zoomLevel, @@ -898,9 +865,7 @@ function ExpandedKeyframeLanes({ icon={KeyframeIcon} className={cn( "size-3.5 text-black mr-1", - isSelected - ? "fill-primary" - : "fill-white", + isSelected ? "fill-primary" : "fill-white", )} strokeWidth={1.5} /> diff --git a/apps/web/src/components/editor/panels/timeline/timeline-track.tsx b/apps/web/src/components/editor/panels/timeline/timeline-track.tsx index 888e1496..32b1b93d 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-track.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-track.tsx @@ -1,129 +1,132 @@ -"use client"; - -import { useElementSelection } from "@/hooks/timeline/element/use-element-selection"; -import { TimelineElement } from "./timeline-element"; -import type { TimelineTrack } from "@/lib/timeline"; -import type { TimelineElement as TimelineElementType } from "@/lib/timeline"; -import type { SnapPoint } from "@/lib/timeline/snap-utils"; -import { TIMELINE_LAYERS } from "./layers"; -import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/lib/timeline/scale"; -import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll"; -import type { ElementDragState } from "@/lib/timeline"; -import { useEditor } from "@/hooks/use-editor"; - -interface TimelineTrackContentProps { - track: TimelineTrack; - zoomLevel: number; - dragState: ElementDragState; - rulerScrollRef: React.RefObject; - tracksScrollRef: React.RefObject; - lastMouseXRef: React.RefObject; - onSnapPointChange?: (snapPoint: SnapPoint | null) => void; - onResizeStateChange?: (params: { isResizing: boolean }) => void; - onElementMouseDown: (params: { - event: React.MouseEvent; - element: TimelineElementType; - track: TimelineTrack; - }) => void; - onElementClick: (params: { - event: React.MouseEvent; - element: TimelineElementType; - track: TimelineTrack; - }) => void; - onTrackMouseDown?: (event: React.MouseEvent) => void; - onTrackMouseUp?: (event: React.MouseEvent) => void; - shouldIgnoreClick?: () => boolean; - targetElementId?: string | null; -} - -export function TimelineTrackContent({ - track, - zoomLevel, - dragState, - rulerScrollRef, - tracksScrollRef, - lastMouseXRef, - onSnapPointChange, - onResizeStateChange, - onElementMouseDown, - onElementClick, - onTrackMouseDown, - onTrackMouseUp, - shouldIgnoreClick, - targetElementId = null, -}: TimelineTrackContentProps) { - const { isElementSelected } = useElementSelection(); - const duration = useEditor((e) => e.timeline.getTotalDuration()); - - useEdgeAutoScroll({ - isActive: dragState.isDragging, - getMouseClientX: () => lastMouseXRef.current ?? 0, - rulerScrollRef, - tracksScrollRef, - contentWidth: duration * BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel, - }); - - return ( -
-