mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 07:16:02 +00:00
add: timeline editor with more seperation of concerns
This commit is contained in:
@@ -4,7 +4,6 @@ import {
|
||||
type KeyboardEvent as ReactKeyboardEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -31,14 +30,8 @@ import KeyframeMarkers from "./KeyframeMarkers";
|
||||
import TimelineWrapper from "./TimelineWrapper";
|
||||
import { useAudioPeaks } from "./useAudioPeaks";
|
||||
import { calculateTimelineScale } from "./core/time";
|
||||
import { useTimelineDndBindings } from "./hooks/useTimelineDndBindings";
|
||||
import { useTimelineAnnotationsActions } from "./hooks/useTimelineAnnotationsActions";
|
||||
import { useTimelineAudioActions } from "./hooks/useTimelineAudioActions";
|
||||
import { useTimelineKeyboardShortcuts } from "./hooks/useTimelineKeyboardShortcuts";
|
||||
import { useTimelineNormalization } from "./hooks/useTimelineNormalization";
|
||||
import { useTimelineEditorRuntime } from "./hooks/useTimelineEditorRuntime";
|
||||
import { useTimelineRange } from "./hooks/useTimelineRange";
|
||||
import { useTimelineSelection } from "./hooks/useTimelineSelection";
|
||||
import { useTimelineZoomActions } from "./hooks/useTimelineZoomActions";
|
||||
import TimelineEditorShell from "./components/editor/TimelineEditorShell";
|
||||
import TimelineCanvas from "./components/viewport/TimelineCanvas";
|
||||
import TimelineToolbar from "./components/toolbar/TimelineToolbar";
|
||||
@@ -60,11 +53,7 @@ export interface TimelineEditorProps {
|
||||
selectedZoomId: string | null;
|
||||
onSelectZoom: (id: string | null) => void;
|
||||
trimRegions?: TrimRegion[];
|
||||
onTrimAdded?: (span: Span) => void;
|
||||
onTrimSpanChange?: (id: string, span: Span) => void;
|
||||
onTrimDelete?: (id: string) => void;
|
||||
selectedTrimId?: string | null;
|
||||
onSelectTrim?: (id: string | null) => void;
|
||||
clipRegions?: ClipRegion[];
|
||||
onClipSplit?: (splitMs: number) => void;
|
||||
onClipSpanChange?: (id: string, span: Span) => void;
|
||||
@@ -78,11 +67,7 @@ export interface TimelineEditorProps {
|
||||
selectedAnnotationId?: string | null;
|
||||
onSelectAnnotation?: (id: string | null) => void;
|
||||
speedRegions?: SpeedRegion[];
|
||||
onSpeedAdded?: (span: Span) => void;
|
||||
onSpeedSpanChange?: (id: string, span: Span) => void;
|
||||
onSpeedDelete?: (id: string) => void;
|
||||
selectedSpeedId?: string | null;
|
||||
onSelectSpeed?: (id: string | null) => void;
|
||||
audioRegions?: AudioRegion[];
|
||||
onAudioAdded?: (span: Span, audioPath: string, trackIndex?: number) => void;
|
||||
onAudioSpanChange?: (id: string, span: Span, trackIndex?: number) => void;
|
||||
@@ -126,11 +111,7 @@ const TimelineEditor = forwardRef<TimelineEditorHandle, TimelineEditorProps>(
|
||||
selectedZoomId,
|
||||
onSelectZoom,
|
||||
trimRegions = [],
|
||||
onTrimAdded: _onTrimAdded,
|
||||
onTrimSpanChange,
|
||||
onTrimDelete: _onTrimDelete,
|
||||
selectedTrimId: _selectedTrimId,
|
||||
onSelectTrim: _onSelectTrim,
|
||||
clipRegions = [],
|
||||
onClipSplit,
|
||||
onClipSpanChange,
|
||||
@@ -144,11 +125,7 @@ const TimelineEditor = forwardRef<TimelineEditorHandle, TimelineEditorProps>(
|
||||
selectedAnnotationId,
|
||||
onSelectAnnotation,
|
||||
speedRegions = [],
|
||||
onSpeedAdded: _onSpeedAdded,
|
||||
onSpeedSpanChange,
|
||||
onSpeedDelete: _onSpeedDelete,
|
||||
selectedSpeedId: _selectedSpeedId,
|
||||
onSelectSpeed: _onSelectSpeed,
|
||||
audioRegions = [],
|
||||
onAudioAdded,
|
||||
onAudioSpanChange,
|
||||
@@ -255,168 +232,68 @@ const TimelineEditor = forwardRef<TimelineEditorHandle, TimelineEditorProps>(
|
||||
setSelectedKeyframeId,
|
||||
selectAllBlocksActive,
|
||||
setSelectAllBlocksActive,
|
||||
hasAnyTimelineBlocks,
|
||||
addKeyframe,
|
||||
deleteSelectedKeyframe,
|
||||
handleKeyframeMove,
|
||||
deleteSelectedZoom,
|
||||
deleteSelectedClip,
|
||||
deleteSelectedAnnotation,
|
||||
deleteSelectedAudio,
|
||||
clearSelectedBlocks,
|
||||
deleteAllBlocks,
|
||||
handleSelectZoom,
|
||||
handleSelectClip,
|
||||
handleSelectAnnotation,
|
||||
handleSelectAudio,
|
||||
cycleAnnotationsAtCurrentTime,
|
||||
} = useTimelineSelection({
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
zoomRegions,
|
||||
clipRegions,
|
||||
annotationRegions,
|
||||
audioRegions,
|
||||
selectedZoomId,
|
||||
selectedClipId,
|
||||
selectedAnnotationId,
|
||||
selectedAudioId,
|
||||
onZoomDelete,
|
||||
onClipDelete,
|
||||
onAnnotationDelete,
|
||||
onAudioDelete,
|
||||
onSelectZoom,
|
||||
onSelectClip,
|
||||
onSelectAnnotation,
|
||||
onSelectAudio,
|
||||
});
|
||||
|
||||
useTimelineNormalization({
|
||||
totalMs,
|
||||
safeMinDurationMs,
|
||||
zoomRegions,
|
||||
trimRegions,
|
||||
speedRegions,
|
||||
audioRegions,
|
||||
onZoomSpanChange,
|
||||
onTrimSpanChange,
|
||||
onSpeedSpanChange,
|
||||
onAudioSpanChange,
|
||||
});
|
||||
|
||||
const {
|
||||
hasOverlap,
|
||||
timelineItems,
|
||||
allRegionSpans,
|
||||
getResolvedDropRowId,
|
||||
handleItemSpanChange,
|
||||
} = useTimelineDndBindings({
|
||||
zoomRegions,
|
||||
trimRegions,
|
||||
clipRegions,
|
||||
annotationRegions,
|
||||
speedRegions,
|
||||
audioRegions,
|
||||
onZoomSpanChange,
|
||||
onTrimSpanChange,
|
||||
onClipSpanChange,
|
||||
onAnnotationSpanChange,
|
||||
onSpeedSpanChange,
|
||||
onAudioSpanChange,
|
||||
});
|
||||
|
||||
// Keep newly added timeline regions at the original short default instead of
|
||||
// scaling them with the full recording length.
|
||||
const {
|
||||
defaultRegionDurationMs,
|
||||
canPlaceZoomAtMs,
|
||||
addZoomAtMs,
|
||||
handleAddZoom,
|
||||
handleSuggestZooms,
|
||||
} = useTimelineZoomActions({
|
||||
handleSplitClip,
|
||||
handleAddAudio,
|
||||
handleAddAnnotation,
|
||||
} = useTimelineEditorRuntime({
|
||||
ref,
|
||||
videoDuration,
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
zoomRegions,
|
||||
clipRegions,
|
||||
safeMinDurationMs,
|
||||
cursorTelemetry,
|
||||
disableSuggestedZooms,
|
||||
autoSuggestZoomsTrigger,
|
||||
onAutoSuggestZoomsConsumed,
|
||||
disableSuggestedZooms,
|
||||
zoomRegions,
|
||||
onZoomAdded,
|
||||
onZoomSuggested,
|
||||
});
|
||||
|
||||
const handleSplitClip = useCallback(() => {
|
||||
if (!videoDuration || videoDuration === 0 || totalMs === 0 || !onClipSplit) {
|
||||
return;
|
||||
}
|
||||
onClipSplit(currentTimeMs);
|
||||
}, [videoDuration, totalMs, currentTimeMs, onClipSplit]);
|
||||
|
||||
const { handleAddAudio } = useTimelineAudioActions({
|
||||
videoDuration,
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
onZoomSpanChange,
|
||||
onZoomDelete,
|
||||
selectedZoomId,
|
||||
onSelectZoom,
|
||||
trimRegions,
|
||||
onTrimSpanChange,
|
||||
clipRegions,
|
||||
onClipSplit,
|
||||
onClipSpanChange,
|
||||
onClipDelete,
|
||||
selectedClipId,
|
||||
onSelectClip,
|
||||
annotationRegions,
|
||||
onAnnotationAdded,
|
||||
onAnnotationSpanChange,
|
||||
onAnnotationDelete,
|
||||
selectedAnnotationId,
|
||||
onSelectAnnotation,
|
||||
speedRegions,
|
||||
onSpeedSpanChange,
|
||||
audioRegions,
|
||||
onAudioAdded,
|
||||
});
|
||||
|
||||
const { handleAddAnnotation } = useTimelineAnnotationsActions({
|
||||
videoDuration,
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
defaultRegionDurationMs,
|
||||
onAnnotationAdded,
|
||||
});
|
||||
|
||||
useTimelineKeyboardShortcuts({
|
||||
onAudioSpanChange,
|
||||
onAudioDelete,
|
||||
selectedAudioId,
|
||||
onSelectAudio,
|
||||
isMac,
|
||||
keyShortcuts,
|
||||
isTimelineFocusedRef,
|
||||
hasAnyTimelineBlocks,
|
||||
annotationCount: annotationRegions.length,
|
||||
selectedKeyframeId,
|
||||
selectedZoomId,
|
||||
selectedClipId,
|
||||
selectedAnnotationId,
|
||||
selectedAudioId,
|
||||
selectAllBlocksActive,
|
||||
setSelectAllBlocksActive,
|
||||
setSelectedKeyframeId,
|
||||
addKeyframe,
|
||||
handleAddZoom,
|
||||
handleSplitClip,
|
||||
handleAddAnnotation: () => handleAddAnnotation(),
|
||||
deleteAllBlocks,
|
||||
deleteSelectedKeyframe,
|
||||
deleteSelectedZoom,
|
||||
deleteSelectedClip,
|
||||
deleteSelectedAnnotation,
|
||||
deleteSelectedAudio,
|
||||
cycleAnnotationsAtCurrentTime,
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
addZoom: handleAddZoom,
|
||||
suggestZooms: handleSuggestZooms,
|
||||
splitClip: handleSplitClip,
|
||||
addAnnotation: handleAddAnnotation,
|
||||
addAudio: handleAddAudio,
|
||||
keyframes,
|
||||
}),
|
||||
[
|
||||
handleAddAnnotation,
|
||||
handleAddAudio,
|
||||
handleAddZoom,
|
||||
handleSuggestZooms,
|
||||
handleSplitClip,
|
||||
keyframes,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<TimelineEditorShell
|
||||
videoDuration={videoDuration}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useTimelineContext } from "dnd-timeline";
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import type { MouseEvent } from "react";
|
||||
import {
|
||||
getTimelineContentMinHeightPx,
|
||||
getTimelineRowsMinHeightPx,
|
||||
@@ -75,7 +76,7 @@ export default function TimelineCanvas({
|
||||
);
|
||||
|
||||
const handleTimelineClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
if (!onSeek || videoDurationMs <= 0) return;
|
||||
|
||||
onSelectZoom?.(null);
|
||||
|
||||
@@ -31,7 +31,7 @@ interface TimelineCanvasRowsProps {
|
||||
onSelectAnnotation?: (id: string | null) => void;
|
||||
onSelectAudio?: (id: string | null) => void;
|
||||
audioPeaks?: AudioPeaksData | null;
|
||||
direction: "ltr" | "rtl";
|
||||
direction: string;
|
||||
canShowGhostZoom: boolean;
|
||||
ghostStartMs: number | null;
|
||||
ghostStartOffsetPx: number;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useState, type MouseEvent } from "react";
|
||||
|
||||
interface UseTimelineCanvasHoverParams {
|
||||
direction: "ltr" | "rtl";
|
||||
direction: string;
|
||||
sidebarWidth: number;
|
||||
rangeStart: number;
|
||||
rangeEnd: number;
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
import type { Span } from "dnd-timeline";
|
||||
import { useCallback, useImperativeHandle } from "react";
|
||||
import type { ForwardedRef, RefObject } from "react";
|
||||
import type { ShortcutBinding } from "@/lib/shortcuts";
|
||||
import { useTimelineDndBindings } from "./useTimelineDndBindings";
|
||||
import { useTimelineAnnotationsActions } from "./useTimelineAnnotationsActions";
|
||||
import { useTimelineAudioActions } from "./useTimelineAudioActions";
|
||||
import { useTimelineKeyboardShortcuts } from "./useTimelineKeyboardShortcuts";
|
||||
import { useTimelineNormalization } from "./useTimelineNormalization";
|
||||
import { useTimelineSelection } from "./useTimelineSelection";
|
||||
import { useTimelineZoomActions } from "./useTimelineZoomActions";
|
||||
import type {
|
||||
AnnotationRegion,
|
||||
AudioRegion,
|
||||
ClipRegion,
|
||||
CursorTelemetryPoint,
|
||||
SpeedRegion,
|
||||
TrimRegion,
|
||||
ZoomFocus,
|
||||
ZoomRegion,
|
||||
} from "../../types";
|
||||
import type { TimelineEditorHandle } from "../TimelineEditor";
|
||||
|
||||
interface UseTimelineEditorRuntimeParams {
|
||||
ref: ForwardedRef<TimelineEditorHandle>;
|
||||
videoDuration: number;
|
||||
totalMs: number;
|
||||
currentTimeMs: number;
|
||||
safeMinDurationMs: number;
|
||||
cursorTelemetry: CursorTelemetryPoint[];
|
||||
autoSuggestZoomsTrigger: number;
|
||||
onAutoSuggestZoomsConsumed?: () => void;
|
||||
disableSuggestedZooms: boolean;
|
||||
zoomRegions: ZoomRegion[];
|
||||
onZoomAdded: (span: Span) => void;
|
||||
onZoomSuggested?: (span: Span, focus: ZoomFocus) => void;
|
||||
onZoomSpanChange: (id: string, span: Span) => void;
|
||||
onZoomDelete: (id: string) => void;
|
||||
selectedZoomId: string | null;
|
||||
onSelectZoom: (id: string | null) => void;
|
||||
trimRegions: TrimRegion[];
|
||||
onTrimSpanChange?: (id: string, span: Span) => void;
|
||||
clipRegions: ClipRegion[];
|
||||
onClipSplit?: (splitMs: number) => void;
|
||||
onClipSpanChange?: (id: string, span: Span) => void;
|
||||
onClipDelete?: (id: string) => void;
|
||||
selectedClipId?: string | null;
|
||||
onSelectClip?: (id: string | null) => void;
|
||||
annotationRegions: AnnotationRegion[];
|
||||
onAnnotationAdded?: (span: Span, trackIndex?: number) => void;
|
||||
onAnnotationSpanChange?: (id: string, span: Span, trackIndex?: number) => void;
|
||||
onAnnotationDelete?: (id: string) => void;
|
||||
selectedAnnotationId?: string | null;
|
||||
onSelectAnnotation?: (id: string | null) => void;
|
||||
speedRegions: SpeedRegion[];
|
||||
onSpeedSpanChange?: (id: string, span: Span) => void;
|
||||
audioRegions: AudioRegion[];
|
||||
onAudioAdded?: (span: Span, audioPath: string, trackIndex?: number) => void;
|
||||
onAudioSpanChange?: (id: string, span: Span, trackIndex?: number) => void;
|
||||
onAudioDelete?: (id: string) => void;
|
||||
selectedAudioId?: string | null;
|
||||
onSelectAudio?: (id: string | null) => void;
|
||||
isMac: boolean;
|
||||
keyShortcuts: {
|
||||
addKeyframe: ShortcutBinding;
|
||||
addZoom: ShortcutBinding;
|
||||
splitClip: ShortcutBinding;
|
||||
addAnnotation: ShortcutBinding;
|
||||
deleteSelected: ShortcutBinding;
|
||||
};
|
||||
isTimelineFocusedRef: RefObject<boolean>;
|
||||
}
|
||||
|
||||
export function useTimelineEditorRuntime({
|
||||
ref,
|
||||
videoDuration,
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
safeMinDurationMs,
|
||||
cursorTelemetry,
|
||||
autoSuggestZoomsTrigger,
|
||||
onAutoSuggestZoomsConsumed,
|
||||
disableSuggestedZooms,
|
||||
zoomRegions,
|
||||
onZoomAdded,
|
||||
onZoomSuggested,
|
||||
onZoomSpanChange,
|
||||
onZoomDelete,
|
||||
selectedZoomId,
|
||||
onSelectZoom,
|
||||
trimRegions,
|
||||
onTrimSpanChange,
|
||||
clipRegions,
|
||||
onClipSplit,
|
||||
onClipSpanChange,
|
||||
onClipDelete,
|
||||
selectedClipId,
|
||||
onSelectClip,
|
||||
annotationRegions,
|
||||
onAnnotationAdded,
|
||||
onAnnotationSpanChange,
|
||||
onAnnotationDelete,
|
||||
selectedAnnotationId,
|
||||
onSelectAnnotation,
|
||||
speedRegions,
|
||||
onSpeedSpanChange,
|
||||
audioRegions,
|
||||
onAudioAdded,
|
||||
onAudioSpanChange,
|
||||
onAudioDelete,
|
||||
selectedAudioId,
|
||||
onSelectAudio,
|
||||
isMac,
|
||||
keyShortcuts,
|
||||
isTimelineFocusedRef,
|
||||
}: UseTimelineEditorRuntimeParams) {
|
||||
const {
|
||||
keyframes,
|
||||
selectedKeyframeId,
|
||||
setSelectedKeyframeId,
|
||||
selectAllBlocksActive,
|
||||
setSelectAllBlocksActive,
|
||||
hasAnyTimelineBlocks,
|
||||
addKeyframe,
|
||||
deleteSelectedKeyframe,
|
||||
handleKeyframeMove,
|
||||
deleteSelectedZoom,
|
||||
deleteSelectedClip,
|
||||
deleteSelectedAnnotation,
|
||||
deleteSelectedAudio,
|
||||
clearSelectedBlocks,
|
||||
deleteAllBlocks,
|
||||
handleSelectZoom,
|
||||
handleSelectClip,
|
||||
handleSelectAnnotation,
|
||||
handleSelectAudio,
|
||||
cycleAnnotationsAtCurrentTime,
|
||||
} = useTimelineSelection({
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
zoomRegions,
|
||||
clipRegions,
|
||||
annotationRegions,
|
||||
audioRegions,
|
||||
selectedZoomId,
|
||||
selectedClipId,
|
||||
selectedAnnotationId,
|
||||
selectedAudioId,
|
||||
onZoomDelete,
|
||||
onClipDelete,
|
||||
onAnnotationDelete,
|
||||
onAudioDelete,
|
||||
onSelectZoom,
|
||||
onSelectClip,
|
||||
onSelectAnnotation,
|
||||
onSelectAudio,
|
||||
});
|
||||
|
||||
useTimelineNormalization({
|
||||
totalMs,
|
||||
safeMinDurationMs,
|
||||
zoomRegions,
|
||||
trimRegions,
|
||||
speedRegions,
|
||||
audioRegions,
|
||||
onZoomSpanChange,
|
||||
onTrimSpanChange,
|
||||
onSpeedSpanChange,
|
||||
onAudioSpanChange,
|
||||
});
|
||||
|
||||
const { hasOverlap, timelineItems, allRegionSpans, getResolvedDropRowId, handleItemSpanChange } =
|
||||
useTimelineDndBindings({
|
||||
zoomRegions,
|
||||
trimRegions,
|
||||
clipRegions,
|
||||
annotationRegions,
|
||||
speedRegions,
|
||||
audioRegions,
|
||||
onZoomSpanChange,
|
||||
onTrimSpanChange,
|
||||
onClipSpanChange,
|
||||
onAnnotationSpanChange,
|
||||
onSpeedSpanChange,
|
||||
onAudioSpanChange,
|
||||
});
|
||||
|
||||
const { defaultRegionDurationMs, canPlaceZoomAtMs, addZoomAtMs, handleAddZoom, handleSuggestZooms } =
|
||||
useTimelineZoomActions({
|
||||
videoDuration,
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
zoomRegions,
|
||||
clipRegions,
|
||||
cursorTelemetry,
|
||||
disableSuggestedZooms,
|
||||
autoSuggestZoomsTrigger,
|
||||
onAutoSuggestZoomsConsumed,
|
||||
onZoomAdded,
|
||||
onZoomSuggested,
|
||||
});
|
||||
|
||||
const handleSplitClip = useCallback(() => {
|
||||
if (!videoDuration || videoDuration === 0 || totalMs === 0 || !onClipSplit) {
|
||||
return;
|
||||
}
|
||||
onClipSplit(currentTimeMs);
|
||||
}, [videoDuration, totalMs, currentTimeMs, onClipSplit]);
|
||||
|
||||
const { handleAddAudio } = useTimelineAudioActions({
|
||||
videoDuration,
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
audioRegions,
|
||||
onAudioAdded,
|
||||
});
|
||||
|
||||
const { handleAddAnnotation } = useTimelineAnnotationsActions({
|
||||
videoDuration,
|
||||
totalMs,
|
||||
currentTimeMs,
|
||||
defaultRegionDurationMs,
|
||||
onAnnotationAdded,
|
||||
});
|
||||
|
||||
useTimelineKeyboardShortcuts({
|
||||
isMac,
|
||||
keyShortcuts,
|
||||
isTimelineFocusedRef,
|
||||
hasAnyTimelineBlocks,
|
||||
annotationCount: annotationRegions.length,
|
||||
selectedKeyframeId,
|
||||
selectedZoomId,
|
||||
selectedClipId,
|
||||
selectedAnnotationId,
|
||||
selectedAudioId,
|
||||
selectAllBlocksActive,
|
||||
setSelectAllBlocksActive,
|
||||
setSelectedKeyframeId,
|
||||
addKeyframe,
|
||||
handleAddZoom,
|
||||
handleSplitClip,
|
||||
handleAddAnnotation: () => handleAddAnnotation(),
|
||||
deleteAllBlocks,
|
||||
deleteSelectedKeyframe,
|
||||
deleteSelectedZoom,
|
||||
deleteSelectedClip,
|
||||
deleteSelectedAnnotation,
|
||||
deleteSelectedAudio,
|
||||
cycleAnnotationsAtCurrentTime,
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
addZoom: handleAddZoom,
|
||||
suggestZooms: handleSuggestZooms,
|
||||
splitClip: handleSplitClip,
|
||||
addAnnotation: handleAddAnnotation,
|
||||
addAudio: handleAddAudio,
|
||||
keyframes,
|
||||
}),
|
||||
[handleAddAnnotation, handleAddAudio, handleAddZoom, handleSuggestZooms, handleSplitClip, keyframes],
|
||||
);
|
||||
|
||||
return {
|
||||
keyframes,
|
||||
selectedKeyframeId,
|
||||
setSelectedKeyframeId,
|
||||
selectAllBlocksActive,
|
||||
setSelectAllBlocksActive,
|
||||
handleKeyframeMove,
|
||||
clearSelectedBlocks,
|
||||
handleSelectZoom,
|
||||
handleSelectClip,
|
||||
handleSelectAnnotation,
|
||||
handleSelectAudio,
|
||||
hasOverlap,
|
||||
timelineItems,
|
||||
allRegionSpans,
|
||||
getResolvedDropRowId,
|
||||
handleItemSpanChange,
|
||||
canPlaceZoomAtMs,
|
||||
addZoomAtMs,
|
||||
handleAddZoom,
|
||||
handleSuggestZooms,
|
||||
handleSplitClip,
|
||||
handleAddAudio,
|
||||
handleAddAnnotation,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user