diff --git a/src/ClaudeDo.App/Program.cs b/src/ClaudeDo.App/Program.cs index ab74563..c1015e0 100644 --- a/src/ClaudeDo.App/Program.cs +++ b/src/ClaudeDo.App/Program.cs @@ -6,6 +6,7 @@ using ClaudeDo.Ui; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels; using ClaudeDo.Ui.ViewModels.Islands; +using ClaudeDo.Ui.ViewModels.Modals; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; @@ -78,6 +79,7 @@ sealed class Program // ViewModels sc.AddTransient(); sc.AddTransient(); + sc.AddTransient(); sc.AddSingleton(); sc.AddSingleton(); sc.AddSingleton(sp => diff --git a/src/ClaudeDo.Ui/Design/IslandStyles.axaml b/src/ClaudeDo.Ui/Design/IslandStyles.axaml index e5aa313..46a9be2 100644 --- a/src/ClaudeDo.Ui/Design/IslandStyles.axaml +++ b/src/ClaudeDo.Ui/Design/IslandStyles.axaml @@ -260,6 +260,35 @@ + + + + + + + + + + + + + diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs new file mode 100644 index 0000000..103fdab --- /dev/null +++ b/src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs @@ -0,0 +1,86 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using ClaudeDo.Data.Git; + +namespace ClaudeDo.Ui.ViewModels.Modals; + +public sealed partial class WorktreeNodeViewModel : ViewModelBase +{ + public required string Name { get; init; } + public string? Status { get; init; } + public bool IsDirectory { get; init; } + public ObservableCollection Children { get; } = new(); +} + +public sealed partial class WorktreeModalViewModel : ViewModelBase +{ + private readonly GitService _git; + + public ObservableCollection Root { get; } = new(); + + [ObservableProperty] private string _worktreePath = ""; + + // Set by the view (same pattern as DiffModalViewModel.CloseAction) + public Action? CloseAction { get; set; } + + public WorktreeModalViewModel(GitService git) + { + _git = git; + } + + [RelayCommand] + private void Close() => CloseAction?.Invoke(); + + public async Task LoadAsync(CancellationToken ct = default) + { + Root.Clear(); + + string stdout; + try { stdout = await _git.GetStatusPorcelainAsync(WorktreePath, ct); } + catch { return; } + + if (string.IsNullOrWhiteSpace(stdout)) return; + + var dirs = new Dictionary(StringComparer.Ordinal); + + foreach (var line in stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries)) + { + if (line.Length < 4) continue; + + // porcelain format: XYpath (XY = two-char status) + var xy = line[..2]; + // Pick staged char first, fall back to unstaged + var statusChar = xy[0] != ' ' ? xy[0] : xy[1]; + var status = statusChar != ' ' ? statusChar.ToString() : null; + var path = line[3..].Trim().Replace('\\', '/'); + + var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + if (segments.Length == 0) continue; + + WorktreeNodeViewModel? parent = null; + var accumulated = ""; + for (var i = 0; i < segments.Length - 1; i++) + { + accumulated = accumulated.Length == 0 ? segments[i] : accumulated + "/" + segments[i]; + if (!dirs.TryGetValue(accumulated, out var dir)) + { + dir = new WorktreeNodeViewModel { Name = segments[i], IsDirectory = true }; + dirs[accumulated] = dir; + if (parent == null) Root.Add(dir); + else parent.Children.Add(dir); + } + parent = dir; + } + + var leaf = new WorktreeNodeViewModel + { + Name = segments[^1], + Status = status, + IsDirectory = false + }; + if (parent == null) Root.Add(leaf); + else parent.Children.Add(leaf); + } + } +} diff --git a/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml.cs index 214c71d..37bfc05 100644 --- a/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/AgentStripView.axaml.cs @@ -30,7 +30,6 @@ public partial class AgentStripView : UserControl var owner = TopLevel.GetTopLevel(this) as Window; if (owner == null) return; var modal = new WorktreeModalView { DataContext = worktreeVm }; - worktreeVm.CloseCommand.Subscribe(_ => modal.Close()); await modal.ShowDialog(owner); }; } diff --git a/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml b/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml new file mode 100644 index 0000000..444fcb4 --- /dev/null +++ b/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + +