Editor/ImportConflictWindow.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Editor;
using Sandbox;
namespace ImportUnityPackage;
/// <summary>Review destination conflicts without changing package selection.</summary>
public sealed class ImportConflictWindow : Widget
{
readonly TaskCompletionSource<bool> completion = new();
readonly CancellationToken token;
public static async Task Review( string title, string destination, IEnumerable<ImportFileChoice> entries, CancellationToken cancel )
{
var window = new ImportConflictWindow( title, destination, entries.ToArray(), cancel );
window.Show();
if ( !await window.completion.Task ) throw new OperationCanceledException( "Import review cancelled", cancel );
}
ImportConflictWindow( string title, string destination, ImportFileChoice[] entries, CancellationToken cancel ) : base( null, true )
{
token = cancel;
WindowTitle = title;
Size = new Vector2( 850, 600 );
MinimumSize = new Vector2( 600, 350 );
Layout = Layout.Column();
Layout.Margin = 16;
Layout.Spacing = 8;
Layout.Add( new Label( $"Destination: {destination}\n{entries.Count( c => c.ExistingHash == null )} new · {entries.Count( c => c.ExistingHash != null && !c.Conflict )} identical · {entries.Count( c => c.Conflict )} conflicts" ) { WordWrap = true } );
Layout.Add( new Label( "Conflicts keep the existing file unless Replace is checked. Replacing shared files can change models and scenes that already use them." ) { WordWrap = true } );
var controls = Layout.AddRow();
var keep = controls.Add( new Button( "Keep all existing" ) );
var replace = controls.Add( new Button( "Replace all conflicts" ) );
controls.AddStretchCell();
var scroll = Layout.Add( new ScrollArea( this ), 1 );
scroll.Canvas = new Widget( this );
scroll.Canvas.SetSizeMode( SizeMode.Default, SizeMode.CanGrow );
scroll.Canvas.Layout = Layout.Column();
scroll.Canvas.Layout.Spacing = 8;
var checkboxes = new List<Checkbox>();
foreach ( var entry in entries.OrderByDescending( c => c.Conflict ).ThenBy( c => c.Path ) )
{
if ( entry.Conflict )
{
var box = scroll.Canvas.Layout.Add( new Checkbox( $"Replace · {entry.Status} · {entry.Path}" ) { Value = entry.Replace } );
box.StateChanged += _ => entry.Replace = box.Value;
checkboxes.Add( box );
var detail = entry.Detail;
var asset = AssetSystem.FindByPath( Path.Combine( destination, entry.Path ) );
if ( asset != null )
{
var users = asset.GetDependants( true ).Select( a => a.Path ).Distinct().ToArray();
if ( users.Length > 0 ) detail += " Project references: " + string.Join( ", ", users.Take( 6 ) ) + (users.Length > 6 ? $" (+{users.Length - 6} more)" : "");
}
if ( !string.IsNullOrWhiteSpace( detail ) ) scroll.Canvas.Layout.Add( new Label( detail ) { WordWrap = true } );
}
else scroll.Canvas.Layout.Add( new Label( $"{entry.Action} · {entry.Path}" ) );
}
scroll.Canvas.Layout.AddStretchCell();
keep.Clicked = () => { foreach ( var entry in entries.Where( e => e.Conflict ) ) entry.Replace = false; checkboxes.ForEach( b => b.Value = false ); };
replace.Clicked = () => { foreach ( var entry in entries.Where( e => e.Conflict ) ) entry.Replace = true; checkboxes.ForEach( b => b.Value = true ); };
var buttons = Layout.AddRow();
buttons.AddStretchCell();
buttons.Add( new Button( "Cancel import" ) ).Clicked = () => Close();
buttons.Add( new Button.Primary( "Continue" ) ).Clicked = () => { completion.TrySetResult( true ); Close(); };
}
[EditorEvent.Frame]
void Tick() { if ( IsValid && token.IsCancellationRequested ) Close(); }
protected override bool OnClose() { completion.TrySetResult( false ); return true; }
}