Skip to content
Open
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
20 changes: 15 additions & 5 deletions packages/studio/src/components/editor/LayersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
import { useStudioContext } from "../../contexts/StudioContext";
import { useDomEditContext } from "../../contexts/DomEditContext";
import { usePlayerStore } from "../../player";
import { findMatchingTimelineElementId } from "../../utils/studioHelpers";
import {
findMatchingTimelineElementId,
resolveTimelineSelectionSeekTime,
} from "../../utils/studioHelpers";
import { Layers } from "../../icons/SystemIcons";

const TAG_ICONS: Record<string, string> = {
Expand Down Expand Up @@ -49,8 +52,14 @@ interface CollapsedState {
}

export const LayersPanel = memo(function LayersPanel() {
const { previewIframeRef, activeCompPath, refreshKey, compositionLoading, timelineElements } =
useStudioContext();
const {
previewIframeRef,
activeCompPath,
refreshKey,
compositionLoading,
timelineElements,
currentTime,
} = useStudioContext();
const { domEditSelection, applyDomSelection, updateDomEditHoverSelection } = useDomEditContext();

const [layers, setLayers] = useState<DomEditLayerItem[]>([]);
Expand Down Expand Up @@ -140,11 +149,12 @@ export const LayersPanel = memo(function LayersPanel() {
if (matchedId) {
const el = timelineElements.find((e) => (e.key ?? e.id) === matchedId);
if (el) {
usePlayerStore.getState().requestSeek(el.start + el.duration / 2);
const nextTime = resolveTimelineSelectionSeekTime(currentTime, el);
if (nextTime != null) usePlayerStore.getState().requestSeek(nextTime);
}
}
},
[resolveSelection, timelineElements],
[currentTime, resolveSelection, timelineElements],
);

const handleSelectLayer = useCallback(
Expand Down
20 changes: 20 additions & 0 deletions packages/studio/src/utils/studioHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";
import { resolveTimelineSelectionSeekTime } from "./studioHelpers";

describe("resolveTimelineSelectionSeekTime", () => {
it("keeps the current time when it is already inside the clip range", () => {
expect(resolveTimelineSelectionSeekTime(3, { start: 0, duration: 5 })).toBe(3);
});

it("clamps to the clip start when current time is before the clip", () => {
expect(resolveTimelineSelectionSeekTime(1, { start: 4, duration: 3 })).toBe(4);
});

it("clamps to the clip end when current time is after the clip", () => {
expect(resolveTimelineSelectionSeekTime(10, { start: 4, duration: 3 })).toBe(7);
});

it("falls back to the clip start for invalid current time", () => {
expect(resolveTimelineSelectionSeekTime(Number.NaN, { start: 2, duration: 5 })).toBe(2);
});
});
14 changes: 14 additions & 0 deletions packages/studio/src/utils/studioHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,20 @@ export function findMatchingTimelineElementId(
return null;
}

export function resolveTimelineSelectionSeekTime(
currentTime: number,
element: Pick<TimelineElement, "start" | "duration"> | null | undefined,
): number | null {
if (!element) return null;
if (!Number.isFinite(element.start) || !Number.isFinite(element.duration)) return null;

const start = Math.max(0, element.start);
const end = Math.max(start, start + Math.max(0, element.duration));
const time = Number.isFinite(currentTime) ? currentTime : start;

return clampNumber(time, start, end);
}

export function clampNumber(value: number, min: number, max: number): number {
if (max < min) return min;
return Math.min(Math.max(value, min), max);
Expand Down
Loading