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
27 changes: 10 additions & 17 deletions packages/studio/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useCallback, useRef, useMemo, useEffect, useLayoutEffect } from "react";
import type { LeftSidebarHandle, SidebarTab } from "./components/sidebar/LeftSidebar";
import { useRenderQueue } from "./components/renders/useRenderQueue";
import { usePlayerStore, type TimelineElement } from "./player";
import { usePlayerStore } from "./player";
import { StudioOverlays } from "./components/StudioOverlays";
import { SaveQueuePausedBanner } from "./components/SaveQueuePausedBanner";
import { useCaptionStore } from "./captions/store";
Expand All @@ -12,9 +12,12 @@ import { useFileManager } from "./hooks/useFileManager";
import { usePreviewPersistence } from "./hooks/usePreviewPersistence";
import { usePreviewDocumentVersion } from "./hooks/usePreviewDocumentVersion";
import { useTimelineEditing } from "./hooks/useTimelineEditing";
import { persistTimelineMoveEditsAtomically } from "./hooks/timelineMoveAdapter";
import {
persistTimelineMoveEditsAtomically,
type TimelineMoveEditsHandler,
type TimelineMoveOperation,
} from "./hooks/timelineMoveAdapter";
import type { TimelineZIndexReorderCommit } from "./hooks/useTimelineEditingTypes";
import type { TimelineStackingReorderIntent } from "./player/components/timelineStacking";
import type { BlockPreviewInfo } from "./components/sidebar/BlocksTab";
import { useDomEditSession } from "./hooks/useDomEditSession";
import { useSdkSelectionSync } from "./hooks/useSdkSelectionSync";
Expand Down Expand Up @@ -62,7 +65,6 @@ import {
} from "./utils/studioUrlState";
import { trackStudioSessionStart } from "./telemetry/events";
import { hasFiredSessionStart, markSessionStartFired } from "./telemetry/config";
type TimelineMoveOperation = Parameters<typeof persistTimelineMoveEditsAtomically>[2];
// fallow-ignore-next-line complexity
export function StudioApp() {
const { projectId, resolving, waitingForServer } = useServerConnection();
Expand Down Expand Up @@ -154,6 +156,7 @@ export function StudioApp() {
reloadPreview: () => setRefreshKey((k) => k + 1),
pendingTimelineEditPathRef,
});
const invalidateGsapCacheRef = useRef<() => void>(() => {});
const timelineEditing = useTimelineEditing({
projectId,
activeCompPath,
Expand All @@ -171,20 +174,11 @@ export function StudioApp() {
sdkSession: editFlowSdkSession,
publishSdkSession: sdkHandle.publish,
forceReloadSdkSession: sdkHandle.forceReload,
invalidateGsapCache: () => invalidateGsapCacheRef.current(),
Comment thread
miguel-heygen marked this conversation as resolved.
handleDomZIndexReorderCommitRef,
});
const handleTimelineElementsMove = useCallback(
async (
edits: Array<{
element: TimelineElement;
updates: Pick<TimelineElement, "start" | "track"> & {
stackingReorder?: TimelineStackingReorderIntent | null;
};
}>,
coalesceKey?: string,
operation: TimelineMoveOperation = "timing",
coalesceMs?: number,
) => {
const handleTimelineElementsMove: TimelineMoveEditsHandler = useCallback(
async (edits, coalesceKey, operation: TimelineMoveOperation = "timing", coalesceMs) => {
const deps = { handleTimelineGroupMove: timelineEditing.handleTimelineGroupMove };
await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps, coalesceMs);
},
Expand Down Expand Up @@ -228,7 +222,6 @@ export function StudioApp() {
const domEditDeleteBridge = (s: DomEditSelection) => handleDomEditElementDeleteRef.current(s);
const resetKeyframesRef = useRef<() => boolean>(() => false);
const deleteSelectedKeyframesRef = useRef<() => void>(() => {});
const invalidateGsapCacheRef = useRef<() => void>(() => {});
const { handleCopy, handlePaste, handleCut } = useClipboard({
projectId,
activeCompPath,
Expand Down
93 changes: 18 additions & 75 deletions packages/studio/src/components/editor/AnimationCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,34 @@ function selectPreset(host: HTMLElement, presetId: string): string {
return presetConfig.ease;
}

function renderExpandedCard({
animation,
/** Every test mounts the same card; only expansion, flat mode, and the spies differ. */
function renderCard({
animation = baseAnimation(),
defaultExpanded = true,
flat,
onUpdateMeta = vi.fn(),
onUpdateKeyframeEase = vi.fn(),
onDeleteAnimation = noop,
}: {
animation: GsapAnimation;
animation?: GsapAnimation;
defaultExpanded?: boolean;
flat?: boolean;
onUpdateMeta?: ReturnType<typeof vi.fn>;
onUpdateKeyframeEase?: ReturnType<typeof vi.fn>;
}) {
onDeleteAnimation?: (id: string) => void;
} = {}) {
const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
act(() => {
root.render(
<AnimationCard
animation={animation}
defaultExpanded
defaultExpanded={defaultExpanded}
flat={flat}
onUpdateProperty={noop}
onUpdateMeta={onUpdateMeta}
onDeleteAnimation={noop}
onDeleteAnimation={onDeleteAnimation}
onAddProperty={noop}
onRemoveProperty={noop}
onUpdateKeyframeEase={onUpdateKeyframeEase}
Expand All @@ -90,7 +95,7 @@ describe("AnimationCard ease editing", () => {
],
},
});
const view = renderExpandedCard({ animation, onUpdateKeyframeEase });
const view = renderCard({ animation, onUpdateKeyframeEase });

const segment = Array.from(view.host.querySelectorAll("button")).find((button) =>
button.textContent?.includes("0% β†’ 50%"),
Expand All @@ -101,6 +106,7 @@ describe("AnimationCard ease editing", () => {

expect(onUpdateKeyframeEase).toHaveBeenCalledExactlyOnceWith(animation.id, 50, ease);
expect(trackStudioSegmentEaseEdit).toHaveBeenCalledExactlyOnceWith({
action: "commit",
ease,
});
act(() => view.root.unmount());
Expand All @@ -110,7 +116,7 @@ describe("AnimationCard ease editing", () => {
const onUpdateMeta = vi.fn();
const onUpdateKeyframeEase = vi.fn();
const animation = baseAnimation({ id: "flat-tween" });
const view = renderExpandedCard({
const view = renderCard({
animation,
flat: true,
onUpdateMeta,
Expand All @@ -128,70 +134,23 @@ describe("AnimationCard ease editing", () => {

describe("AnimationCard flat branch", () => {
it("renders a mint border-left and panel-token colors when flat", () => {
const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
act(() => {
root.render(
<AnimationCard
animation={baseAnimation()}
defaultExpanded={false}
flat
onUpdateProperty={noop}
onUpdateMeta={noop}
onDeleteAnimation={noop}
onAddProperty={noop}
onRemoveProperty={noop}
/>,
);
});
const { host, root } = renderCard({ defaultExpanded: false, flat: true });
const card = host.querySelector('[data-flat-effect-card="true"]');
expect(card).not.toBeNull();
expect(card?.className).toContain("border-panel-accent");
act(() => root.unmount());
});

it("still renders the legacy (non-flat) appearance when flat is omitted", () => {
const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
act(() => {
root.render(
<AnimationCard
animation={baseAnimation()}
defaultExpanded={false}
onUpdateProperty={noop}
onUpdateMeta={noop}
onDeleteAnimation={noop}
onAddProperty={noop}
onRemoveProperty={noop}
/>,
);
});
const { host, root } = renderCard({ defaultExpanded: false });
expect(host.querySelector('[data-flat-effect-card="true"]')).toBeNull();
expect(host.textContent).toContain("power2.out");
act(() => root.unmount());
});

it("toggles expanded state when the collapsed header button is clicked, in both modes", () => {
for (const flat of [false, true]) {
const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
act(() => {
root.render(
<AnimationCard
animation={baseAnimation()}
defaultExpanded={false}
flat={flat || undefined}
onUpdateProperty={noop}
onUpdateMeta={noop}
onDeleteAnimation={noop}
onAddProperty={noop}
onRemoveProperty={noop}
/>,
);
});
const { host, root } = renderCard({ defaultExpanded: false, flat: flat || undefined });
expect(host.textContent).not.toContain("Remove");
const button = host.querySelector("button");
expect(button).not.toBeNull();
Expand All @@ -205,23 +164,7 @@ describe("AnimationCard flat branch", () => {

it("invokes onDeleteAnimation with the animation id when Remove is clicked, in flat mode", () => {
const onDeleteAnimation = vi.fn();
const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
act(() => {
root.render(
<AnimationCard
animation={baseAnimation()}
defaultExpanded={true}
flat
onUpdateProperty={noop}
onUpdateMeta={noop}
onDeleteAnimation={onDeleteAnimation}
onAddProperty={noop}
onRemoveProperty={noop}
/>,
);
});
const { host, root } = renderCard({ flat: true, onDeleteAnimation });
const buttons = Array.from(host.querySelectorAll("button"));
const removeButton = buttons.find((b) => b.textContent === "Remove");
expect(removeButton).not.toBeUndefined();
Expand Down
2 changes: 1 addition & 1 deletion packages/studio/src/components/editor/AnimationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ export const AnimationCard = memo(function AnimationCard({
onToggle={setExpandedKfPct}
onEaseCommit={(pct, ease) => {
onUpdateKeyframeEase(animation.id, pct, ease);
trackStudioSegmentEaseEdit({ ease });
trackStudioSegmentEaseEdit({ action: "commit", ease });
}}
onApplyAll={
onSetAllKeyframeEases
Expand Down
4 changes: 2 additions & 2 deletions packages/studio/src/hooks/gsapDragCommit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { usePlayerStore } from "../player/store/playerStore";
import { readRuntimeKeyframes, scanAllRuntimeKeyframes } from "./gsapRuntimeKeyframes";
import { resolveTweenStart, resolveTweenDuration } from "../utils/globalTimeCompiler";
import { roundTo3 } from "../utils/rounding";
import { computeElementPercentage } from "./gsapShared";
import { computeElementPercentage, idSelector } from "./gsapShared";
import { computeDraggedGsapPosition } from "./draggedGsapPosition";
import type { RuntimeTweenChange } from "./gsapRuntimePatch";
import { isGestureTransactionCommit, runGestureTransaction } from "./gestureTransaction";
Expand Down Expand Up @@ -117,7 +117,7 @@ export async function materializeIfDynamic(
const allScanned = scanAllRuntimeKeyframes(iframe);
if (allScanned.size === 0) return;
const allElements = Array.from(allScanned.entries()).map(([id, data]) => ({
selector: `#${id}`,
selector: idSelector(id),
keyframes: data.keyframes,
easeEach: data.easeEach,
}));
Expand Down
Loading
Loading