@@ -15,6 +15,8 @@ import {
1515 defaultSettings ,
1616 draftLaneLayout ,
1717 readFileAsDataUrl ,
18+ resizeRegionFromHandle ,
19+ retuneDraftsForSettings ,
1820 syncLaneConfigs ,
1921 type AnalysisResult ,
2022 type AnalysisSettings ,
@@ -44,6 +46,7 @@ import type {
4446 LaneDraft ,
4547 PanelAsset ,
4648 Rect ,
49+ ResizeHandle ,
4750 SelectedRegion ,
4851 StatisticalSettings ,
4952} from './types'
@@ -126,6 +129,7 @@ function App() {
126129 startX : number
127130 startY : number
128131 region : SelectedRegion
132+ handle ?: ResizeHandle
129133 } | null > ( null )
130134 const [ showAutosaveBanner , setShowAutosaveBanner ] = useState ( Boolean ( restoredProject ) )
131135 const [ installPrompt , setInstallPrompt ] = useState < BeforeInstallPromptEvent | null > ( null )
@@ -368,10 +372,35 @@ function App() {
368372 if ( ! activePanelId ) {
369373 return
370374 }
375+ const previousSettings = settingsByPanelId [ activePanelId ] ?? settings
376+ const nextSettings = { ...previousSettings , [ key ] : value }
371377 setSettingsByPanelId ( ( current ) => ( {
372378 ...current ,
373- [ activePanelId ] : { ... ( current [ activePanelId ] ?? settings ) , [ key ] : value } ,
379+ [ activePanelId ] : nextSettings ,
374380 } ) )
381+
382+ if (
383+ draftsByPanelId [ activePanelId ] ?. length &&
384+ [ 'laneWidthScale' , 'laneHeight' , 'bandWidthScale' , 'bandHeight' , 'primaryY' , 'referenceY' ] . includes (
385+ String ( key ) ,
386+ )
387+ ) {
388+ const bounds = {
389+ x : 0 ,
390+ y : 0 ,
391+ width : activeAnalysis ?. width ?? 1000 ,
392+ height : activeAnalysis ?. height ?? 620 ,
393+ }
394+ setDraftsByPanelId ( ( current ) => ( {
395+ ...current ,
396+ [ activePanelId ] : retuneDraftsForSettings ( {
397+ drafts : current [ activePanelId ] ?? [ ] ,
398+ previous : previousSettings ,
399+ next : nextSettings ,
400+ bounds,
401+ } ) ,
402+ } ) )
403+ }
375404 }
376405
377406 function updateStatisticalSettings < K extends keyof StatisticalSettings > (
@@ -700,16 +729,20 @@ function App() {
700729 function handleOverlayMouseDown (
701730 event : ReactMouseEvent < HTMLButtonElement > ,
702731 region : SelectedRegion ,
732+ handle ?: ResizeHandle ,
703733 ) {
734+ event . preventDefault ( )
735+ event . stopPropagation ( )
704736 setSelectedRegion ( region )
705737 setDragState ( {
706738 startX : event . clientX ,
707739 startY : event . clientY ,
708740 region,
741+ handle,
709742 } )
710743 }
711744
712- function handleOverlayMouseUp ( event : ReactMouseEvent < HTMLDivElement > ) {
745+ function handleOverlayMouseMove ( event : ReactMouseEvent < HTMLDivElement > ) {
713746 if ( ! dragState || ! activePanelId ) {
714747 return
715748 }
@@ -730,10 +763,34 @@ function App() {
730763 ...current ,
731764 [ activePanelId ] : current [ activePanelId ] . map ( ( draft ) =>
732765 draft . id === dragState . region . laneId
733- ? applyRegionDelta ( draft , dragState . region , deltaX , deltaY , bounds )
766+ ? dragState . handle
767+ ? resizeRegionFromHandle (
768+ draft ,
769+ dragState . region ,
770+ dragState . handle ,
771+ deltaX ,
772+ deltaY ,
773+ dragState . region . target === 'lane' ? bounds : draft . lane ,
774+ )
775+ : applyRegionDelta ( draft , dragState . region , deltaX , deltaY , bounds )
734776 : draft ,
735777 ) ,
736778 } ) )
779+ setDragState ( ( current ) =>
780+ current
781+ ? {
782+ ...current ,
783+ startX : event . clientX ,
784+ startY : event . clientY ,
785+ }
786+ : null ,
787+ )
788+ }
789+
790+ function handleOverlayMouseUp ( ) {
791+ if ( ! dragState ) {
792+ return
793+ }
737794 setDragState ( null )
738795 }
739796
@@ -784,6 +841,31 @@ function App() {
784841 setShowAutosaveBanner ( false )
785842 }
786843
844+ function renderResizeHandles (
845+ laneId : string ,
846+ target : SelectedRegion [ 'target' ] ,
847+ ) {
848+ const handles : ResizeHandle [ ] = [ 'nw' , 'ne' , 'sw' , 'se' ]
849+ return handles . map ( ( handle ) => (
850+ < button
851+ key = { `${ laneId } -${ target } -${ handle } ` }
852+ type = "button"
853+ className = { `resize-handle resize-${ handle } ` }
854+ onMouseDown = { ( event ) =>
855+ handleOverlayMouseDown (
856+ event ,
857+ {
858+ laneId,
859+ target,
860+ } ,
861+ handle ,
862+ )
863+ }
864+ onClick = { ( event ) => event . stopPropagation ( ) }
865+ />
866+ ) )
867+ }
868+
787869 function handleDropzoneDragOver ( event : ReactDragEvent < HTMLDivElement > ) {
788870 event . preventDefault ( )
789871 if ( ! isDragActive ) {
@@ -1084,7 +1166,13 @@ function App() {
10841166 alt = { activePanel . name }
10851167 className = "stage-image"
10861168 />
1087- < div className = "overlay" style = { overlayStyle ( stageMetrics ) } onMouseUp = { handleOverlayMouseUp } >
1169+ < div
1170+ className = "overlay"
1171+ style = { overlayStyle ( stageMetrics ) }
1172+ onMouseMove = { handleOverlayMouseMove }
1173+ onMouseUp = { handleOverlayMouseUp }
1174+ onMouseLeave = { handleOverlayMouseUp }
1175+ >
10881176 { activeGeometries . map ( ( geometry , index ) => (
10891177 < div
10901178 key = { geometry . id }
@@ -1108,6 +1196,9 @@ function App() {
11081196 }
11091197 onClick = { ( ) => handleSelectRegion ( { laneId : geometry . id , target : 'lane' } ) }
11101198 />
1199+ { isSelected ( selectedRegion , geometry . id , 'lane' )
1200+ ? renderResizeHandles ( geometry . id , 'lane' )
1201+ : null }
11111202 < button
11121203 type = "button"
11131204 className = { `band-outline primary ${
@@ -1122,6 +1213,9 @@ function App() {
11221213 }
11231214 onClick = { ( ) => handleSelectRegion ( { laneId : geometry . id , target : 'primary' } ) }
11241215 />
1216+ { isSelected ( selectedRegion , geometry . id , 'primary' )
1217+ ? renderResizeHandles ( geometry . id , 'primary' )
1218+ : null }
11251219 { geometry . reference ? (
11261220 < >
11271221 < button
@@ -1140,6 +1234,9 @@ function App() {
11401234 handleSelectRegion ( { laneId : geometry . id , target : 'reference' } )
11411235 }
11421236 />
1237+ { isSelected ( selectedRegion , geometry . id , 'reference' )
1238+ ? renderResizeHandles ( geometry . id , 'reference' )
1239+ : null }
11431240 </ >
11441241 ) : null }
11451242 </ div >
0 commit comments