Skip to content

Commit 03db58c

Browse files
committed
fix(studio): timeline clips keep their pictures while you scroll
With row virtualization on, the timeline dropped every clip's picture for the length of a scroll gesture and put it back 100ms after the gesture stopped, so a zoomed-in timeline showed grey boxes the whole time a person was moving through it. Clip content now stays mounted while scrolling; pictures already loaded come straight from the thumbnail cache.
1 parent 8055961 commit 03db58c

4 files changed

Lines changed: 6 additions & 27 deletions

File tree

‎packages/studio/src/player/components/Timeline.virtualization.test.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe("Timeline row virtualization", { timeout: 30_000 }, () => {
140140
usePlayerStore.getState().reset();
141141
});
142142

143-
it("defers rich clip content while scrolling without replacing the clip shell", async () => {
143+
it("keeps a clip's picture on screen while the timeline scrolls", async () => {
144144
const [{ Timeline }, { usePlayerStore }] = await Promise.all([
145145
import("./Timeline"),
146146
import("../store/playerStore"),
@@ -167,17 +167,18 @@ describe("Timeline row virtualization", { timeout: 30_000 }, () => {
167167
expect(scroller).not.toBeNull();
168168
expect(clip).not.toBeNull();
169169
expect(clip?.title).toBe("Clip 0 • 0.0s – 10.0s");
170-
expect(host.querySelector("[data-rich-content]")).not.toBeNull();
170+
const picture = host.querySelector("[data-rich-content]");
171+
expect(picture).not.toBeNull();
171172

172173
if (scroller) await dispatchScroll(scroller);
173174
expect(host.querySelector('[data-el-id="clip-0"]')).toBe(clip);
174-
expect(host.querySelector("[data-rich-content]")).toBeNull();
175+
expect(host.querySelector("[data-rich-content]")).toBe(picture);
175176

176177
await act(async () => {
177178
await new Promise((resolve) => setTimeout(resolve, 110));
178179
});
179180
expect(host.querySelector('[data-el-id="clip-0"]')).toBe(clip);
180-
expect(host.querySelector("[data-rich-content]")).not.toBeNull();
181+
expect(host.querySelector("[data-rich-content]")).toBe(picture);
181182
} finally {
182183
act(() => root.unmount());
183184
usePlayerStore.getState().reset();

‎packages/studio/src/player/components/timelineProviderStateBuilders.test.ts‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { describe, expect, it, vi } from "vitest";
44
import {
55
buildTimelineMeta,
6-
resolveRenderClipContent,
76
resolveResizingElementIds,
87
shouldIgnoreTimelinePointerDown,
98
type TimelineMetaBuilderInputs,
@@ -85,12 +84,6 @@ describe("buildTimelineMeta", () => {
8584
});
8685

8786
describe("timeline provider branch helpers", () => {
88-
it("resolves virtualized clip content", () => {
89-
const render = () => null;
90-
expect(resolveRenderClipContent(true, true, render)).toBeUndefined();
91-
expect(resolveRenderClipContent(false, true, render)).toBe(render);
92-
});
93-
9487
it("resolves resizing element ids", () => {
9588
expect(resolveResizingElementIds(null)).toBeUndefined();
9689
expect(resolveResizingElementIds({ element: { id: "clip" } } as never)).toEqual(["clip"]);

‎packages/studio/src/player/components/timelineProviderStateBuilders.ts‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {
44
TimelineViewportProps,
55
} from "./TimelineProvider";
66
import type { ResizingClipState } from "./useTimelineClipDrag";
7-
import type { TimelineLaneBaseProps } from "./timelineLaneProps";
87
import type { DraggedClipState } from "./timelineClipDragTypes";
98
import type { MultiDragPreviewInput } from "./timelineMultiDragPreview";
109
import { getTimelineElementIdentity } from "../lib/timelineElementHelpers";
@@ -34,14 +33,6 @@ export function resolveResizingElementIds(
3433
return undefined;
3534
}
3635

37-
export function resolveRenderClipContent(
38-
rowVirtualizationActive: boolean,
39-
isScrolling: boolean,
40-
renderClipContent: TimelineLaneBaseProps["renderClipContent"],
41-
): TimelineLaneBaseProps["renderClipContent"] {
42-
return rowVirtualizationActive && isScrolling ? undefined : renderClipContent;
43-
}
44-
4536
export function shouldIgnoreTimelinePointerDown(target: EventTarget | null): boolean {
4637
return target instanceof Element && target.closest("button, input, select, a") !== null;
4738
}

‎packages/studio/src/player/components/useTimelineProviderState.tsx‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type { ClipContextMenuState, TimelineContextValue } from "./TimelineProvi
1111
import {
1212
buildTimelineMeta,
1313
resolveMultiDragPreview,
14-
resolveRenderClipContent,
1514
resolveResizingElementIds,
1615
shouldIgnoreTimelinePointerDown,
1716
} from "./timelineProviderStateBuilders";
@@ -509,11 +508,6 @@ export function useTimelineProviderState({
509508
onRazorSplit: editContext.onRazorSplit,
510509
onRazorSplitAll: editContext.onRazorSplitAll,
511510
};
512-
const timelineRenderClipContent = resolveRenderClipContent(
513-
timelineFocus.rowVirtualizationActive,
514-
viewport.isScrolling,
515-
renderClipContent,
516-
);
517511
const timelineMeta = buildTimelineMeta({
518512
emptyState: {
519513
isDragOver: assetDrop.isDragOver,
@@ -580,7 +574,7 @@ export function useTimelineProviderState({
580574
overlays,
581575
},
582576
actions: {
583-
renderClipContent: timelineRenderClipContent,
577+
renderClipContent,
584578
renderClipOverlay,
585579
setFocusedEaseSegment,
586580
},

0 commit comments

Comments
 (0)