chore(claude-do): refactor(ui): DetailsIslandViewModel (1431 Zeilen) in Sektio
Kontext: src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs ist mit 1431 Zeilen ein God-VM mit ~12 Concerns (Log-Streaming, Titel/Description-Editing, Subtasks, Child-Outcomes, Merge-Preview/-Targets, Diff, Agent-Settings-Overrides, Notes-Mode, Prep-Mode, Tabs, Session-Outcome/Roadblocks, Worktree-Info). Jedes neue Feature landet dort. Änderungen — drei klar abgrenzbare Sektionen als ei ClaudeDo-Task: 483e419f-1ec8-46ba-986b-8b90d6596b49
This commit is contained in:
200
src/ClaudeDo.Ui/ViewModels/Islands/MergeSectionViewModel.cs
Normal file
200
src/ClaudeDo.Ui/ViewModels/Islands/MergeSectionViewModel.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
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<DiffModalViewModel, System.Threading.Tasks.Task>? ShowDiffModal { get; set; }
|
||||
public Func<MergeModalViewModel, System.Threading.Tasks.Task>? ShowMergeModal { get; set; }
|
||||
public Func<ViewModels.Planning.PlanningDiffViewModel, System.Threading.Tasks.Task>? ShowPlanningDiffModal { get; set; }
|
||||
public Func<string, string, System.Threading.Tasks.Task>? RequestConflictResolution { 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 || ShowPlanningDiffModal is null) return;
|
||||
var vm = new ViewModels.Planning.PlanningDiffViewModel(_worker, TaskId, SelectedMergeTarget ?? "main");
|
||||
await vm.InitializeAsync();
|
||||
await ShowPlanningDiffModal(vm);
|
||||
}
|
||||
|
||||
private bool CanReviewDiff() => (_isPlanningParent && _subtaskCount > 0) || _hasChildOutcomes;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanOpenDiff))]
|
||||
private async System.Threading.Tasks.Task OpenDiffAsync()
|
||||
{
|
||||
if (ShowDiffModal is null) return;
|
||||
var git = _services.GetRequiredService<ClaudeDo.Data.Git.GitService>();
|
||||
|
||||
var hasLiveWorktree =
|
||||
_worktreePath != null
|
||||
&& _worktreeStateLabel == "Active"
|
||||
&& System.IO.Directory.Exists(_worktreePath);
|
||||
|
||||
DiffModalViewModel diffVm;
|
||||
if (hasLiveWorktree)
|
||||
{
|
||||
diffVm = new DiffModalViewModel(git)
|
||||
{
|
||||
WorktreePath = _worktreePath!,
|
||||
BaseRef = _worktreeBaseCommit,
|
||||
TaskId = TaskId,
|
||||
TaskTitle = TaskTitle ?? "",
|
||||
ShowMergeModal = ShowMergeModal,
|
||||
ResolveMergeVm = () => _services.GetRequiredService<MergeModalViewModel>(),
|
||||
};
|
||||
}
|
||||
else if (CanDiffMergedRange)
|
||||
{
|
||||
diffVm = new DiffModalViewModel(git)
|
||||
{
|
||||
WorktreePath = _listWorkingDir!,
|
||||
BaseRef = _worktreeBaseCommit,
|
||||
HeadCommit = _worktreeHeadCommit,
|
||||
FromCommitRange = true,
|
||||
TaskId = TaskId,
|
||||
TaskTitle = TaskTitle ?? "",
|
||||
};
|
||||
}
|
||||
else return;
|
||||
|
||||
await diffVm.LoadAsync();
|
||||
await ShowDiffModal(diffVm);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user