Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions src/components/ai-edition/NewEditorShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1203,22 +1203,17 @@ export function NewEditorShell() {
return;
}

// An effect region goes with its definition (`RegionSnapshot`), and with the span of the
// whole pill: one cut by a clip boundary is several fragments.
// The pill on screen, not the fragment clicked: a region cut by a clip boundary is several.
const { wholePillOf } = await import("@/lib/ai-edition/timeline/timelineMap");

// An effect region goes with its definition (`RegionSnapshot`).
if (sel.kind === "shaderEffect") {
const { coalesceRegionsForRuler } = await import("@/lib/ai-edition/timeline/timelineMap");
const pill = coalesceRegionsForRuler(tl.shaderEffectRanges).find((p) =>
p.ids.includes(sel.id),
);
const definition = pill && tl.shaderEffects.find((e) => e.id === pill.member.effectId);
if (!pill || !definition) return;
const region = wholePillOf(tl.shaderEffectRanges, sel.id);
const definition = region && tl.shaderEffects.find((e) => e.id === region.effectId);
if (!region || !definition) return;
copyRegion({
kind: "shaderEffect",
region: {
...pill.member,
startMs: Math.round(pill.start * 1000),
endMs: Math.round(pill.end * 1000),
} as unknown as Record<string, unknown>,
region: region as unknown as Record<string, unknown>,
definition,
});
setCopiedClipId(null);
Expand All @@ -1233,7 +1228,10 @@ export function NewEditorShell() {
: sel.kind === "speed"
? tl.speedRegions
: tl.cameraFullscreenRegions;
const region = (source as Array<{ id: string }>).find((r) => r.id === sel.id);
const region = wholePillOf(
source as Array<{ id: string; startMs: number; endMs: number }>,
sel.id,
);
if (!region) return;
copyRegion({ kind: sel.kind, region: region as unknown as Record<string, unknown> });
// One clipboard wins at a time: a copied pill retires the copied clip.
Expand Down
13 changes: 13 additions & 0 deletions src/lib/ai-edition/timeline/timelineMap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
resolveNativePosition,
resolvePillIds,
resolveRegionIds,
wholePillOf,
} from "./timelineMap";

function clip(overrides: Partial<AxcutClip> & Pick<AxcutClip, "id" | "assetId">): AxcutClip {
Expand Down Expand Up @@ -787,6 +788,18 @@ describe("pills wired to the universal rules", () => {
expect(pills[0].ids).toEqual(["a", "b"]);
});

it("wholePillOf gives the pill a fragment belongs to, over its whole span", () => {
// One zoom cut by the clip boundary at 25.557 s: two fragments, one pill.
const regions = [
{ id: "a", startMs: 20000, endMs: 25557, depth: 3 },
{ id: "b", startMs: 25557, endMs: 30000, depth: 3 },
{ id: "c", startMs: 30000, endMs: 31000, depth: 5 },
];
expect(wholePillOf(regions, "b")).toEqual({ id: "a", startMs: 20000, endMs: 30000, depth: 3 });
expect(wholePillOf(regions, "c")).toEqual(regions[2]);
expect(wholePillOf(regions, "gone")).toBeNull();
});

it("keeps adjacent regions with different properties as separate pills", () => {
const pills = coalesceRegionsForRuler([
{ id: "a", startMs: 2000, endMs: 5000, speed: 3 },
Expand Down
18 changes: 18 additions & 0 deletions src/lib/ai-edition/timeline/timelineMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,24 @@ export function coalesceRegionsForRuler<T extends { id: string; startMs: number;
}));
}

/**
* The pill `id` belongs to, as one region: its first member over the pill's whole span. A pill
* cut by a clip boundary is several fragments, and copying the fragment clicked would paste a
* shorter region than the one on screen. `null` when no region has that id.
*/
export function wholePillOf<T extends { id: string; startMs: number; endMs: number }>(
regions: T[],
id: string,
): T | null {
const pill = coalesceRegionsForRuler(regions).find((p) => p.ids.includes(id));
if (!pill) return null;
return {
...pill.member,
startMs: Math.round(pill.start * 1000),
endMs: Math.round(pill.end * 1000),
};
}

/**
* The regions that render as the SAME pill as `id`. Recomputed from the merge rule
* rather than stored: nothing records which regions belong together, because equal
Expand Down
Loading