feat(ui): open ListSettingsModal via context menu and gear button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-04-22 13:27:05 +02:00
parent 5348220e60
commit 5784dbee94
5 changed files with 73 additions and 5 deletions

View File

@@ -85,7 +85,8 @@ sealed class Program
sc.AddSingleton<ListsIslandViewModel>(sp => sc.AddSingleton<ListsIslandViewModel>(sp =>
new ListsIslandViewModel( new ListsIslandViewModel(
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(), sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
sp)); sp,
sp.GetRequiredService<WorkerClient>()));
sc.AddSingleton<TasksIslandViewModel>(sp => sc.AddSingleton<TasksIslandViewModel>(sp =>
new TasksIslandViewModel(sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>())); new TasksIslandViewModel(sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>()));
sc.AddSingleton<DetailsIslandViewModel>(sp => sc.AddSingleton<DetailsIslandViewModel>(sp =>

View File

@@ -9,6 +9,8 @@ public sealed partial class ListNavItemViewModel : ViewModelBase
public required ListKind Kind { get; init; } public required ListKind Kind { get; init; }
[ObservableProperty] private int _count; [ObservableProperty] private int _count;
[ObservableProperty] private bool _isActive; [ObservableProperty] private bool _isActive;
[ObservableProperty] private string? _workingDir;
[ObservableProperty] private string _defaultCommitType = "chore";
public string? IconKey { get; init; } public string? IconKey { get; init; }
public string? DotColorKey { get; init; } public string? DotColorKey { get; init; }
} }

View File

@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Data; using ClaudeDo.Data;
using ClaudeDo.Data.Repositories; using ClaudeDo.Data.Repositories;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Modals; using ClaudeDo.Ui.ViewModels.Modals;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -15,12 +16,14 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
{ {
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory; private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
private readonly IServiceProvider? _services; private readonly IServiceProvider? _services;
private readonly WorkerClient? _worker;
public event EventHandler? SelectionChanged; public event EventHandler? SelectionChanged;
public event EventHandler? FocusSearchRequested; public event EventHandler? FocusSearchRequested;
public void RequestFocusSearch() => FocusSearchRequested?.Invoke(this, EventArgs.Empty); public void RequestFocusSearch() => FocusSearchRequested?.Invoke(this, EventArgs.Empty);
public Func<SettingsModalViewModel, Task>? ShowSettingsModal { get; set; } public Func<SettingsModalViewModel, Task>? ShowSettingsModal { get; set; }
public Func<ListSettingsModalViewModel, System.Threading.Tasks.Task>? ShowListSettingsModal { get; set; }
[RelayCommand] [RelayCommand]
private async Task OpenSettings() private async Task OpenSettings()
@@ -31,6 +34,16 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
await ShowSettingsModal(settingsVm); await ShowSettingsModal(settingsVm);
} }
[RelayCommand]
private async System.Threading.Tasks.Task OpenListSettingsAsync(ListNavItemViewModel? row)
{
if (row is null || ShowListSettingsModal is null || _services is null) return;
var vm = _services.GetRequiredService<ListSettingsModalViewModel>();
await vm.LoadAsync(row.Id, row.Name, row.WorkingDir, row.DefaultCommitType);
await ShowListSettingsModal(vm);
await RefreshRowAsync(row.Id);
}
public ObservableCollection<ListNavItemViewModel> Items { get; } = new(); public ObservableCollection<ListNavItemViewModel> Items { get; } = new();
public ObservableCollection<ListNavItemViewModel> SmartLists { get; } = new(); public ObservableCollection<ListNavItemViewModel> SmartLists { get; } = new();
public ObservableCollection<ListNavItemViewModel> UserLists { get; } = new(); public ObservableCollection<ListNavItemViewModel> UserLists { get; } = new();
@@ -42,16 +55,20 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
public string MachineName { get; } = Environment.MachineName; public string MachineName { get; } = Environment.MachineName;
public string UserInitials { get; } public string UserInitials { get; }
public ListsIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IServiceProvider? services = null) public ListsIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IServiceProvider? services = null, WorkerClient? worker = null)
{ {
_dbFactory = dbFactory; _dbFactory = dbFactory;
_services = services; _services = services;
_worker = worker;
var parts = Environment.UserName.Split('.', '_', '-', ' '); var parts = Environment.UserName.Split('.', '_', '-', ' ');
UserInitials = parts.Length >= 2 UserInitials = parts.Length >= 2
? $"{parts[0][0]}{parts[1][0]}".ToUpperInvariant() ? $"{parts[0][0]}{parts[1][0]}".ToUpperInvariant()
: Environment.UserName.Length >= 2 : Environment.UserName.Length >= 2
? Environment.UserName[..2].ToUpperInvariant() ? Environment.UserName[..2].ToUpperInvariant()
: Environment.UserName.ToUpperInvariant(); : Environment.UserName.ToUpperInvariant();
if (_worker is not null)
_worker.ListUpdatedEvent += id => _ = RefreshRowAsync(id);
} }
public async Task LoadAsync(CancellationToken ct = default) public async Task LoadAsync(CancellationToken ct = default)
@@ -85,6 +102,8 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
Kind = ListKind.User, Kind = ListKind.User,
IconKey = "Folder", IconKey = "Folder",
DotColorKey = dotColors[idx % dotColors.Length], DotColorKey = dotColors[idx % dotColors.Length],
WorkingDir = l.WorkingDir,
DefaultCommitType = l.DefaultCommitType,
}; };
Items.Add(item); Items.Add(item);
UserLists.Add(item); UserLists.Add(item);
@@ -109,4 +128,23 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
foreach (var i in Items) i.IsActive = ReferenceEquals(i, value); foreach (var i in Items) i.IsActive = ReferenceEquals(i, value);
SelectionChanged?.Invoke(this, EventArgs.Empty); SelectionChanged?.Invoke(this, EventArgs.Empty);
} }
private async System.Threading.Tasks.Task RefreshRowAsync(string rowId)
{
try
{
var rawId = rowId.StartsWith("user:") ? rowId["user:".Length..] : rowId;
var row = UserLists.FirstOrDefault(r => r.Id == rowId);
if (row is null) return;
await using var ctx = await _dbFactory.CreateDbContextAsync();
var lists = new ListRepository(ctx);
var entity = await lists.GetByIdAsync(rawId);
if (entity is null) return;
row.WorkingDir = entity.WorkingDir;
row.DefaultCommitType = entity.DefaultCommitType;
}
catch { /* best-effort refresh */ }
}
} }

