Files
ClaudeDo/src/ClaudeDo.Ui/ViewModels/Islands/MergeSectionViewModel.cs

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;
}