fix(ui): make overview modal resizable; add diff content pane
Drop outer Border wrapper in WorktreesOverviewModalView so Avalonia edge resize handles reach the window frame. Add split pane to WorktreeModalView with file tree on left and per-file unified diff on right; wire SelectedNode via SelectedItem TwoWay binding + SelectionChanged fallback; add GetFileDiffAsync to GitService. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,15 @@ public sealed class GitService
|
||||
return stdout.Trim();
|
||||
}
|
||||
|
||||
public async Task<string> GetFileDiffAsync(string worktreePath, string? baseCommit, string relativePath, CancellationToken ct = default)
|
||||
{
|
||||
string[] args = string.IsNullOrEmpty(baseCommit)
|
||||
? ["diff", "--", relativePath]
|
||||
: ["diff", $"{baseCommit}..HEAD", "--", relativePath];
|
||||
var (_, stdout, _) = await RunGitAsync(worktreePath, args, ct);
|
||||
return stdout;
|
||||
}
|
||||
|
||||
public async Task WorktreeRemoveAsync(string repoDir, string worktreePath, bool force = false, CancellationToken ct = default)
|
||||
{
|
||||
var args = new List<string> { "worktree", "remove" };
|
||||
|
||||
@@ -10,6 +10,7 @@ public sealed partial class WorktreeNodeViewModel : ViewModelBase
|
||||
public required string Name { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public bool IsDirectory { get; init; }
|
||||
public string RelativePath { get; init; } = "";
|
||||
public ObservableCollection<WorktreeNodeViewModel> Children { get; } = new();
|
||||
}
|
||||
|
||||
@@ -21,6 +22,8 @@ public sealed partial class WorktreeModalViewModel : ViewModelBase
|
||||
|
||||
[ObservableProperty] private string _worktreePath = "";
|
||||
[ObservableProperty] private string? _baseCommit;
|
||||
[ObservableProperty] private WorktreeNodeViewModel? _selectedNode;
|
||||
[ObservableProperty] private string _selectedFileDiff = "";
|
||||
|
||||
// Set by the view (same pattern as DiffModalViewModel.CloseAction)
|
||||
public Action? CloseAction { get; set; }
|
||||
@@ -30,6 +33,29 @@ public sealed partial class WorktreeModalViewModel : ViewModelBase
|
||||
_git = git;
|
||||
}
|
||||
|
||||
partial void OnSelectedNodeChanged(WorktreeNodeViewModel? value)
|
||||
{
|
||||
_ = LoadFileDiffAsync(value);
|
||||
}
|
||||
|
||||
private async Task LoadFileDiffAsync(WorktreeNodeViewModel? node)
|
||||
{
|
||||
if (node is null || node.IsDirectory || string.IsNullOrEmpty(node.RelativePath))
|
||||
{
|
||||
SelectedFileDiff = "";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SelectedFileDiff = await _git.GetFileDiffAsync(WorktreePath, BaseCommit, node.RelativePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
SelectedFileDiff = "";
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Close() => CloseAction?.Invoke();
|
||||
|
||||
@@ -97,7 +123,8 @@ public sealed partial class WorktreeModalViewModel : ViewModelBase
|
||||
{
|
||||
Name = segments[^1],
|
||||
Status = status,
|
||||
IsDirectory = false
|
||||
IsDirectory = false,
|
||||
RelativePath = path
|
||||
};
|
||||
if (parent == null) Root.Add(leaf);
|
||||
else parent.Children.Add(leaf);
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
x:Class="ClaudeDo.Ui.Views.Modals.WorktreeModalView"
|
||||
x:DataType="vm:WorktreeModalViewModel"
|
||||
Title="Worktree"
|
||||
Width="640" Height="720"
|
||||
Width="1100" Height="720"
|
||||
MinWidth="640" MinHeight="400"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
SystemDecorations="None"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
Background="Transparent"
|
||||
CanResize="False"
|
||||
CanResize="True"
|
||||
TransparencyLevelHint="AcrylicBlur">
|
||||
|
||||
<Window.KeyBindings>
|
||||
@@ -39,9 +40,16 @@
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
|
||||
<!-- File tree -->
|
||||
<TreeView DockPanel.Dock="Top" ItemsSource="{Binding Root}"
|
||||
Background="Transparent" Margin="8,0,8,8">
|
||||
<!-- Split: file tree | splitter | diff pane -->
|
||||
<Grid ColumnDefinitions="260,4,*">
|
||||
|
||||
<!-- Left: file tree -->
|
||||
<TreeView x:Name="FileTree"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{Binding Root}"
|
||||
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"
|
||||
Background="Transparent"
|
||||
Margin="8,0,4,8">
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate DataType="vm:WorktreeNodeViewModel"
|
||||
ItemsSource="{Binding Children}">
|
||||
@@ -61,6 +69,23 @@
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<!-- Splitter -->
|
||||
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Background="{DynamicResource LineBrush}"/>
|
||||
|
||||
<!-- Right: diff content -->
|
||||
<ScrollViewer Grid.Column="2" Padding="8"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Margin="4,0,8,8">
|
||||
<SelectableTextBlock Text="{Binding SelectedFileDiff}"
|
||||
FontFamily="{DynamicResource MonoFont}"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Window>
|
||||
|
||||
@@ -21,6 +21,18 @@ public partial class WorktreeModalView : Window
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is WorktreeModalViewModel vm)
|
||||
vm.CloseAction = Close;
|
||||
|
||||
// Wire TreeView selection — SelectedItem TwoWay binding may not fire
|
||||
// reliably in Avalonia 12 for TreeView; use SelectionChanged as backup.
|
||||
var tree = this.FindControl<TreeView>("FileTree");
|
||||
if (tree is not null)
|
||||
tree.SelectionChanged += OnFileTreeSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnFileTreeSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is WorktreeModalViewModel vm && sender is TreeView tree)
|
||||
vm.SelectedNode = tree.SelectedItem as WorktreeNodeViewModel;
|
||||
}
|
||||
|
||||
protected override async void OnOpened(EventArgs e)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
Width="900" Height="560" MinWidth="640" MinHeight="360"
|
||||
CanResize="True"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource VoidBrush}"
|
||||
Background="{DynamicResource SurfaceBrush}"
|
||||
SystemDecorations="None"
|
||||
ExtendClientAreaToDecorationsHint="True">
|
||||
|
||||
@@ -104,9 +104,6 @@
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<Border Background="{DynamicResource SurfaceBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="1">
|
||||
<Grid RowDefinitions="36,Auto,*,52">
|
||||
|
||||
<!-- Title bar -->
|
||||
@@ -208,5 +205,4 @@
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user