View File

@@ -66,7 +66,7 @@
</StackPanel> </StackPanel>
<!-- More button --> <!-- More button -->
<Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center" <Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center"
Command="{Binding OpenSettingsCommand}" Command="{Binding OpenListSettingsCommand}"
ToolTip.Tip="Settings"> ToolTip.Tip="Settings">
<PathIcon Data="{StaticResource Icon.MoreHorizontal}" <PathIcon Data="{StaticResource Icon.MoreHorizontal}"
Width="14" Height="14" Width="14" Height="14"
@@ -130,9 +130,16 @@
<DataTemplate DataType="vm:ListNavItemViewModel"> <DataTemplate DataType="vm:ListNavItemViewModel">
<Border Classes="list-item" Classes.active="{Binding IsActive}" <Border Classes="list-item" Classes.active="{Binding IsActive}"
Tapped="OnItemTapped"> Tapped="OnItemTapped">
<Grid ColumnDefinitions="20,*,Auto"> <Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Settings..."
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenListSettingsCommand}"
CommandParameter="{Binding}"/>
</ContextMenu>
</Border.ContextMenu>
<Grid ColumnDefinitions="20,*,Auto,Auto">
<!-- Left accent bar for active state --> <!-- Left accent bar for active state -->
<Border Grid.Column="0" Grid.ColumnSpan="3" <Border Grid.Column="0" Grid.ColumnSpan="4"
Background="Transparent" Background="Transparent"
CornerRadius="8" IsHitTestVisible="False" CornerRadius="8" IsHitTestVisible="False"
IsVisible="{Binding IsActive}"> IsVisible="{Binding IsActive}">
@@ -158,6 +165,18 @@
FontFamily="{DynamicResource MonoFamily}" FontSize="10" FontFamily="{DynamicResource MonoFamily}" FontSize="10"
Foreground="{DynamicResource TextFaintBrush}" Foreground="{DynamicResource TextFaintBrush}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<!-- Gear button -->
<Button Grid.Column="3"
Content="⚙"
ToolTip.Tip="Settings..."
Background="Transparent"
BorderThickness="0"
Padding="4,0"
FontSize="11"
Foreground="{DynamicResource TextFaintBrush}"
VerticalAlignment="Center"
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenListSettingsCommand}"
CommandParameter="{Binding}"/>
</Grid> </Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>

View File

@@ -17,6 +17,14 @@ public partial class ListsIslandView : UserControl
{ {
vm.FocusSearchRequested += (_, _) => SearchBox.Focus(); vm.FocusSearchRequested += (_, _) => SearchBox.Focus();
vm.ShowSettingsModal = ShowSettingsAsync; vm.ShowSettingsModal = ShowSettingsAsync;
vm.ShowListSettingsModal = async modal =>
{
var window = new ListSettingsModalView { DataContext = modal };
modal.CloseAction = () => window.Close();
var top = TopLevel.GetTopLevel(this) as Window;
if (top is null) window.Show();
else await window.ShowDialog(top);
};
} }
}; };
} }