feat(ui): repo-import modal — remember folders, search, compact rows, no auto-select
This commit is contained in:
@@ -12,4 +12,7 @@ public sealed partial class RepoImportItemViewModel : ViewModelBase
|
||||
public bool CanToggle => !AlreadyAdded;
|
||||
|
||||
[ObservableProperty] private bool _isChecked;
|
||||
|
||||
// Driven by the search filter; the row collapses when it doesn't match.
|
||||
[ObservableProperty] private bool _isVisible = true;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -13,14 +14,18 @@ public sealed partial class RepoImportModalViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private readonly HashSet<string> _existingDirs = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly List<string> _folders = new();
|
||||
|
||||
public ObservableCollection<RepoImportItemViewModel> Repos { get; } = new();
|
||||
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
[ObservableProperty] private string _searchText = "";
|
||||
|
||||
public int CreateCount => Repos.Count(r => r.IsChecked && !r.AlreadyAdded);
|
||||
public bool CanCreate => CreateCount > 0;
|
||||
public string CreateButtonText => $"Create {CreateCount} list(s)";
|
||||
public bool HasFolders => _folders.Count > 0;
|
||||
|
||||
public RepoImportModalViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
||||
{
|
||||
@@ -29,8 +34,9 @@ public sealed partial class RepoImportModalViewModel : ViewModelBase
|
||||
|
||||
public async Task LoadAsync(CancellationToken ct = default)
|
||||
{
|
||||
Repos.Clear();
|
||||
ClearRepos();
|
||||
_existingDirs.Clear();
|
||||
_folders.Clear();
|
||||
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
var lists = new ListRepository(ctx);
|
||||
@@ -39,25 +45,45 @@ public sealed partial class RepoImportModalViewModel : ViewModelBase
|
||||
if (!string.IsNullOrWhiteSpace(l.WorkingDir))
|
||||
_existingDirs.Add(l.WorkingDir!);
|
||||
}
|
||||
|
||||
var settings = new AppSettingsRepository(ctx);
|
||||
foreach (var f in await settings.GetRepoImportFoldersAsync(ct))
|
||||
AddFolderToSet(f);
|
||||
|
||||
ScanAndAdd(_folders);
|
||||
OnPropertyChanged(nameof(HasFolders));
|
||||
NotifyCreateState();
|
||||
}
|
||||
|
||||
public void AddFolders(IEnumerable<string> folders)
|
||||
public async Task AddFoldersAsync(IEnumerable<string> folders)
|
||||
{
|
||||
var added = new List<string>();
|
||||
foreach (var f in folders)
|
||||
if (AddFolderToSet(f)) added.Add(f);
|
||||
|
||||
if (added.Count == 0) return;
|
||||
|
||||
ScanAndAdd(added);
|
||||
OnPropertyChanged(nameof(HasFolders));
|
||||
NotifyCreateState();
|
||||
await SaveFoldersAsync();
|
||||
}
|
||||
|
||||
private void ScanAndAdd(IEnumerable<string> folders)
|
||||
{
|
||||
var current = new HashSet<string>(
|
||||
Repos.Select(r => r.FullPath), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
var found = RepoScanner.Scan(folder);
|
||||
foreach (var item in BuildCandidates(found, current, _existingDirs))
|
||||
foreach (var item in BuildCandidates(RepoScanner.Scan(folder), current, _existingDirs))
|
||||
{
|
||||
item.PropertyChanged += OnItemChanged;
|
||||
Repos.Add(item);
|
||||
current.Add(item.FullPath);
|
||||
}
|
||||
}
|
||||
NotifyCreateState();
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
public static List<RepoImportItemViewModel> BuildCandidates(
|
||||
@@ -69,17 +95,60 @@ public sealed partial class RepoImportModalViewModel : ViewModelBase
|
||||
foreach (var c in found)
|
||||
{
|
||||
if (currentPaths.Contains(c.FullPath)) continue;
|
||||
var alreadyAdded = existingDirs.Contains(c.FullPath);
|
||||
items.Add(new RepoImportItemViewModel
|
||||
{
|
||||
Name = c.Name,
|
||||
FullPath = c.FullPath,
|
||||
AlreadyAdded = existingDirs.Contains(c.FullPath),
|
||||
IsChecked = true,
|
||||
AlreadyAdded = alreadyAdded,
|
||||
IsChecked = alreadyAdded,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ForgetFoldersAsync()
|
||||
{
|
||||
_folders.Clear();
|
||||
ClearRepos();
|
||||
OnPropertyChanged(nameof(HasFolders));
|
||||
NotifyCreateState();
|
||||
await SaveFoldersAsync();
|
||||
}
|
||||
|
||||
partial void OnSearchTextChanged(string value) => ApplyFilter();
|
||||
|
||||
private void ApplyFilter()
|
||||
{
|
||||
var q = SearchText?.Trim() ?? "";
|
||||
foreach (var r in Repos)
|
||||
r.IsVisible = q.Length == 0
|
||||
|| r.Name.Contains(q, StringComparison.OrdinalIgnoreCase)
|
||||
|| r.FullPath.Contains(q, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private bool AddFolderToSet(string folder)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(folder)) return false;
|
||||
if (_folders.Any(f => string.Equals(f, folder, StringComparison.OrdinalIgnoreCase)))
|
||||
return false;
|
||||
_folders.Add(folder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task SaveFoldersAsync()
|
||||
{
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync();
|
||||
await new AppSettingsRepository(ctx).SetRepoImportFoldersAsync(_folders);
|
||||
}
|
||||
|
||||
private void ClearRepos()
|
||||
{
|
||||
foreach (var r in Repos) r.PropertyChanged -= OnItemChanged;
|
||||
Repos.Clear();
|
||||
}
|
||||
|
||||
private void OnItemChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(RepoImportItemViewModel.IsChecked))
|
||||
|
||||
Reference in New Issue
Block a user