Skip to content

Commit ac43a5a

Browse files
committed
feat: move/resize multiple elements
1 parent 6312fc5 commit ac43a5a

File tree

21 files changed

+2219
-958
lines changed

21 files changed

+2219
-958
lines changed

apps/web/src/components/editor/panels/timeline/index.tsx

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import { SELECTED_TRACK_ROW_CLASS } from "./theme";
6161
import {
6262
computeTrackExpansionHeight,
6363
getTrackExpandedRows,
64-
getExpansionHeight,
6564
getPropertyLabel,
6665
type ExpandedRow,
6766
} from "./expanded-layout";
@@ -75,6 +74,7 @@ import { TimelineBookmarksRow } from "./bookmarks";
7574
import { useBookmarkDrag } from "@/hooks/timeline/use-bookmark-drag";
7675
import { useEdgeAutoScroll } from "@/hooks/timeline/use-edge-auto-scroll";
7776
import { useInitialScrollBottom } from "@/hooks/timeline/use-initial-scroll-bottom";
77+
import { useTimelineResize } from "@/hooks/timeline/use-timeline-resize";
7878
import { useTimelineStore } from "@/stores/timeline-store";
7979
import { useEditor } from "@/hooks/use-editor";
8080
import { useTimelinePlayhead } from "@/hooks/timeline/use-timeline-playhead";
@@ -119,7 +119,9 @@ export function Timeline() {
119119
} = useElementSelection();
120120
const editor = useEditor();
121121
const timeline = editor.timeline;
122-
const scene = useEditor((currentEditor) => currentEditor.scenes.getActiveSceneOrNull());
122+
const scene = useEditor((currentEditor) =>
123+
currentEditor.scenes.getActiveSceneOrNull(),
124+
);
123125
const tracks = useMemo<TimelineTrack[]>(
124126
() =>
125127
scene
@@ -140,23 +142,13 @@ export function Timeline() {
140142
const playheadRef = useRef<HTMLDivElement>(null);
141143
const trackLabelsScrollRef = useRef<HTMLDivElement>(null);
142144

143-
const [isResizing, setIsResizing] = useState(false);
144145
const [currentSnapPoint, setCurrentSnapPoint] = useState<SnapPoint | null>(
145146
null,
146147
);
147148

148149
const handleSnapPointChange = useCallback((snapPoint: SnapPoint | null) => {
149150
setCurrentSnapPoint(snapPoint);
150151
}, []);
151-
const handleResizeStateChange = useCallback(
152-
({ isResizing: nextIsResizing }: { isResizing: boolean }) => {
153-
setIsResizing(nextIsResizing);
154-
if (!nextIsResizing) {
155-
setCurrentSnapPoint(null);
156-
}
157-
},
158-
[],
159-
);
160152

161153
const timelineDuration = timeline.getTotalDuration() || 0;
162154
const minZoomLevel = getTimelineZoomMin({
@@ -176,6 +168,10 @@ export function Timeline() {
176168
tracksScrollRef,
177169
rulerScrollRef,
178170
});
171+
const { isResizing, handleResizeStart } = useTimelineResize({
172+
zoomLevel,
173+
onSnapPointChange: handleSnapPointChange,
174+
});
179175

180176
const expandedElementIds = useTimelineStore((s) => s.expandedElementIds);
181177

@@ -366,7 +362,10 @@ export function Timeline() {
366362

367363
const containerWidth =
368364
tracksContainerRef.current?.clientWidth || FALLBACK_CONTAINER_WIDTH;
369-
const contentWidth = timelineTimeToPixels({ time: timelineDuration, zoomLevel });
365+
const contentWidth = timelineTimeToPixels({
366+
time: timelineDuration,
367+
zoomLevel,
368+
});
370369
const paddingPx = getTimelinePaddingPx({
371370
containerWidth,
372371
zoomLevel,
@@ -513,7 +512,10 @@ export function Timeline() {
513512
TRACKS_CONTAINER_HEIGHT.min,
514513
Math.min(
515514
TRACKS_CONTAINER_HEIGHT.max,
516-
getTotalTracksHeight({ tracks, getExtraHeight: getTrackExpansionHeight }),
515+
getTotalTracksHeight({
516+
tracks,
517+
getExtraHeight: getTrackExpansionHeight,
518+
}),
517519
),
518520
) + TIMELINE_CONTENT_TOP_PADDING_PX
519521
}px`,
@@ -533,14 +535,13 @@ export function Timeline() {
533535
}}
534536
>
535537
{tracks.length > 0 && (
536-
<TimelineTrackRows
537-
mainTrackId={mainTrackId}
538+
<TimelineTrackRows
539+
mainTrackId={mainTrackId}
538540
zoomLevel={zoomLevel}
539541
dragState={dragState}
540542
tracksScrollRef={tracksScrollRef}
541543
lastMouseXRef={lastMouseXRef}
542-
onSnapPointChange={handleSnapPointChange}
543-
onResizeStateChange={handleResizeStateChange}
544+
onResizeStart={handleResizeStart}
544545
onElementMouseDown={handleElementMouseDown}
545546
onElementClick={handleElementClick}
546547
onTrackMouseDown={(event) => {
@@ -718,8 +719,7 @@ function TimelineTrackRows({
718719
dragState,
719720
tracksScrollRef,
720721
lastMouseXRef,
721-
onSnapPointChange,
722-
onResizeStateChange,
722+
onResizeStart,
723723
onElementMouseDown,
724724
onElementClick,
725725
onTrackMouseDown,
@@ -733,8 +733,9 @@ function TimelineTrackRows({
733733
dragState: ElementDragState;
734734
tracksScrollRef: React.RefObject<HTMLDivElement | null>;
735735
lastMouseXRef: React.RefObject<number>;
736-
onSnapPointChange: (snapPoint: SnapPoint | null) => void;
737-
onResizeStateChange: (params: { isResizing: boolean }) => void;
736+
onResizeStart: React.ComponentProps<
737+
typeof TimelineTrackContent
738+
>["onResizeStart"];
738739
onElementMouseDown: React.ComponentProps<
739740
typeof TimelineTrackContent
740741
>["onElementMouseDown"];
@@ -798,8 +799,7 @@ function TimelineTrackRows({
798799
<div
799800
className={cn(
800801
"absolute right-0 left-0 transition-colors",
801-
tracksWithSelection.has(track.id) &&
802-
SELECTED_TRACK_ROW_CLASS,
802+
tracksWithSelection.has(track.id) && SELECTED_TRACK_ROW_CLASS,
803803
)}
804804
style={{
805805
top: `${TIMELINE_CONTENT_TOP_PADDING_PX + getCumulativeHeightBefore({ tracks, trackIndex: index, getExtraHeight: getTrackExpansionHeight })}px`,
@@ -813,8 +813,7 @@ function TimelineTrackRows({
813813
rulerScrollRef={tracksScrollRef}
814814
tracksScrollRef={tracksScrollRef}
815815
lastMouseXRef={lastMouseXRef}
816-
onSnapPointChange={onSnapPointChange}
817-
onResizeStateChange={onResizeStateChange}
816+
onResizeStart={onResizeStart}
818817
onElementMouseDown={onElementMouseDown}
819818
onElementClick={onElementClick}
820819
onTrackMouseDown={onTrackMouseDown}
@@ -929,12 +928,10 @@ function TrackToggleIcon({
929928
function PropertyTree({ rows }: { rows: ExpandedRow[] }) {
930929
return (
931930
<div className="flex flex-col overflow-hidden">
932-
{rows.map((row, index) => (
931+
{rows.map((row) => (
933932
<div
934933
key={row.propertyPath}
935-
className={cn(
936-
"flex shrink-0 items-center px-3 bg-muted/50",
937-
)}
934+
className={cn("flex shrink-0 items-center px-3 bg-muted/50")}
938935
style={{ height: `${KEYFRAME_LANE_HEIGHT_PX}px` }}
939936
>
940937
<span className="text-muted-foreground truncate text-xs leading-none">

apps/web/src/components/editor/panels/timeline/timeline-element.tsx

Lines changed: 19 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
} from "@/hooks/timeline/element/use-keyframe-drag";
1111
import { useKeyframeSelection } from "@/hooks/timeline/element/use-keyframe-selection";
1212
import { useKeyframeBoxSelect } from "@/hooks/timeline/element/use-keyframe-box-select";
13-
import { useTimelineElementResize } from "@/hooks/timeline/element/use-element-resize";
1413
import { SelectionBox } from "@/lib/selection/selection-box";
1514
import { getElementKeyframes } from "@/lib/animation";
1615
import {
@@ -86,7 +85,6 @@ import {
8685
getExpansionHeight,
8786
type ExpandedRow,
8887
} from "./expanded-layout";
89-
import type { SnapPoint } from "@/lib/timeline/snap-utils";
9088

9189
const KEYFRAME_INDICATOR_MIN_WIDTH_PX = 40;
9290
const ELEMENT_RING_WIDTH_PX = 1.5;
@@ -197,8 +195,12 @@ interface TimelineElementProps {
197195
track: TimelineTrack;
198196
zoomLevel: number;
199197
isSelected: boolean;
200-
onSnapPointChange?: (snapPoint: SnapPoint | null) => void;
201-
onResizeStateChange?: (params: { isResizing: boolean }) => void;
198+
onResizeStart: (params: {
199+
event: React.MouseEvent;
200+
element: TimelineElementType;
201+
track: TimelineTrack;
202+
side: "left" | "right";
203+
}) => void;
202204
onElementMouseDown: (
203205
event: React.MouseEvent,
204206
element: TimelineElementType,
@@ -216,8 +218,7 @@ export function TimelineElement({
216218
track,
217219
zoomLevel,
218220
isSelected,
219-
onSnapPointChange,
220-
onResizeStateChange,
221+
onResizeStart,
221222
onElementMouseDown,
222223
onElementClick,
223224
dragState,
@@ -231,18 +232,6 @@ export function TimelineElement({
231232
elementId: element.id,
232233
fallback: element,
233234
});
234-
const {
235-
currentDuration,
236-
currentStartTime,
237-
handleResizeStart,
238-
isResizing,
239-
} = useTimelineElementResize({
240-
element,
241-
track,
242-
zoomLevel,
243-
onSnapPointChange,
244-
onResizeStateChange,
245-
});
246235

247236
let mediaAsset: MediaAsset | null = null;
248237

@@ -267,13 +256,9 @@ export function TimelineElement({
267256
const elementStartTime =
268257
isBeingDragged && dragState.isDragging
269258
? dragState.currentTime + dragTimeOffset
270-
: isResizing
271-
? currentStartTime
272-
: renderElement.startTime;
259+
: renderElement.startTime;
273260
const displayedStartTime = elementStartTime;
274-
const displayedDuration = isResizing
275-
? currentDuration
276-
: renderElement.duration;
261+
const displayedDuration = renderElement.duration;
277262
const elementWidth = timelineTimeToPixels({
278263
time: displayedDuration,
279264
zoomLevel,
@@ -282,22 +267,6 @@ export function TimelineElement({
282267
time: displayedStartTime,
283268
zoomLevel,
284269
});
285-
const handleElementResizeStart = ({
286-
event,
287-
element,
288-
side,
289-
}: {
290-
event: React.MouseEvent;
291-
element: TimelineElementType;
292-
track: TimelineTrack;
293-
side: "left" | "right";
294-
}) => {
295-
handleResizeStart({
296-
event,
297-
elementId: element.id,
298-
side,
299-
});
300-
};
301270
const keyframeIndicators = isSelected
302271
? getKeyframeIndicators({
303272
keyframes: getElementKeyframes({ animations: element.animations }),
@@ -329,9 +298,7 @@ export function TimelineElement({
329298
);
330299
const expandedRows = useMemo(
331300
() =>
332-
isExpanded
333-
? getExpandedRows({ animations: element.animations })
334-
: [],
301+
isExpanded ? getExpandedRows({ animations: element.animations }) : [],
335302
[isExpanded, element.animations],
336303
);
337304

@@ -422,7 +389,7 @@ export function TimelineElement({
422389
expandedContent={expandedContent}
423390
onElementClick={onElementClick}
424391
onElementMouseDown={onElementMouseDown}
425-
onResizeStart={handleElementResizeStart}
392+
onResizeStart={onResizeStart}
426393
isDropTarget={isDropTarget}
427394
/>
428395
{isSelected && (
@@ -811,8 +778,7 @@ function ExpandedKeyframeLanes({
811778
[...keyframes]
812779
.sort(
813780
(a, b) =>
814-
a.time - b.time ||
815-
a.propertyPath.localeCompare(b.propertyPath),
781+
a.time - b.time || a.propertyPath.localeCompare(b.propertyPath),
816782
)
817783
.map((kf) => ({
818784
trackId,
@@ -824,6 +790,8 @@ function ExpandedKeyframeLanes({
824790
);
825791

826792
return (
793+
// biome-ignore lint/a11y/noStaticElementInteractions: expanded keyframe lanes are a pointer-only editing surface
794+
// biome-ignore lint/a11y/useKeyWithClickEvents: expanded keyframe lanes are a pointer-only editing surface
827795
<div
828796
ref={containerRef}
829797
className="relative flex flex-col"
@@ -837,9 +805,7 @@ function ExpandedKeyframeLanes({
837805
return (
838806
<div
839807
key={row.propertyPath}
840-
className={cn(
841-
"relative flex items-center bg-muted/50",
842-
)}
808+
className={cn("relative flex items-center bg-muted/50")}
843809
style={{ height: `${KEYFRAME_LANE_HEIGHT_PX}px` }}
844810
>
845811
{laneKeyframes.map((kf) => {
@@ -849,8 +815,9 @@ function ExpandedKeyframeLanes({
849815
propertyPath: row.propertyPath,
850816
keyframeId: kf.id,
851817
};
852-
const isBeingDragged =
853-
keyframeDragState.draggingKeyframeIds.has(kf.id);
818+
const isBeingDragged = keyframeDragState.draggingKeyframeIds.has(
819+
kf.id,
820+
);
854821
const kfLeft = timelineTimeToSnappedPixels({
855822
time: displayedStartTime + kf.time,
856823
zoomLevel,
@@ -898,9 +865,7 @@ function ExpandedKeyframeLanes({
898865
icon={KeyframeIcon}
899866
className={cn(
900867
"size-3.5 text-black mr-1",
901-
isSelected
902-
? "fill-primary"
903-
: "fill-white",
868+
isSelected ? "fill-primary" : "fill-white",
904869
)}
905870
strokeWidth={1.5}
906871
/>

0 commit comments

Comments
 (0)