refactor(studio): extract shared timeline components and deduplicate code (#1329)

Extract shared utilities to reduce duplication across timeline components:

- PlayheadIndicator: shared playhead rendering (was duplicated in
  TimelineCanvas and TimelineEditorNotice)
- useContextMenuDismiss: outside-click/Escape dismiss pattern (was
  duplicated in ClipContextMenu and KeyframeDiamondContextMenu)
- TimelineCallbacks: shared callback interfaces for drop and edit
  operations (was duplicated in NLELayout and Timeline props)
- useTimelineZoom: consolidated zoom store selectors
- timelineElementSplit: shared canSplitElement, buildPatchTarget, and
  readFileContent utilities
- gsapParser.test-helpers: shared test utilities for parser specs
This commit is contained in:
Miguel Ángel
2026-06-10 23:45:03 -04:00
committed by GitHub
parent 06426b5014
commit ab08260201
12 changed files with 373 additions and 157 deletions
@@ -0,0 +1,44 @@
// fallow-ignore-file code-duplication
// fallow-ignore-file dead-code
import type { TimelineElement } from "../store/playerStore";
import type { BlockedTimelineEditIntent } from "./timelineEditing";
/**
* Shared callback signatures for timeline editing operations.
* Used by NLELayout, Timeline, and any component that passes through
* the standard set of timeline mutation handlers.
*/
export interface TimelineDropCallbacks {
onFileDrop?: (
files: File[],
placement?: { start: number; track: number },
) => Promise<void> | void;
onAssetDrop?: (
assetPath: string,
placement: { start: number; track: number },
) => Promise<void> | void;
onBlockDrop?: (
blockName: string,
placement: { start: number; track: number },
) => Promise<void> | void;
}
export interface TimelineEditCallbacks {
onMoveElement?: (
element: TimelineElement,
updates: Pick<TimelineElement, "start" | "track">,
) => Promise<void> | void;
onResizeElement?: (
element: TimelineElement,
updates: Pick<TimelineElement, "start" | "duration" | "playbackStart">,
) => Promise<void> | void;
onBlockedEditAttempt?: (element: TimelineElement, intent: BlockedTimelineEditIntent) => void;
onSplitElement?: (element: TimelineElement, splitTime: number) => Promise<void> | void;
onRazorSplit?: (element: TimelineElement, splitTime: number) => Promise<void> | void;
onRazorSplitAll?: (splitTime: number) => Promise<void> | void;
onDeleteKeyframe?: (elementId: string, percentage: number) => void;
onDeleteAllKeyframes?: (elementId: string) => void;
onChangeKeyframeEase?: (elementId: string, percentage: number, ease: string) => void;
onMoveKeyframe?: (element: TimelineElement, oldPct: number, newPct: number) => void;
onToggleKeyframeAtPlayhead?: (element: TimelineElement) => void;
}