@@ -4,13 +4,18 @@ import {
44 getNextTimelineZoomPercent ,
55 getTimelineZoomPercent ,
66} from "../player/components/timelineZoom" ;
7+ import { useTimelineZoom } from "../player/components/useTimelineZoom" ;
78import { getTimelineToggleTitle } from "../utils/timelineDiscovery" ;
89import { usePlayerStore , type TimelineElement } from "../player" ;
9- import { STUDIO_KEYFRAMES_ENABLED } from "./editor/manualEditingAvailability" ;
10+ import {
11+ STUDIO_KEYFRAMES_ENABLED ,
12+ STUDIO_RAZOR_TOOL_ENABLED ,
13+ } from "./editor/manualEditingAvailability" ;
1014import { Tooltip } from "./ui" ;
1115import { Scissors } from "../icons/SystemIcons" ;
1216import type { GsapAnimation } from "@hyperframes/core/gsap-parser" ;
1317import type { DomEditSelection } from "./editor/domEditingTypes" ;
18+ import { canSplitElement } from "../utils/timelineElementSplit" ;
1419
1520function AutoKeyframeToggle ( ) {
1621 const enabled = usePlayerStore ( ( s ) => s . autoKeyframeEnabled ) ;
@@ -58,14 +63,17 @@ function useKeyframeToggle(session?: DomEditSessionSlice) {
5863 const anims = session . selectedGsapAnimations ;
5964 const kfAnim = anims . find ( ( a ) => a . keyframes ) ;
6065
66+ const computePct = ( time : number ) => {
67+ const elStart = Number . parseFloat ( sel ?. dataAttributes ?. start ?? "0" ) || 0 ;
68+ const elDuration = Number . parseFloat ( sel ?. dataAttributes ?. duration ?? "1" ) || 1 ;
69+ return elDuration > 0
70+ ? Math . max ( 0 , Math . min ( 100 , Math . round ( ( ( time - elStart ) / elDuration ) * 1000 ) / 10 ) )
71+ : 0 ;
72+ } ;
73+
6174 let state : "active" | "inactive" | "none" = "none" ;
6275 if ( kfAnim ?. keyframes && sel ) {
63- const elStart = Number . parseFloat ( sel . dataAttributes ?. start ?? "0" ) || 0 ;
64- const elDuration = Number . parseFloat ( sel . dataAttributes ?. duration ?? "1" ) || 1 ;
65- const pct =
66- elDuration > 0
67- ? Math . max ( 0 , Math . min ( 100 , Math . round ( ( ( currentTime - elStart ) / elDuration ) * 1000 ) / 10 ) )
68- : 0 ;
76+ const pct = computePct ( currentTime ) ;
6977 state = kfAnim . keyframes . keyframes . some ( ( k ) => Math . abs ( k . percentage - pct ) <= 1 )
7078 ? "active"
7179 : "inactive" ;
@@ -74,15 +82,15 @@ function useKeyframeToggle(session?: DomEditSessionSlice) {
7482 return { state, onToggle : sel ? onToggle : undefined } ;
7583}
7684
85+ // fallow-ignore-next-line complexity
7786export function TimelineToolbar ( {
7887 toggleTimelineVisibility,
7988 domEditSession,
8089 onSplitElement,
8190} : TimelineToolbarProps ) {
82- const zoomMode = usePlayerStore ( ( s ) => s . zoomMode ) ;
83- const manualZoomPercent = usePlayerStore ( ( s ) => s . manualZoomPercent ) ;
84- const setZoomMode = usePlayerStore ( ( s ) => s . setZoomMode ) ;
85- const setManualZoomPercent = usePlayerStore ( ( s ) => s . setManualZoomPercent ) ;
91+ const activeTool = usePlayerStore ( ( s ) => s . activeTool ) ;
92+ const setActiveTool = usePlayerStore ( ( s ) => s . setActiveTool ) ;
93+ const { zoomMode, manualZoomPercent, setZoomMode, setManualZoomPercent } = useTimelineZoom ( ) ;
8694 const displayedTimelineZoomPercent = getTimelineZoomPercent ( zoomMode , manualZoomPercent ) ;
8795 const { state : keyframeState , onToggle : onToggleKeyframe } = useKeyframeToggle ( domEditSession ) ;
8896
@@ -93,6 +101,38 @@ export function TimelineToolbar({
93101 < div className = "text-[10px] font-medium uppercase tracking-[0.16em] text-neutral-500" >
94102 Timeline
95103 </ div >
104+ { STUDIO_RAZOR_TOOL_ENABLED && (
105+ < div className = "flex items-center border border-neutral-800 rounded overflow-hidden" >
106+ < Tooltip label = "Selection tool (V)" >
107+ < button
108+ type = "button"
109+ onClick = { ( ) => setActiveTool ( "select" ) }
110+ className = { `flex h-6 w-6 items-center justify-center transition-colors ${
111+ activeTool === "select"
112+ ? "bg-neutral-700 text-neutral-200"
113+ : "text-neutral-500 hover:text-neutral-300"
114+ } `}
115+ >
116+ < svg width = "12" height = "12" viewBox = "0 0 12 12" fill = "currentColor" >
117+ < path d = "M2 0.5L10 6L6.5 6.5L8.5 11L6.5 11.5L4.5 7L2 9Z" />
118+ </ svg >
119+ </ button >
120+ </ Tooltip >
121+ < Tooltip label = "Razor tool (B)" >
122+ < button
123+ type = "button"
124+ onClick = { ( ) => setActiveTool ( "razor" ) }
125+ className = { `flex h-6 w-6 items-center justify-center transition-colors ${
126+ activeTool === "razor"
127+ ? "bg-neutral-700 text-neutral-200"
128+ : "text-neutral-500 hover:text-neutral-300"
129+ } `}
130+ >
131+ < Scissors size = { 11 } />
132+ </ button >
133+ </ Tooltip >
134+ </ div >
135+ ) }
96136 { STUDIO_KEYFRAMES_ENABLED && onToggleKeyframe && (
97137 < >
98138 < Tooltip
@@ -138,9 +178,7 @@ export function TimelineToolbar({
138178 const el = selectedElementId
139179 ? elements . find ( ( e ) => ( e . key ?? e . id ) === selectedElementId )
140180 : null ;
141- const splittable =
142- el && ! el . compositionSrc && [ "video" , "audio" , "img" ] . includes ( el . tag ) ;
143- if ( ! splittable ) return null ;
181+ if ( ! el || ! canSplitElement ( el ) ) return null ;
144182 const canSplit = currentTime > el . start && currentTime < el . start + el . duration ;
145183 return (
146184 < Tooltip label = "Split clip at playhead (S)" >
0 commit comments