185 lines
6.4 KiB
C#
185 lines
6.4 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using System.Collections.ObjectModel;
|
|
using ClaudeDo.Ui.Services;
|
|
using ClaudeDo.Ui.ViewModels.Modals;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace ClaudeDo.Ui.ViewModels.Islands;
|
|
|
|
public sealed partial class MergeSectionViewModel : ViewModelBase
|
|
{
|
|
private readonly IWorkerClient _worker;
|
|
private readonly IServiceProvider _services;
|
|
|
|
// Context mirrored from parent, updated via Sync* methods
|
|
internal string? TaskId { get; private set; }
|
|
internal string? TaskTitle { get; private set; }
|
|
private string? _worktreePath;
|
|
private string? _worktreeBaseCommit;
|
|
private string? _worktreeHeadCommit;
|
|
private string? _worktreeStateLabel;
|
|
private string? _listWorkingDir;
|
|
private bool _isPlanningParent;
|
|
private int _subtaskCount;
|
|
private bool _hasChildOutcomes;
|
|
|
|
[ObservableProperty] private ObservableCollection<string> _mergeTargetBranches = new();
|
|
[ObservableProperty] private string? _selectedMergeTarget;
|
|
|
|
[ObservableProperty]
|
|
[NotifyPropertyChangedFor(nameof(ShowMergePreviewMuted))]
|
|
private string _mergePreviewText = "";
|
|
|
|
[ObservableProperty]
|
|
[NotifyPropertyChangedFor(nameof(ShowMergePreviewMuted))]
|
|
private bool _mergeIsClean;
|
|
|
|
[ObservableProperty]
|
|
[NotifyPropertyChangedFor(nameof(ShowMergePreviewMuted))]
|
|
private bool _mergeIsConflict;
|
|
|
|
public bool ShowMergePreviewMuted =>
|
|
!MergeIsClean && !MergeIsConflict && !string.IsNullOrEmpty(MergePreviewText);
|
|
|
|
public bool ShowMergeSection =>
|
|
_worktreePath != null || _isPlanningParent || _hasChildOutcomes;
|
|
|
|
public Func<DiffViewerViewModel, System.Threading.Tasks.Task>? ShowDiffViewer { get; set; }
|
|
public Func<MergeModalViewModel, System.Threading.Tasks.Task>? ShowMergeModal { get; set; }
|
|
|
|
public MergeSectionViewModel(IWorkerClient worker, IServiceProvider services)
|
|
{
|
|
_worker = worker;
|
|
_services = services;
|
|
}
|
|
|
|
partial void OnSelectedMergeTargetChanged(string? value) => _ = RefreshMergePreviewAsync();
|
|
|
|
internal void SyncWorktree(
|
|
string? worktreePath,
|
|
string? worktreeBase,
|
|
string? worktreeHead,
|
|
string? worktreeState,
|
|
string? listWorkDir)
|
|
{
|
|
_worktreePath = worktreePath;
|
|
_worktreeBaseCommit = worktreeBase;
|
|
_worktreeHeadCommit = worktreeHead;
|
|
_worktreeStateLabel = worktreeState;
|
|
_listWorkingDir = listWorkDir;
|
|
OnPropertyChanged(nameof(ShowMergeSection));
|
|
OpenDiffCommand.NotifyCanExecuteChanged();
|
|
OpenWorktreeCommand.NotifyCanExecuteChanged();
|
|
}
|
|
|
|
internal void SyncTaskContext(string? taskId, string? taskTitle, bool isPlanningParent)
|
|
{
|
|
TaskId = taskId;
|
|
TaskTitle = taskTitle;
|
|
_isPlanningParent = isPlanningParent;
|
|
OnPropertyChanged(nameof(ShowMergeSection));
|
|
}
|
|
|
|
internal void SyncChildOutcomes(bool hasChildOutcomes, int subtaskCount)
|
|
{
|
|
_hasChildOutcomes = hasChildOutcomes;
|
|
_subtaskCount = subtaskCount;
|
|
OnPropertyChanged(nameof(ShowMergeSection));
|
|
ReviewCombinedDiffCommand.NotifyCanExecuteChanged();
|
|
}
|
|
|
|
internal async System.Threading.Tasks.Task RefreshMergePreviewAsync()
|
|
{
|
|
if (TaskId is null || _worktreePath is null)
|
|
{
|
|
MergePreviewText = ""; MergeIsClean = false; MergeIsConflict = false;
|
|
return;
|
|
}
|
|
if (_worktreeStateLabel is { } label && label != "Active")
|
|
{
|
|
MergePreviewText = label; MergeIsClean = false; MergeIsConflict = false;
|
|
return;
|
|
}
|
|
var capturedTaskId = TaskId;
|
|
var capturedTarget = SelectedMergeTarget;
|
|
var dto = await _worker.PreviewMergeAsync(capturedTaskId, capturedTarget ?? "");
|
|
if (TaskId != capturedTaskId || SelectedMergeTarget != capturedTarget) return;
|
|
var (text, clean, conflict) = MergePreviewPresenter.Describe(dto);
|
|
MergePreviewText = text; MergeIsClean = clean; MergeIsConflict = conflict;
|
|
}
|
|
|
|
internal void Clear()
|
|
{
|
|
MergeTargetBranches.Clear();
|
|
SelectedMergeTarget = null;
|
|
MergePreviewText = "";
|
|
MergeIsClean = false;
|
|
MergeIsConflict = false;
|
|
SyncWorktree(null, null, null, null, null);
|
|
SyncTaskContext(null, null, false);
|
|
SyncChildOutcomes(false, 0);
|
|
}
|
|
|
|
[RelayCommand(CanExecute = nameof(CanReviewDiff))]
|
|
private async System.Threading.Tasks.Task ReviewCombinedDiffAsync()
|
|
{
|
|
if (TaskId is null || ShowDiffViewer is null) return;
|
|
var vm = _services.GetRequiredService<DiffViewerViewModel>();
|
|
vm.ConfigurePlanning(TaskId, SelectedMergeTarget ?? "main");
|
|
await vm.LoadAsync();
|
|
await ShowDiffViewer(vm);
|
|
}
|
|
|
|
private bool CanReviewDiff() => (_isPlanningParent && _subtaskCount > 0) || _hasChildOutcomes;
|
|
|
|
[RelayCommand(CanExecute = nameof(CanOpenDiff))]
|
|
private async System.Threading.Tasks.Task OpenDiffAsync()
|
|
{
|
|
if (ShowDiffViewer is null) return;
|
|
|
|
var hasLiveWorktree =
|
|
_worktreePath != null
|
|
&& _worktreeStateLabel == "Active"
|
|
&& System.IO.Directory.Exists(_worktreePath);
|
|
|
|
var vm = _services.GetRequiredService<DiffViewerViewModel>();
|
|
if (hasLiveWorktree)
|
|
{
|
|
vm.ConfigureWorktree(_worktreePath!, _worktreeBaseCommit, TaskId, TaskTitle ?? "");
|
|
vm.ShowMergeModal = ShowMergeModal;
|
|
vm.ResolveMergeVm = () => _services.GetRequiredService<MergeModalViewModel>();
|
|
}
|
|
else if (CanDiffMergedRange)
|
|
{
|
|
vm.ConfigureCommitRange(_listWorkingDir!, _worktreeBaseCommit, _worktreeHeadCommit, TaskId, TaskTitle ?? "");
|
|
}
|
|
else return;
|
|
|
|
await vm.LoadAsync();
|
|
await ShowDiffViewer(vm);
|
|
}
|
|
|
|
private bool CanDiffMergedRange =>
|
|
_worktreeBaseCommit != null && _worktreeHeadCommit != null && _listWorkingDir != null;
|
|
|
|
private bool CanOpenDiff() => _worktreePath != null || CanDiffMergedRange;
|
|
|
|
[RelayCommand(CanExecute = nameof(CanOpenWorktree))]
|
|
private void OpenWorktree()
|
|
{
|
|
if (_worktreePath is null) return;
|
|
try
|
|
{
|
|
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
FileName = _worktreePath,
|
|
UseShellExecute = true,
|
|
});
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private bool CanOpenWorktree() => _worktreePath != null;
|
|
}
|