fix(dock): centralize tear-off transaction in TabTearOffService and fix build breaks
This commit is contained in:
@@ -53,50 +53,6 @@ public partial class App : Application
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to tear off a tab into a new window.
|
||||
/// </summary>
|
||||
/// <param name="sourceItems">The collection to remove the item from.</param>
|
||||
/// <param name="tabItem">The item to tear off.</param>
|
||||
/// <param name="onSuccess">Optional callback after successful tear-off.</param>
|
||||
/// <returns>A result indicating success or failure.</returns>
|
||||
internal static Result TryTearOffTab(System.Collections.IList sourceItems, object tabItem, Action? onSuccess = null)
|
||||
{
|
||||
int originalIndex = sourceItems.IndexOf(tabItem);
|
||||
if (originalIndex == -1)
|
||||
{
|
||||
return Result.Failure("Item not found in source collection.");
|
||||
}
|
||||
|
||||
object? originalSelection = null;
|
||||
// Try to capture selection if the source is a DockPanelNode or similar
|
||||
// For now, we'll just handle the collection mutation.
|
||||
|
||||
try
|
||||
{
|
||||
sourceItems.Remove(tabItem);
|
||||
|
||||
try
|
||||
{
|
||||
CreateAndShowDockWindow(tabItem);
|
||||
onSuccess?.Invoke();
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Rollback
|
||||
sourceItems.Insert(originalIndex, tabItem);
|
||||
Logger.LogError(ex);
|
||||
return Result.Failure($"Failed to create tear-off window: {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
return Result.Failure($"Failed to remove item from source: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates, registers, and shows a new DockWindow for a torn-off tab.
|
||||
/// </summary>
|
||||
|
||||
@@ -302,32 +302,16 @@ public sealed partial class DockLayout : Control
|
||||
{
|
||||
if (_sourceNode != null && _draggedItem != null)
|
||||
{
|
||||
if (TabTornOff == null)
|
||||
var result = TabTearOffService.TryTearOffTab(_sourceNode.Items, _draggedItem, _sourceNode);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Logger.LogWarning("Tab dropped outside but no TabTornOff subscribers found.");
|
||||
ClearDragOperationState();
|
||||
return;
|
||||
DockMutationEngine.CleanupEmptyNodes(_sourceNode);
|
||||
}
|
||||
|
||||
object? originalSelection = _sourceNode.SelectedItem;
|
||||
|
||||
App.TryTearOffTab(_sourceNode.Items, _draggedItem, () =>
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Raise event to let the host handle window creation
|
||||
TabTornOff.Invoke(this, new TabTornOffEventArgs(_draggedItem));
|
||||
|
||||
// Only cleanup if the tear-off was successful
|
||||
DockMutationEngine.CleanupEmptyNodes(_sourceNode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If the event handler fails, we still need to cleanup or restore selection
|
||||
_sourceNode.SelectedItem = originalSelection;
|
||||
throw; // Re-throw to trigger rollback in TryTearOffTab
|
||||
}
|
||||
});
|
||||
Logger.LogWarning($"Tab tear-off failed: {result.Error}");
|
||||
}
|
||||
|
||||
ClearDragOperationState();
|
||||
}
|
||||
|
||||
@@ -43,18 +43,11 @@ internal sealed partial class EngineEditorWindow : WindowEx
|
||||
// For static tabs in EngineEditorWindow, we remove the item from TabItems
|
||||
if (sender.TabItems.Contains(args.Item))
|
||||
{
|
||||
object? originalSelection = sender.SelectedItem;
|
||||
var result = TabTearOffService.TryTearOffTab(sender.TabItems, args.Item, sender);
|
||||
|
||||
App.TryTearOffTab(sender.TabItems, args.Item, () =>
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
// If we need to do anything else on success
|
||||
});
|
||||
|
||||
// Note: If TryTearOffTab fails, it will have already re-inserted the item.
|
||||
// We might want to restore selection if it was the selected item.
|
||||
if (sender.TabItems.Contains(args.Item) && sender.SelectedItem == null)
|
||||
{
|
||||
sender.SelectedItem = originalSelection;
|
||||
Logger.LogWarning($"Tab tear-off failed: {result.Error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
src/Editor/Ghost.Editor/View/Windows/TabTearOffService.cs
Normal file
69
src/Editor/Ghost.Editor/View/Windows/TabTearOffService.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Editor.Core.Controls.Internal.Docking;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Collections;
|
||||
|
||||
namespace Ghost.Editor.View.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Service for handling tab tear-off operations across the editor.
|
||||
/// </summary>
|
||||
internal static class TabTearOffService
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to tear off a tab into a new window.
|
||||
/// </summary>
|
||||
/// <param name="sourceItems">The collection to remove the item from.</param>
|
||||
/// <param name="tabItem">The item to tear off.</param>
|
||||
/// <param name="selectionContainer">Optional container to restore selection to on failure.</param>
|
||||
/// <returns>A result indicating success or failure.</returns>
|
||||
public static Result TryTearOffTab(IList sourceItems, object tabItem, object? selectionContainer = null)
|
||||
{
|
||||
int originalIndex = sourceItems.IndexOf(tabItem);
|
||||
if (originalIndex == -1)
|
||||
{
|
||||
return Result.Failure("Item not found in source collection.");
|
||||
}
|
||||
|
||||
object? originalSelection = GetSelection(selectionContainer);
|
||||
|
||||
try
|
||||
{
|
||||
sourceItems.Remove(tabItem);
|
||||
|
||||
try
|
||||
{
|
||||
App.CreateAndShowDockWindow(tabItem);
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Rollback collection
|
||||
sourceItems.Insert(originalIndex, tabItem);
|
||||
RestoreSelection(selectionContainer, originalSelection);
|
||||
|
||||
Logger.LogError(ex);
|
||||
return Result.Failure($"Failed to create tear-off window: {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
return Result.Failure($"Failed to remove item from source: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static object? GetSelection(object? container)
|
||||
{
|
||||
if (container is DockPanelNode panel) return panel.SelectedItem;
|
||||
if (container is Microsoft.UI.Xaml.Controls.TabView tabView) return tabView.SelectedItem;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void RestoreSelection(object? container, object? selection)
|
||||
{
|
||||
if (selection == null) return;
|
||||
if (container is DockPanelNode panel) panel.SelectedItem = selection;
|
||||
else if (container is Microsoft.UI.Xaml.Controls.TabView tabView) tabView.SelectedItem = selection;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user