feat(ui): polish worktrees overview modal
- Restyle to match ListSettingsModalView: custom title bar, DeepBrush toolbar, LineBrush footer, SurfaceBrush outer border, no system chrome - Add column header row (TASK / BRANCH / STATE / DIFF / AGE) with TextFaintBrush + MonoFont + LetterSpacing, separator line below - Replace wt-row style with task-row-equivalent: transparent bg, CornerRadius 8, 1px border, :pointerover + .selected transitions - Add IsSelected to WorktreeOverviewRowViewModel; SelectRow() helper on modal VM clears previous selection before setting new one - Wire OnRowTapped in code-behind for click-to-select - Wire ShowDiff: VM takes Func<WorktreeModalViewModel> factory, builds diffVm and delegates window creation to both call sites (MainWindow and ListsIslandView); register Func<WorktreeModalViewModel> in DI Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,7 @@ sealed class Program
|
||||
|
||||
// ViewModels
|
||||
sc.AddTransient<WorktreeModalViewModel>();
|
||||
sc.AddTransient<Func<WorktreeModalViewModel>>(sp => () => sp.GetRequiredService<WorktreeModalViewModel>());
|
||||
sc.AddTransient<WorktreesOverviewModalViewModel>();
|
||||
sc.AddTransient<Func<WorktreesOverviewModalViewModel>>(sp => () => sp.GetRequiredService<WorktreesOverviewModalViewModel>());
|
||||
sc.AddSingleton<IPrimeScheduleApi, WorkerPrimeScheduleApi>();
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed partial class WorktreeOverviewRowViewModel : ViewModelBase
|
||||
[ObservableProperty] private string? _diffStat;
|
||||
[ObservableProperty] private DateTime _createdAt;
|
||||
[ObservableProperty] private bool _pathExistsOnDisk;
|
||||
[ObservableProperty] private bool _isSelected;
|
||||
|
||||
public string AgeText => FormatAge(DateTime.UtcNow - CreatedAt);
|
||||
public bool IsActive => State == WorktreeState.Active;
|
||||
@@ -48,24 +49,34 @@ public sealed partial class WorktreesGroupViewModel : ViewModelBase
|
||||
public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
{
|
||||
private readonly WorkerClient _worker;
|
||||
private readonly Func<WorktreeModalViewModel> _diffVmFactory;
|
||||
|
||||
[ObservableProperty] private string? _listIdFilter;
|
||||
[ObservableProperty] private string _title = "Worktrees";
|
||||
[ObservableProperty] private bool _isGlobal;
|
||||
[ObservableProperty] private bool _isBusy;
|
||||
[ObservableProperty] private string? _statusMessage;
|
||||
[ObservableProperty] private WorktreeOverviewRowViewModel? _selectedRow;
|
||||
|
||||
public ObservableCollection<WorktreeOverviewRowViewModel> Rows { get; } = new();
|
||||
public ObservableCollection<WorktreesGroupViewModel> Groups { get; } = new();
|
||||
|
||||
public Action? CloseAction { get; set; }
|
||||
public Action<WorktreeOverviewRowViewModel>? ShowDiffAction { get; set; }
|
||||
public Action<WorktreeModalViewModel>? ShowDiffAction { get; set; }
|
||||
public Action<string, string>? JumpToTaskAction { get; set; }
|
||||
public Func<string, Task<bool>>? ConfirmAction { get; set; }
|
||||
|
||||
public WorktreesOverviewModalViewModel(WorkerClient worker)
|
||||
public WorktreesOverviewModalViewModel(WorkerClient worker, Func<WorktreeModalViewModel> diffVmFactory)
|
||||
{
|
||||
_worker = worker;
|
||||
_diffVmFactory = diffVmFactory;
|
||||
}
|
||||
|
||||
public void SelectRow(WorktreeOverviewRowViewModel row)
|
||||
{
|
||||
if (SelectedRow is not null) SelectedRow.IsSelected = false;
|
||||
SelectedRow = row;
|
||||
row.IsSelected = true;
|
||||
}
|
||||
|
||||
public void Configure(string? listId, string? listName)
|
||||
@@ -136,7 +147,9 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
||||
private void ShowDiff(WorktreeOverviewRowViewModel? row)
|
||||
{
|
||||
if (row is null) return;
|
||||
ShowDiffAction?.Invoke(row);
|
||||
var diffVm = _diffVmFactory();
|
||||
diffVm.WorktreePath = row.Path;
|
||||
ShowDiffAction?.Invoke(diffVm);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -38,7 +38,15 @@ public partial class ListsIslandView : UserControl
|
||||
if (item is not null) v.SelectedList = item;
|
||||
}
|
||||
};
|
||||
// TODO: ShowDiffAction and ConfirmAction not wired in v1
|
||||
modal.ShowDiffAction = diffVm =>
|
||||
{
|
||||
var top2 = TopLevel.GetTopLevel(this) as Window;
|
||||
if (top2 is null) return;
|
||||
var dlg = new WorktreeModalView { DataContext = diffVm };
|
||||
diffVm.CloseAction = () => dlg.Close();
|
||||
_ = diffVm.LoadAsync();
|
||||
_ = dlg.ShowDialog(top2);
|
||||
};
|
||||
var top = TopLevel.GetTopLevel(this) as Window;
|
||||
if (top is null) window.Show();
|
||||
else await window.ShowDialog(top);
|
||||
|
||||
@@ -3,6 +3,7 @@ using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
using ClaudeDo.Ui.Views.Modals;
|
||||
using ClaudeDo.Ui.Views.Planning;
|
||||
|
||||
@@ -45,7 +46,13 @@ public partial class MainWindow : Window
|
||||
if (item is not null && s.Lists is not null) s.Lists.SelectedList = item;
|
||||
}
|
||||
};
|
||||
// TODO: ShowDiffAction and ConfirmAction not wired in v1
|
||||
modal.ShowDiffAction = diffVm =>
|
||||
{
|
||||
var diffDlg = new WorktreeModalView { DataContext = diffVm };
|
||||
diffVm.CloseAction = () => diffDlg.Close();
|
||||
_ = diffVm.LoadAsync();
|
||||
_ = diffDlg.ShowDialog(this);
|
||||
};
|
||||
await dlg.ShowDialog(this);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,11 +7,16 @@
|
||||
Title="{Binding Title}"
|
||||
Width="900" Height="560" MinWidth="640" MinHeight="360"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource VoidBrush}">
|
||||
Background="{DynamicResource VoidBrush}"
|
||||
SystemDecorations="None"
|
||||
ExtendClientAreaToDecorationsHint="True">
|
||||
|
||||
<Window.Resources>
|
||||
<converters:WorktreeStateColorConverter x:Key="WorktreeStateColor"/>
|
||||
<DataTemplate x:Key="WorktreeRowTemplate" x:DataType="vm:WorktreeOverviewRowViewModel">
|
||||
<Border Classes="wt-row">
|
||||
<Border Classes="wt-row"
|
||||
Classes.selected="{Binding IsSelected}"
|
||||
Tapped="OnRowTapped">
|
||||
<Border.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Show diff"
|
||||
@@ -80,27 +85,95 @@
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Border.wt-row">
|
||||
<Setter Property="Background" Value="{DynamicResource DeepBrush}"/>
|
||||
<Setter Property="Padding" Value="12,10"/>
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource LineBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||
<Setter Property="Padding" Value="10,8"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Margin" Value="0,0,0,6"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<BrushTransition Property="BorderBrush" Duration="0:0:0.10"/>
|
||||
</Transitions>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="Border.wt-row:pointerover">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource LineBrightBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="Border.wt-row.selected">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource LineBrightBrush}"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<DockPanel LastChildFill="True" Margin="12">
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0,0,0,8">
|
||||
<Border Background="{DynamicResource SurfaceBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="1">
|
||||
<Grid RowDefinitions="36,Auto,*,52">
|
||||
|
||||
<!-- Title bar -->
|
||||
<Border Grid.Row="0"
|
||||
Background="{DynamicResource DeepBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
PointerPressed="OnTitleBarPressed">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding Title}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="14,0,0,0"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextBrush}"/>
|
||||
<Button Grid.Column="1"
|
||||
Content="✕"
|
||||
Command="{Binding CloseCommand}"
|
||||
Margin="0,0,8,0"
|
||||
Width="28" Height="28"
|
||||
FontSize="11"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{DynamicResource DeepBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="12,8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Refresh" Command="{Binding RefreshCommand}" IsEnabled="{Binding !IsBusy}"/>
|
||||
<Button Content="Cleanup finished" Command="{Binding CleanupFinishedCommand}" IsEnabled="{Binding !IsBusy}"/>
|
||||
<TextBlock Text="{Binding StatusMessage}" VerticalAlignment="Center" Margin="12,0,0,0"
|
||||
Foreground="{DynamicResource TextDimBrush}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,8,0,0">
|
||||
<Button Content="Close" Command="{Binding CloseCommand}"/>
|
||||
</StackPanel>
|
||||
<!-- Content -->
|
||||
<ScrollViewer Grid.Row="2" Padding="12,8">
|
||||
<StackPanel>
|
||||
<!-- Column headers -->
|
||||
<Grid ColumnDefinitions="*,200,90,80,80" Margin="12,0,12,4">
|
||||
<TextBlock Grid.Column="0" Text="TASK"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="10" LetterSpacing="1.4"
|
||||
Foreground="{DynamicResource TextFaintBrush}"/>
|
||||
<TextBlock Grid.Column="1" Text="BRANCH"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="10" LetterSpacing="1.4"
|
||||
Foreground="{DynamicResource TextFaintBrush}"/>
|
||||
<TextBlock Grid.Column="2" Text="STATE"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="10" LetterSpacing="1.4"
|
||||
Foreground="{DynamicResource TextFaintBrush}"/>
|
||||
<TextBlock Grid.Column="3" Text="DIFF"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="10" LetterSpacing="1.4"
|
||||
Foreground="{DynamicResource TextFaintBrush}"/>
|
||||
<TextBlock Grid.Column="4" Text="AGE"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="10" LetterSpacing="1.4"
|
||||
Foreground="{DynamicResource TextFaintBrush}"/>
|
||||
</Grid>
|
||||
<Border Height="1" Background="{DynamicResource LineBrush}" Margin="0,0,0,8"/>
|
||||
|
||||
<ScrollViewer>
|
||||
<Grid>
|
||||
<!-- Rows (per-list) -->
|
||||
<ItemsControl ItemsSource="{Binding Rows}" IsVisible="{Binding !IsGlobal}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="vm:WorktreeOverviewRowViewModel">
|
||||
@@ -109,6 +182,7 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Rows (global, grouped) -->
|
||||
<ItemsControl ItemsSource="{Binding Groups}" IsVisible="{Binding IsGlobal}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="vm:WorktreesGroupViewModel">
|
||||
@@ -124,7 +198,20 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border Grid.Row="3"
|
||||
Background="{DynamicResource DeepBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="12,10">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="Close" Command="{Binding CloseCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Modals;
|
||||
|
||||
public partial class WorktreesOverviewModalView : Window
|
||||
{
|
||||
public WorktreesOverviewModalView() => InitializeComponent();
|
||||
|
||||
private void OnRowTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is Border { DataContext: WorktreeOverviewRowViewModel row } &&
|
||||
DataContext is WorktreesOverviewModalViewModel vm)
|
||||
{
|
||||
vm.SelectRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user