@@ -3238,6 +3238,7 @@ export class ToolStarterApp {
32383238 hitLayer . remove ( ) ;
32393239 }
32403240 this . renderObjectBounds ( object ) ;
3241+ this . renderObjectOriginMarker ( object ) ;
32413242 this . renderSnapPointTargets ( object ) ;
32423243 this . renderDrawingPreview ( ) ;
32433244 this . renderDrawingHint ( ) ;
@@ -3416,6 +3417,34 @@ export class ToolStarterApp {
34163417 this . elements . renderSurface . append ( box ) ;
34173418 }
34183419
3420+ renderObjectOriginMarker ( object ) {
3421+ if ( ! object ) {
3422+ return ;
3423+ }
3424+ const origin = this . objectTransformOrigin ( object ) ;
3425+ const x = this . scaleDrawingValue ( origin . x , OBJECT_PREVIEW_DRAWING_SCALE ) ;
3426+ const y = this . scaleDrawingValue ( origin . y , OBJECT_PREVIEW_DRAWING_SCALE ) ;
3427+ const marker = document . createElementNS ( SVG_NS , "g" ) ;
3428+ marker . classList . add ( "object-vector-studio-v2__object-origin" ) ;
3429+ marker . dataset . objectOrigin = object . id ;
3430+ marker . setAttribute ( "role" , "img" ) ;
3431+ marker . setAttribute ( "aria-label" , "Object origin/pivot marker for object rotation and scale" ) ;
3432+ const markerTitle = document . createElementNS ( SVG_NS , "title" ) ;
3433+ markerTitle . textContent = "Object Origin/Pivot: rotate and scale pivot for the selected object." ;
3434+ const horizontal = document . createElementNS ( SVG_NS , "line" ) ;
3435+ horizontal . setAttribute ( "x1" , x - 8 ) ;
3436+ horizontal . setAttribute ( "x2" , x + 8 ) ;
3437+ horizontal . setAttribute ( "y1" , y ) ;
3438+ horizontal . setAttribute ( "y2" , y ) ;
3439+ const vertical = document . createElementNS ( SVG_NS , "line" ) ;
3440+ vertical . setAttribute ( "x1" , x ) ;
3441+ vertical . setAttribute ( "x2" , x ) ;
3442+ vertical . setAttribute ( "y1" , y - 8 ) ;
3443+ vertical . setAttribute ( "y2" , y + 8 ) ;
3444+ marker . append ( markerTitle , horizontal , vertical ) ;
3445+ this . elements . renderSurface . append ( marker ) ;
3446+ }
3447+
34193448 renderCenterOriginMarker ( ) {
34203449 if ( ! this . centerOriginVisible ) {
34213450 return ;
@@ -4276,17 +4305,17 @@ export class ToolStarterApp {
42764305 pivot . setAttribute ( "aria-label" , "Origin/Pivot marker for selected shape rotation and scale" ) ;
42774306 const pivotTitle = document . createElementNS ( SVG_NS , "title" ) ;
42784307 pivotTitle . textContent = "Origin/Pivot: rotate and scale pivot for the selected shape." ;
4279- const horizontal = document . createElementNS ( SVG_NS , "line" ) ;
4280- horizontal . setAttribute ( "x1" , bounds . originX - 4 ) ;
4281- horizontal . setAttribute ( "x2" , bounds . originX + 4 ) ;
4282- horizontal . setAttribute ( "y1" , bounds . originY ) ;
4283- horizontal . setAttribute ( "y2" , bounds . originY ) ;
4284- const vertical = document . createElementNS ( SVG_NS , "line" ) ;
4285- vertical . setAttribute ( "x1" , bounds . originX ) ;
4286- vertical . setAttribute ( "x2" , bounds . originX ) ;
4287- vertical . setAttribute ( "y1" , bounds . originY - 4 ) ;
4288- vertical . setAttribute ( "y2" , bounds . originY + 4 ) ;
4289- pivot . append ( pivotTitle , horizontal , vertical ) ;
4308+ const diagonalForward = document . createElementNS ( SVG_NS , "line" ) ;
4309+ diagonalForward . setAttribute ( "x1" , bounds . originX - 8 ) ;
4310+ diagonalForward . setAttribute ( "x2" , bounds . originX + 8 ) ;
4311+ diagonalForward . setAttribute ( "y1" , bounds . originY - 8 ) ;
4312+ diagonalForward . setAttribute ( "y2" , bounds . originY + 8 ) ;
4313+ const diagonalBack = document . createElementNS ( SVG_NS , "line" ) ;
4314+ diagonalBack . setAttribute ( "x1" , bounds . originX - 8 ) ;
4315+ diagonalBack . setAttribute ( "x2" , bounds . originX + 8 ) ;
4316+ diagonalBack . setAttribute ( "y1" , bounds . originY + 8 ) ;
4317+ diagonalBack . setAttribute ( "y2" , bounds . originY - 8 ) ;
4318+ pivot . append ( pivotTitle , diagonalForward , diagonalBack ) ;
42904319 this . elements . renderSurface . append ( pivot ) ;
42914320 } catch ( error ) {
42924321 this . statusLog . write ( `FAIL Selection overlay render failed for ${ object . name } /shape-${ this . selectedShapeIndex } (${ shapeTool ( selectedShape ) } ): ${ error . message } ` ) ;
@@ -6631,44 +6660,111 @@ export class ToolStarterApp {
66316660 this . statusLog . write ( "WARN Shape order skipped: no shape is selected." ) ;
66326661 return ;
66336662 }
6634- if ( selected . locked ) {
6635- this . statusLog . write ( `WARN Shape order skipped: shape row ${ this . selectedShapeIndex } is locked.` ) ;
6636- return ;
6637- }
66386663 if ( this . guardSelectedObjectMutation ( "Shape order" ) ) {
66396664 return ;
66406665 }
66416666
66426667 const nextPayload = this . cloneCurrentPayload ( ) ;
66436668 const object = nextPayload . objects . find ( ( candidate ) => candidate . id === this . selectedObjectId ) ;
66446669 const shapes = sortedShapes ( object ) ;
6645- const index = this . selectedShapeIndex ;
6670+ const selectedIndexes = Array . from ( new Set ( Array . from ( this . selectedShapeIndexes || [ ] )
6671+ . map ( ( shapeIndex ) => normalizeShapeIndex ( shapeIndex ) )
6672+ . filter ( ( shapeIndex ) => shapeIndex >= 0 && shapeIndex < shapes . length ) ) )
6673+ . sort ( ( left , right ) => left - right ) ;
6674+ if ( ! selectedIndexes . length && this . selectedShapeIndex >= 0 && this . selectedShapeIndex < shapes . length ) {
6675+ selectedIndexes . push ( this . selectedShapeIndex ) ;
6676+ }
6677+ const lockedIndex = selectedIndexes . find ( ( shapeIndex ) => shapes [ shapeIndex ] ?. locked ) ;
6678+ if ( Number . isInteger ( lockedIndex ) ) {
6679+ this . statusLog . write ( `WARN Shape order skipped: shape row ${ lockedIndex } is locked.` ) ;
6680+ return ;
6681+ }
66466682 if ( shapes . length < 2 ) {
6647- this . statusLog . write ( `WARN Shape order skipped: shape row ${ index } is the only shape in ${ object . name } .` ) ;
6683+ this . statusLog . write ( `WARN Shape order skipped: shape row ${ this . selectedShapeIndex } is the only shape in ${ object . name } .` ) ;
66486684 return ;
66496685 }
66506686
6651- const nextIndexByAction = {
6652- back : 0 ,
6653- backward : index - 1 ,
6654- forward : index + 1 ,
6655- front : shapes . length - 1
6656- } ;
6657- const nextIndex = nextIndexByAction [ action ] ;
6658- if ( ! Number . isInteger ( nextIndex ) || nextIndex < 0 || nextIndex >= shapes . length || nextIndex === index ) {
6659- this . statusLog . write ( `WARN Shape z-order skipped: shape row ${ index } cannot move ${ action } .` ) ;
6687+ const selectedIndexSet = new Set ( selectedIndexes ) ;
6688+ const oldIndexByShape = new Map ( shapes . map ( ( shape , shapeIndex ) => [ shape , shapeIndex ] ) ) ;
6689+ const reorderedShapes = shapes . slice ( ) ;
6690+ if ( action === "back" ) {
6691+ reorderedShapes . sort ( ( left , right ) => {
6692+ const leftIndex = oldIndexByShape . get ( left ) ;
6693+ const rightIndex = oldIndexByShape . get ( right ) ;
6694+ const leftSelected = selectedIndexSet . has ( leftIndex ) ;
6695+ const rightSelected = selectedIndexSet . has ( rightIndex ) ;
6696+ if ( leftSelected === rightSelected ) {
6697+ return leftIndex - rightIndex ;
6698+ }
6699+ return leftSelected ? - 1 : 1 ;
6700+ } ) ;
6701+ } else if ( action === "front" ) {
6702+ reorderedShapes . sort ( ( left , right ) => {
6703+ const leftIndex = oldIndexByShape . get ( left ) ;
6704+ const rightIndex = oldIndexByShape . get ( right ) ;
6705+ const leftSelected = selectedIndexSet . has ( leftIndex ) ;
6706+ const rightSelected = selectedIndexSet . has ( rightIndex ) ;
6707+ if ( leftSelected === rightSelected ) {
6708+ return leftIndex - rightIndex ;
6709+ }
6710+ return leftSelected ? 1 : - 1 ;
6711+ } ) ;
6712+ } else if ( action === "backward" ) {
6713+ for ( let index = 1 ; index < reorderedShapes . length ; index += 1 ) {
6714+ const currentOldIndex = oldIndexByShape . get ( reorderedShapes [ index ] ) ;
6715+ const previousOldIndex = oldIndexByShape . get ( reorderedShapes [ index - 1 ] ) ;
6716+ if ( selectedIndexSet . has ( currentOldIndex ) && ! selectedIndexSet . has ( previousOldIndex ) ) {
6717+ [ reorderedShapes [ index - 1 ] , reorderedShapes [ index ] ] = [ reorderedShapes [ index ] , reorderedShapes [ index - 1 ] ] ;
6718+ }
6719+ }
6720+ } else if ( action === "forward" ) {
6721+ for ( let index = reorderedShapes . length - 2 ; index >= 0 ; index -= 1 ) {
6722+ const currentOldIndex = oldIndexByShape . get ( reorderedShapes [ index ] ) ;
6723+ const nextOldIndex = oldIndexByShape . get ( reorderedShapes [ index + 1 ] ) ;
6724+ if ( selectedIndexSet . has ( currentOldIndex ) && ! selectedIndexSet . has ( nextOldIndex ) ) {
6725+ [ reorderedShapes [ index ] , reorderedShapes [ index + 1 ] ] = [ reorderedShapes [ index + 1 ] , reorderedShapes [ index ] ] ;
6726+ }
6727+ }
6728+ } else {
6729+ this . statusLog . write ( `WARN Shape z-order skipped: unsupported action ${ action } .` ) ;
66606730 return ;
66616731 }
66626732
66636733 const oldShapes = shapes . slice ( ) ;
6664- const [ movedShape ] = shapes . splice ( index , 1 ) ;
6665- shapes . splice ( nextIndex , 0 , movedShape ) ;
6666- shapes . forEach ( ( shape , shapeIndex ) => {
6734+ if ( oldShapes . every ( ( shape , shapeIndex ) => reorderedShapes [ shapeIndex ] === shape ) ) {
6735+ const subject = selectedIndexes . length > 1 ? `selected shape rows ${ selectedIndexes . join ( ", " ) } ` : `shape row ${ this . selectedShapeIndex } ` ;
6736+ this . statusLog . write ( `WARN Shape z-order skipped: ${ subject } cannot move ${ action } .` ) ;
6737+ return ;
6738+ }
6739+ const oldIndexToNextIndex = new Map ( ) ;
6740+ oldShapes . forEach ( ( shape , oldIndex ) => {
6741+ oldIndexToNextIndex . set ( oldIndex , reorderedShapes . indexOf ( shape ) ) ;
6742+ } ) ;
6743+ reorderedShapes . forEach ( ( shape , shapeIndex ) => {
66676744 shape . order = shapeIndex + 1 ;
66686745 } ) ;
6669- object . shapes = shapes ;
6670- this . remapShapeOverrideIndexes ( object , oldShapes , shapes ) ;
6671- this . commitPayloadUpdate ( nextPayload , this . selectedObjectId , nextIndex , `OK Shape row ${ index } z-order ${ action } .` , "Shape z-order failed schema validation" ) ;
6746+ object . shapes = reorderedShapes ;
6747+ this . remapShapeOverrideIndexes ( object , oldShapes , reorderedShapes ) ;
6748+ const selectedShapeIndexes = new Set ( selectedIndexes
6749+ . map ( ( shapeIndex ) => oldIndexToNextIndex . get ( shapeIndex ) )
6750+ . filter ( ( shapeIndex ) => Number . isInteger ( shapeIndex ) && shapeIndex >= 0 ) ) ;
6751+ const directSelectedShapeIndexes = new Set ( Array . from ( this . directSelectedShapeIndexes || [ ] )
6752+ . map ( ( shapeIndex ) => oldIndexToNextIndex . get ( normalizeShapeIndex ( shapeIndex ) ) )
6753+ . filter ( ( shapeIndex ) => Number . isInteger ( shapeIndex ) && shapeIndex >= 0 ) ) ;
6754+ if ( ! directSelectedShapeIndexes . size ) {
6755+ selectedShapeIndexes . forEach ( ( shapeIndex ) => directSelectedShapeIndexes . add ( shapeIndex ) ) ;
6756+ }
6757+ const nextSelectedShapeIndex = oldIndexToNextIndex . get ( this . selectedShapeIndex ) ;
6758+ const primaryShapeIndex = Number . isInteger ( nextSelectedShapeIndex ) && nextSelectedShapeIndex >= 0
6759+ ? nextSelectedShapeIndex
6760+ : Math . min ( ...selectedShapeIndexes ) ;
6761+ const okMessage = selectedIndexes . length > 1
6762+ ? `OK Shape rows ${ selectedIndexes . join ( ", " ) } z-order ${ action } .`
6763+ : `OK Shape row ${ this . selectedShapeIndex } z-order ${ action } .` ;
6764+ this . commitPayloadUpdate ( nextPayload , this . selectedObjectId , primaryShapeIndex , okMessage , "Shape z-order failed schema validation" , {
6765+ directSelectedShapeIndexes,
6766+ selectedShapeIndexes
6767+ } ) ;
66726768 }
66736769
66746770 objectTransformOrigin ( object ) {
0 commit comments