diff --git a/src/ClaudeDo.App/App.axaml b/src/ClaudeDo.App/App.axaml index edd82c3..0c7445f 100644 --- a/src/ClaudeDo.App/App.axaml +++ b/src/ClaudeDo.App/App.axaml @@ -15,6 +15,9 @@ + + + diff --git a/src/ClaudeDo.Ui/Converters/DotBrushConverter.cs b/src/ClaudeDo.Ui/Converters/DotBrushConverter.cs new file mode 100644 index 0000000..d24bbad --- /dev/null +++ b/src/ClaudeDo.Ui/Converters/DotBrushConverter.cs @@ -0,0 +1,24 @@ +using System.Globalization; +using Avalonia; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace ClaudeDo.Ui.Converters; + +public class DotBrushConverter : IValueConverter +{ + public static DotBrushConverter Instance { get; } = new(); + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var key = value?.ToString(); + if (string.IsNullOrEmpty(key)) key = "Moss"; + var resourceKey = $"{key}Brush"; + if (Application.Current?.TryGetResource(resourceKey, null, out var res) == true) + return res as IBrush; + return Brushes.Transparent; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} diff --git a/src/ClaudeDo.Ui/Converters/IconKeyConverter.cs b/src/ClaudeDo.Ui/Converters/IconKeyConverter.cs new file mode 100644 index 0000000..39a7c6d --- /dev/null +++ b/src/ClaudeDo.Ui/Converters/IconKeyConverter.cs @@ -0,0 +1,28 @@ +using System.Globalization; +using Avalonia; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace ClaudeDo.Ui.Converters; + +/// +/// Converts an icon key string (e.g. "Sun") to the matching StreamGeometry resource "Icon.Sun". +/// +public class IconKeyConverter : IValueConverter +{ + public static IconKeyConverter Instance { get; } = new(); + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not string key || string.IsNullOrEmpty(key)) + return null; + + var resourceKey = $"Icon.{key}"; + if (Application.Current?.TryGetResource(resourceKey, null, out var res) == true) + return res as StreamGeometry; + return null; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} diff --git a/src/ClaudeDo.Ui/Converters/UpperCaseConverter.cs b/src/ClaudeDo.Ui/Converters/UpperCaseConverter.cs new file mode 100644 index 0000000..a9f49de --- /dev/null +++ b/src/ClaudeDo.Ui/Converters/UpperCaseConverter.cs @@ -0,0 +1,15 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace ClaudeDo.Ui.Converters; + +public class UpperCaseConverter : IValueConverter +{ + public static UpperCaseConverter Instance { get; } = new(); + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + => value?.ToString()?.ToUpperInvariant(); + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotSupportedException(); +} diff --git a/src/ClaudeDo.Ui/Design/IslandStyles.axaml b/src/ClaudeDo.Ui/Design/IslandStyles.axaml index 9a39539..cc83799 100644 --- a/src/ClaudeDo.Ui/Design/IslandStyles.axaml +++ b/src/ClaudeDo.Ui/Design/IslandStyles.axaml @@ -24,6 +24,60 @@ M5 5l14 14M19 5L5 19 M3 3 h18 v18 h-18 z M6 12 l4 4 8-8 + + + + + + + + M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z M12 3v2M12 19v2M3 12h2M19 12h2M5.5 5.5l1.4 1.4M17.1 17.1l1.4 1.4M5.5 18.5l1.4-1.4M17.1 6.9l1.4-1.4 + + + M3 12h4l2-6 4 12 2-8 2 2h4 + + + M12 3.5l2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.2-4.1 5.8-.8z + + + M3.5 5a2 2 0 0 1 2-2h13a2 2 0 0 1 2 2v15a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z M3.5 10h17M8 3v4M16 3v4 + + + M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z + + + M3 13h5l1 2h6l1-2h5M3 13l3-8h12l3 8v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z + + + M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z + + + M11 4a7 7 0 1 0 0 14A7 7 0 0 0 11 4z M20 20l-3.5-3.5 + + + M12 5v14M5 12h14 + + + M5 12m-1.3 0a1.3 1.3 0 1 0 2.6 0 1.3 1.3 0 1 0-2.6 0 M12 12m-1.3 0a1.3 1.3 0 1 0 2.6 0 1.3 1.3 0 1 0-2.6 0 M19 12m-1.3 0a1.3 1.3 0 1 0 2.6 0 1.3 1.3 0 1 0-2.6 0 + + + M6 3a2 2 0 1 0 0 4 2 2 0 0 0 0-4z M6 19a2 2 0 1 0 0 4 2 2 0 0 0 0-4z M18 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4z M6 7v10M6 13c0-4 12-2 12-4 + + + M8 8h12a1.5 1.5 0 0 1 1.5 1.5v12A1.5 1.5 0 0 1 20 23H8a1.5 1.5 0 0 1-1.5-1.5v-12A1.5 1.5 0 0 1 8 8z M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3 + + + M4 7h16M10 11v6M14 11v6M5 7l1 13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1l1-13M9 7V4h6v3 + + + M7 4v16M7 20l-3-3M7 4l-3 3M14 8h7M14 12h5M14 16h3 + + + M6 6l12 12M18 6L6 18 + + + M4 12l5 5 11-11 + @@ -346,6 +400,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs index b524b67..f6969c1 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListNavItemViewModel.cs @@ -10,4 +10,5 @@ public sealed partial class ListNavItemViewModel : ViewModelBase [ObservableProperty] private int _count; [ObservableProperty] private bool _isActive; public string? IconKey { get; init; } + public string? DotColorKey { get; init; } } diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs index 59614c2..19a4625 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/ListsIslandViewModel.cs @@ -18,30 +18,63 @@ public sealed partial class ListsIslandViewModel : ViewModelBase public void RequestFocusSearch() => FocusSearchRequested?.Invoke(this, EventArgs.Empty); public ObservableCollection Items { get; } = new(); + public ObservableCollection SmartLists { get; } = new(); + public ObservableCollection UserLists { get; } = new(); [ObservableProperty] private string _searchText = ""; [ObservableProperty] private ListNavItemViewModel? _selectedList; + public string UserName { get; } = Environment.UserName; + public string MachineName { get; } = Environment.MachineName; + public string UserInitials { get; } + public ListsIslandViewModel(IDbContextFactory dbFactory) { _dbFactory = dbFactory; + var parts = Environment.UserName.Split('.', '_', '-', ' '); + UserInitials = parts.Length >= 2 + ? $"{parts[0][0]}{parts[1][0]}".ToUpperInvariant() + : Environment.UserName.Length >= 2 + ? Environment.UserName[..2].ToUpperInvariant() + : Environment.UserName.ToUpperInvariant(); } public async Task LoadAsync(CancellationToken ct = default) { Items.Clear(); - Items.Add(new ListNavItemViewModel { Id = "smart:my-day", Name = "My Day", Kind = ListKind.Smart, IconKey = "Sun" }); - Items.Add(new ListNavItemViewModel { Id = "smart:important", Name = "Important", Kind = ListKind.Smart, IconKey = "Star" }); - Items.Add(new ListNavItemViewModel { Id = "smart:planned", Name = "Planned", Kind = ListKind.Smart, IconKey = "Calendar" }); - Items.Add(new ListNavItemViewModel { Id = "virtual:running", Name = "Running", Kind = ListKind.Virtual, IconKey = "Pulse" }); - Items.Add(new ListNavItemViewModel { Id = "virtual:review", Name = "Review", Kind = ListKind.Virtual, IconKey = "Eye" }); + SmartLists.Clear(); + UserLists.Clear(); + + var smart = new[] + { + new ListNavItemViewModel { Id = "smart:my-day", Name = "My Day", Kind = ListKind.Smart, IconKey = "Sun" }, + new ListNavItemViewModel { Id = "smart:important", Name = "Important", Kind = ListKind.Smart, IconKey = "Star" }, + new ListNavItemViewModel { Id = "smart:planned", Name = "Planned", Kind = ListKind.Smart, IconKey = "Calendar" }, + new ListNavItemViewModel { Id = "virtual:running", Name = "Running", Kind = ListKind.Virtual, IconKey = "Activity" }, + new ListNavItemViewModel { Id = "virtual:review", Name = "Review", Kind = ListKind.Virtual, IconKey = "Eye" }, + }; + foreach (var s in smart) { Items.Add(s); SmartLists.Add(s); } await using var ctx = await _dbFactory.CreateDbContextAsync(ct); var lists = new ListRepository(ctx); var seedNames = new HashSet(new[] { "My Day", "Important", "Planned" }); + var dotColors = new[] { "Moss", "Peat", "Sage" }; + int idx = 0; foreach (var l in await lists.GetAllAsync(ct)) - if (!seedNames.Contains(l.Name)) - Items.Add(new ListNavItemViewModel { Id = $"user:{l.Id}", Name = l.Name, Kind = ListKind.User, IconKey = "Folder" }); + { + if (seedNames.Contains(l.Name)) continue; + var item = new ListNavItemViewModel + { + Id = $"user:{l.Id}", + Name = l.Name, + Kind = ListKind.User, + IconKey = "Folder", + DotColorKey = dotColors[idx % dotColors.Length], + }; + Items.Add(item); + UserLists.Add(item); + idx++; + } await RefreshCountsAsync(ct); SelectedList = Items.FirstOrDefault();