@@ -9,21 +9,45 @@ import { ctx } from '../three'
99import { redrawIntraLayerEdges , toggleInterLayerEdgesRendering } from './edge'
1010import { getSelectedLayers , initialSpreadLayers } from './layer'
1111import { getSelectedNodes } from './node'
12+ import { history } from '../commands/base'
13+ import {
14+ captureTransforms ,
15+ TransformCommand ,
16+ type TransformSnapshot ,
17+ } from '../commands/scene'
1218
1319type Axis = 'X' | 'Y' | 'Z'
1420
1521const REPEAT_MS = 70
1622
1723let repeatId : ReturnType < typeof setInterval > | undefined
24+ // Hold-to-repeat interactions register one undo entry per hold: snapshot on
25+ // press, command pushed on release (only if a tick actually ran).
26+ let repeatBefore : TransformSnapshot | null = null
27+ let repeatMoved = false
1828
1929function stopRepeat ( ) : void {
2030 clearInterval ( repeatId )
2131 repeatId = undefined
32+ if ( repeatBefore && repeatMoved )
33+ history . execute (
34+ new TransformCommand (
35+ 'Nav control move' ,
36+ repeatBefore ,
37+ captureTransforms ( )
38+ )
39+ )
40+ repeatBefore = null
41+ repeatMoved = false
2242}
2343
2444function startRepeat ( fn : ( ) => void ) : void {
2545 stopRepeat ( )
26- repeatId = setInterval ( fn , REPEAT_MS )
46+ repeatBefore = captureTransforms ( )
47+ repeatId = setInterval ( ( ) => {
48+ repeatMoved = true
49+ fn ( )
50+ } , REPEAT_MS )
2751}
2852
2953function sliderValue ( id : string ) : number {
@@ -52,8 +76,12 @@ function rotateScene(direction: number, axis: Axis): void {
5276
5377export function recenterNetwork ( ) : void {
5478 if ( ctx . scene ?. exists ( ) ) {
79+ const before = captureTransforms ( )
5580 ctx . scene . recenter ( )
5681 raiseMovedFlags ( )
82+ history . execute (
83+ new TransformCommand ( 'Recenter network' , before , captureTransforms ( ) )
84+ )
5785 }
5886}
5987
@@ -89,8 +117,12 @@ function moveLayers(direction: number, axis: Axis): void {
89117}
90118
91119function spreadLayers ( direction : number ) : void {
120+ const before = captureTransforms ( )
92121 initialSpreadLayers ( direction )
93122 raiseMovedFlags ( )
123+ history . execute (
124+ new TransformCommand ( 'Spread layers' , before , captureTransforms ( ) )
125+ )
94126}
95127
96128function scaleLayers ( ) : void {
@@ -120,13 +152,17 @@ function scaleLayers(): void {
120152function spreadNodes ( multiplier : number ) : void {
121153 const selected = getSelectedNodes ( )
122154 if ( selected . length === 0 ) return alert ( 'Please select at least one node.' )
155+ const before = captureTransforms ( )
123156 for ( const i of selected ) {
124157 const node = ctx . nodeObjects [ i ]
125158 node . setPosition ( 'y' , node . getPosition ( 'y' ) * multiplier )
126159 node . setPosition ( 'z' , node . getPosition ( 'z' ) * multiplier )
127160 }
128161 redrawIntraLayerEdges ( )
129162 raiseMovedFlags ( )
163+ history . execute (
164+ new TransformCommand ( 'Spread nodes' , before , captureTransforms ( ) )
165+ )
130166}
131167
132168function moveNodes ( direction : number , axis : Axis ) : void {
@@ -350,10 +386,61 @@ function attachNavControls(): void {
350386 }
351387 }
352388
353- const scaleL = document . getElementById ( 'layerScaleSlider' ) !
354- scaleL . addEventListener ( 'input' , scaleLayers )
355- const scaleN = document . getElementById ( 'nodeScaleSlider' ) !
356- scaleN . addEventListener ( 'input' , scaleNodes )
389+ // Live-scale on input; one undo entry per drag, finalized on 'change'.
390+ // `sync` re-applies the restored scale to the slider + its value label on
391+ // undo/redo (the transform snapshot only covers the 3D objects).
392+ const wireScaleSlider = (
393+ el : HTMLElement ,
394+ run : ( ) => void ,
395+ description : string ,
396+ sync : ( ) => void
397+ ) : void => {
398+ let before : TransformSnapshot | null = null
399+ el . addEventListener ( 'input' , ( ) => {
400+ before ??= captureTransforms ( )
401+ run ( )
402+ } )
403+ el . addEventListener ( 'change' , ( ) => {
404+ if ( before ) {
405+ history . execute (
406+ new TransformCommand ( description , before , captureTransforms ( ) , sync )
407+ )
408+ before = null
409+ }
410+ } )
411+ }
412+ const setSlider = ( sliderId : string , tdId : string , value : number ) : void => {
413+ const s = document . getElementById ( sliderId ) as HTMLInputElement | null
414+ if ( s ) s . value = String ( value )
415+ const td = document . getElementById ( tdId )
416+ if ( td ) td . textContent = `x${ value } `
417+ }
418+ wireScaleSlider (
419+ document . getElementById ( 'layerScaleSlider' ) ! ,
420+ scaleLayers ,
421+ 'Scale layers' ,
422+ ( ) => {
423+ const sel = getSelectedLayers ( )
424+ setSlider (
425+ 'layerScaleSlider' ,
426+ 'sliderValue4' ,
427+ sel . length ? ctx . layers [ sel [ 0 ] ] . getScale ( ) : 1
428+ )
429+ }
430+ )
431+ wireScaleSlider (
432+ document . getElementById ( 'nodeScaleSlider' ) ! ,
433+ scaleNodes ,
434+ 'Scale nodes' ,
435+ ( ) => {
436+ const sel = getSelectedNodes ( )
437+ setSlider (
438+ 'nodeScaleSlider' ,
439+ 'sliderValue6' ,
440+ sel . length ? ctx . nodeObjects [ sel [ 0 ] ] . getScale ( ) : 1
441+ )
442+ }
443+ )
357444 for ( const [ sliderId , tdId , fmt ] of [
358445 [ 'sceneRotateSlider' , 'sliderValue1' , 'Angle: %v˚' ] ,
359446 [ 'layerRotateSlider' , 'sliderValue2' , 'Angle: %v˚' ] ,
0 commit comments