Editor/Commands/SuiPasteElementCommand.cs
using System.Collections.Generic;
using SboxUiDesigner.Runtime;
namespace SboxUiDesigner.EditorUi.Commands;
/// <summary>
/// Paste a <see cref="SuiClipboardPayload"/> into the document under
/// <c>parentId</c>. The clipboard subtree gets fresh IDs throughout, name
/// uniquified to avoid collisions, and is appended at the end of the parent's
/// children. The id of the pasted root is exposed via
/// <see cref="ResultingElementId"/> so the controller can select it.
/// </summary>
public sealed class SuiPasteElementCommand : ISuiCommand
{
private readonly SuiClipboardPayload _payload;
private readonly string _parentId;
private List<SuiElement> _addedElements;
private string _newRootId;
private int _insertIndex = -1;
public string Description => "Paste";
public string ResultingElementId => _newRootId;
public SuiPasteElementCommand( SuiClipboardPayload payload, string parentId )
{
_payload = payload;
_parentId = parentId;
}
public void Apply( SuiDocument doc )
{
if ( doc == null || _payload?.Root == null ) return;
var parent = doc.GetElement( _parentId ) ?? doc.GetRoot();
if ( parent == null ) return;
// Remap old subtree ids → new ids so the paste can be re-applied
// after an undo without colliding.
var idMap = new Dictionary<string, string>();
foreach ( var el in _payload.All )
idMap[el.Id] = SuiDocument.NewElementId();
_addedElements = new List<SuiElement>();
foreach ( var el in _payload.All )
{
var clone = el.Clone();
clone.Id = idMap[el.Id];
clone.ParentId = idMap.TryGetValue( el.ParentId ?? "", out var newParent ) ? newParent : null;
clone.Children = new List<string>();
foreach ( var oldChild in el.Children ?? new List<string>() )
if ( idMap.TryGetValue( oldChild, out var newChild ) )
clone.Children.Add( newChild );
_addedElements.Add( clone );
}
// Pasted root: pin to the chosen parent + uniquify name.
var pastedRoot = FindById( _addedElements, idMap[_payload.Root.Id] );
if ( pastedRoot == null ) return;
pastedRoot.ParentId = parent.Id;
pastedRoot.Name = SuggestUniqueName( doc, pastedRoot.Name );
if ( pastedRoot.Style != null )
pastedRoot.Style.ClassName = SuiDocumentValidator.SanitizeClassName( pastedRoot.Name );
_insertIndex = parent.Children.Count;
parent.Children.Add( pastedRoot.Id );
_newRootId = pastedRoot.Id;
foreach ( var el in _addedElements ) doc.Elements.Add( el );
}
public void Undo( SuiDocument doc )
{
if ( doc == null || _addedElements == null ) return;
var parent = doc.GetElement( _parentId ) ?? doc.GetRoot();
if ( parent != null && _insertIndex >= 0 && _insertIndex < parent.Children.Count
&& parent.Children[_insertIndex] == _newRootId )
{
parent.Children.RemoveAt( _insertIndex );
}
else if ( parent != null )
{
// Defensive: if the index drifted (shouldn't, but handle).
parent.Children.Remove( _newRootId );
}
foreach ( var el in _addedElements )
doc.Elements.Remove( el );
_addedElements = null;
}
private static SuiElement FindById( List<SuiElement> list, string id )
{
foreach ( var el in list ) if ( el.Id == id ) return el;
return null;
}
private static string SuggestUniqueName( SuiDocument doc, string baseName )
{
if ( string.IsNullOrEmpty( baseName ) ) baseName = "Element";
// Strip an existing "_<int>" so paste of "Panel_2" picks "Panel_3" not "Panel_2_2".
var bare = baseName;
var underscore = baseName.LastIndexOf( '_' );
if ( underscore > 0 && underscore < baseName.Length - 1
&& int.TryParse( baseName.Substring( underscore + 1 ), out _ ) )
{
bare = baseName.Substring( 0, underscore );
}
if ( !NameExists( doc, baseName ) ) return baseName;
for ( int i = 2; i < 1000; i++ )
{
var candidate = $"{bare}_{i}";
if ( !NameExists( doc, candidate ) ) return candidate;
}
return $"{bare}_{System.Guid.NewGuid().ToString( "N" ).Substring( 0, 4 )}";
}
private static bool NameExists( SuiDocument doc, string name )
{
foreach ( var el in doc.Elements )
if ( string.Equals( el.Name, name, System.StringComparison.OrdinalIgnoreCase ) ) return true;
return false;
}
}