@@ -27,11 +27,14 @@ type Props = {
2727 onToggleDelete : ( ) => void ;
2828 onPrune : ( ) => void ;
2929 onClearRange : ( start : number , end : number ) => void ;
30+ /** delete / restore every frame of the marked range (shown only while a range is marked) */
31+ onDeleteRange : ( start : number , end : number ) => void ;
32+ onRestoreRange : ( start : number , end : number ) => void ;
3033} ;
3134
3235export function CleanupPanel ( {
3336 frame, states, lanes, scope, activeLabel, onScope, range, clearBlockedReason, lockReason, error, onRange,
34- onToggleDelete, onPrune, onClearRange,
37+ onToggleDelete, onPrune, onClearRange, onDeleteRange , onRestoreRange ,
3538} : Props ) {
3639 const last = Math . max ( 0 , states . length - 1 ) ;
3740 const clampIndex = ( v : string ) => Math . max ( 0 , Math . min ( last , Math . floor ( Number ( v ) ) || 0 ) ) ;
@@ -53,6 +56,10 @@ export function CleanupPanel({
5356 ...( keyframes > 0 ? [ plural ( keyframes , "keyframe" , "keyframes" ) ] : [ ] ) ,
5457 ...( deleted > 0 ? [ `any mask under the ${ plural ( deleted , "deleted frame" , "deleted frames" ) } ` ] : [ ] ) ,
5558 ] ;
59+ // frames of the marked range that Delete / Restore would actually change
60+ const inRange = valid ? states . slice ( start , end + 1 ) : [ ] ;
61+ const toRestore = inRange . filter ( ( s ) => s === "deleted" ) . length ;
62+ const toDelete = inRange . length - toRestore ;
5663 const outcome = ! valid
5764 ? "The first frame must not come after the last."
5865 : parts . length === 0
@@ -71,8 +78,8 @@ export function CleanupPanel({
7178 </ summary >
7279 < div className = "mt-2 flex flex-col gap-2" >
7380 < p className = "note" >
74- To clear masks from a stretch of frames, mark it first: hold < kbd > Shift</ kbd > and drag along the timeline,
75- or press < kbd > [</ kbd > and < kbd > ]</ kbd > on its first and last frame.
81+ To clear masks from a stretch of frames, or delete or restore it, mark it first: hold < kbd > Shift</ kbd > and
82+ drag along the timeline, or press < kbd > [</ kbd > and < kbd > ]</ kbd > on its first and last frame.
7683 </ p >
7784 < div className = "flex items-center gap-1" >
7885 < input aria-label = "Range start" type = "number" min = { 0 } max = { last } className = "field w-16" value = { start }
@@ -99,6 +106,25 @@ export function CleanupPanel({
99106 </ button >
100107 < p className = "note" > { lockReason ?? clearBlockedReason ?? outcome } </ p >
101108
109+ { range !== null && (
110+ < >
111+ < div className = "mt-1 flex gap-1" >
112+ < button type = "button" className = "btn flex-1" disabled = { ! valid || toDelete === 0 || lockReason !== null }
113+ onClick = { ( ) => onDeleteRange ( start , end ) } >
114+ Delete { where }
115+ </ button >
116+ < button type = "button" className = "btn flex-1" disabled = { ! valid || toRestore === 0 || lockReason !== null }
117+ onClick = { ( ) => onRestoreRange ( start , end ) } >
118+ Restore { where }
119+ </ button >
120+ </ div >
121+ < p className = "note" >
122+ { ! valid
123+ ? "The first frame must not come after the last."
124+ : `In ${ where } : ${ plural ( toDelete , "frame" , "frames" ) } to delete, ${ plural ( toRestore , "deleted frame" , "deleted frames" ) } to restore.` }
125+ </ p >
126+ </ >
127+ ) }
102128 < div className = "mt-1 flex gap-1" >
103129 < button type = "button" aria-keyshortcuts = "Delete" className = "btn flex-1" disabled = { lockReason !== null } onClick = { onToggleDelete } >
104130 { states [ frame ] === "deleted" ? "Restore frame" : "Delete frame" } < kbd aria-hidden > Del</ kbd >
0 commit comments