mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat: add studio timeline editing (#390)
## Summary Add the actual Studio timeline editing layer on top of the preview/runtime foundation. This PR includes: - drag-to-move clips across time and tracks - left/right resize handles with media-aware trim persistence - edge auto-scroll and edge track creation while dragging - selector-based source patching for `data-start`, `data-duration`, `data-track-index`, `z-index`, and media trim attributes - timeline UI cleanup, theming, hover/drag states, and the `Copy Prompt` action ## Why This PR Is Separate This is the user-facing editing behavior. It depends on the preview/runtime fixes in the base PR, but it is much easier to review once that plumbing is isolated. ## Verification - `bun run --filter @hyperframes/studio test` - `bun run --filter @hyperframes/studio typecheck` - `bunx oxlint packages/studio/src/App.tsx packages/studio/src/components/nle/NLELayout.tsx packages/studio/src/player/components/EditModal.tsx packages/studio/src/player/components/Timeline.tsx packages/studio/src/player/components/TimelineClip.tsx packages/studio/src/player/components/timelineEditing.ts packages/studio/src/player/components/timelineEditing.test.ts packages/studio/src/player/components/timelineTheme.ts packages/studio/src/player/components/timelineTheme.test.ts packages/studio/src/utils/sourcePatcher.ts packages/studio/src/utils/sourcePatcher.test.ts` - `bunx oxfmt --check packages/studio/src/App.tsx packages/studio/src/components/nle/NLELayout.tsx packages/studio/src/player/components/EditModal.tsx packages/studio/src/player/components/Timeline.tsx packages/studio/src/player/components/TimelineClip.tsx packages/studio/src/player/components/timelineEditing.ts packages/studio/src/player/components/timelineEditing.test.ts packages/studio/src/player/components/timelineTheme.ts packages/studio/src/player/components/timelineTheme.test.ts packages/studio/src/utils/sourcePatcher.ts packages/studio/src/utils/sourcePatcher.test.ts` ## Browser Proof - verified timeline drag / resize / trim flows in Studio with `agent-browser` - verified preview hot-refresh behavior without iframe remount flashes ## Stack - depends on #389 - followed by `fix: smooth scrubber end seeking` [result.mp4 <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/ca71c177-5042-468d-906f-b353938f40f8.mp4" />](https://app.graphite.com/user-attachments/video/ca71c177-5042-468d-906f-b353938f40f8.mp4)
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
getClipHandleOpacity,
|
||||
getRenderedTimelineElement,
|
||||
getTimelineTrackStyle,
|
||||
} from "./timelineTheme";
|
||||
|
||||
describe("getTimelineTrackStyle", () => {
|
||||
it("reuses heading styles for heading tags", () => {
|
||||
expect(getTimelineTrackStyle("h2").accent).toBe(getTimelineTrackStyle("h1").accent);
|
||||
});
|
||||
|
||||
it("falls back for unknown tags", () => {
|
||||
expect(getTimelineTrackStyle("custom-tag").accent).toBe("#3CE6AC");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getClipHandleOpacity", () => {
|
||||
it("hides handles at rest", () => {
|
||||
expect(getClipHandleOpacity({ isHovered: false, isSelected: false, isDragging: false })).toBe(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
it("prioritizes dragging over hover and selection", () => {
|
||||
expect(getClipHandleOpacity({ isHovered: true, isSelected: true, isDragging: true })).toBe(
|
||||
0.95,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRenderedTimelineElement", () => {
|
||||
it("keeps non-dragged clips unchanged", () => {
|
||||
const element = { id: "a", tag: "div", start: 1, duration: 2, track: 0 };
|
||||
expect(
|
||||
getRenderedTimelineElement({
|
||||
element,
|
||||
draggedElementId: "b",
|
||||
previewStart: 2,
|
||||
previewTrack: 1,
|
||||
}),
|
||||
).toEqual(element);
|
||||
});
|
||||
|
||||
it("moves the actual dragged clip to the preview position", () => {
|
||||
const element = { id: "a", tag: "div", start: 1, duration: 2, track: 0 };
|
||||
expect(
|
||||
getRenderedTimelineElement({
|
||||
element,
|
||||
draggedElementId: "a",
|
||||
previewStart: 2.4,
|
||||
previewTrack: 3,
|
||||
}),
|
||||
).toEqual({ ...element, start: 2.4, track: 3 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user