feat(claude-do): merge Diff viewer file tree: folder/file-type icons and change-kin
ClaudeDo-Task: c5106d39c4e849a6ad0027f77fad742c
This commit is contained in:
@@ -292,6 +292,23 @@
|
||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- diff file tree: folders stay neutral, file icons take the change-kind colour via Tag (A/D/M/R) -->
|
||||
<Style Selector="PathIcon.tree-icon">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
||||
</Style>
|
||||
<Style Selector="PathIcon.tree-icon[Tag=A]">
|
||||
<Setter Property="Foreground" Value="{StaticResource MossBrush}" />
|
||||
</Style>
|
||||
<Style Selector="PathIcon.tree-icon[Tag=D]">
|
||||
<Setter Property="Foreground" Value="{StaticResource BloodBrush}" />
|
||||
</Style>
|
||||
<Style Selector="PathIcon.tree-icon[Tag=M]">
|
||||
<Setter Property="Foreground" Value="{StaticResource PeatBrush}" />
|
||||
</Style>
|
||||
<Style Selector="PathIcon.tree-icon[Tag=R]">
|
||||
<Setter Property="Foreground" Value="{StaticResource SageBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- hover -->
|
||||
<Style Selector=".icon-btn:pointerover PathIcon, .new-list-btn:pointerover PathIcon">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||
@@ -1071,6 +1088,19 @@
|
||||
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||
</Style>
|
||||
<!-- diff file tree names: change-kind colour; must stay after TextBlock.meta -->
|
||||
<Style Selector="TextBlock.tree-name[Tag=A]">
|
||||
<Setter Property="Foreground" Value="{StaticResource MossBrush}" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.tree-name[Tag=D]">
|
||||
<Setter Property="Foreground" Value="{StaticResource BloodBrush}" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.tree-name[Tag=M]">
|
||||
<Setter Property="Foreground" Value="{StaticResource PeatBrush}" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.tree-name[Tag=R]">
|
||||
<Setter Property="Foreground" Value="{StaticResource SageBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- Default body / list values / descriptions -->
|
||||
<Style Selector="TextBlock.body">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Material.Icons;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
@@ -63,7 +64,14 @@ public sealed partial class DiffTreeNodeViewModel : ViewModelBase
|
||||
public string RelativePath { get; init; } = "";
|
||||
public DiffFileViewModel? File { get; init; }
|
||||
public ObservableCollection<DiffTreeNodeViewModel> Children { get; } = new();
|
||||
[ObservableProperty] private bool _isExpanded = true;
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IconKey))]
|
||||
private bool _isExpanded = true;
|
||||
|
||||
/// <summary>The row's <c>mdi:</c> icon key: open/closed folder, or the file-type icon.</summary>
|
||||
public string IconKey => IsDirectory
|
||||
? (IsExpanded ? "mdi:FolderOpen" : "mdi:Folder")
|
||||
: DiffFileIcons.ForPath(Name);
|
||||
|
||||
public string? StatusCode => File?.StatusCode;
|
||||
public bool ShowStats => File is { IsBinary: false, IsPureRename: false };
|
||||
@@ -71,6 +79,36 @@ public sealed partial class DiffTreeNodeViewModel : ViewModelBase
|
||||
public int Deletions => File?.Deletions ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>Maps a file name's extension to an <c>mdi:</c> icon key for the diff file tree.</summary>
|
||||
public static class DiffFileIcons
|
||||
{
|
||||
private const MaterialIconKind Fallback = MaterialIconKind.FileOutline;
|
||||
|
||||
private static readonly Dictionary<string, MaterialIconKind> ByExtension = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[".cs"] = MaterialIconKind.LanguageCsharp,
|
||||
[".axaml"] = MaterialIconKind.CodeTags,
|
||||
[".xaml"] = MaterialIconKind.CodeTags,
|
||||
[".xml"] = MaterialIconKind.CodeTags,
|
||||
[".json"] = MaterialIconKind.CodeJson,
|
||||
[".md"] = MaterialIconKind.LanguageMarkdownOutline,
|
||||
[".csproj"] = MaterialIconKind.FileCogOutline,
|
||||
[".slnx"] = MaterialIconKind.FileCogOutline,
|
||||
[".ps1"] = MaterialIconKind.Powershell,
|
||||
[".sh"] = MaterialIconKind.Console,
|
||||
[".png"] = MaterialIconKind.FileImageOutline,
|
||||
[".jpg"] = MaterialIconKind.FileImageOutline,
|
||||
[".jpeg"] = MaterialIconKind.FileImageOutline,
|
||||
[".gif"] = MaterialIconKind.FileImageOutline,
|
||||
[".svg"] = MaterialIconKind.FileImageOutline,
|
||||
[".ico"] = MaterialIconKind.FileImageOutline,
|
||||
};
|
||||
|
||||
/// <summary>Icon key for a file name or path; unknown or missing extensions fall back to a generic file.</summary>
|
||||
public static string ForPath(string path) =>
|
||||
"mdi:" + (ByExtension.TryGetValue(System.IO.Path.GetExtension(path), out var kind) ? kind : Fallback);
|
||||
}
|
||||
|
||||
/// Builds a folder-grouped tree from a flat list of parsed diff files.
|
||||
public static class DiffTree
|
||||
{
|
||||
|
||||
@@ -88,17 +88,20 @@
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate DataType="vm:DiffTreeNodeViewModel" ItemsSource="{Binding Children}">
|
||||
<Border Background="Transparent" Tapped="OnNodeTapped" Cursor="Hand" Padding="0,2">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Tag="{Binding StatusCode}" CornerRadius="3" Padding="4,0" Margin="0,0,6,0"
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto">
|
||||
<PathIcon Grid.Column="0" Classes="tree-icon" Width="14" Height="14" Margin="0,0,6,0"
|
||||
Tag="{Binding StatusCode}"
|
||||
Data="{Binding IconKey, Converter={StaticResource IconKey}}"/>
|
||||
<Border Grid.Column="1" Tag="{Binding StatusCode}" CornerRadius="3" Padding="4,0" Margin="0,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding StatusCode, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Text="{Binding StatusCode}"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Foreground="{DynamicResource TextBrush}"/>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Classes="meta" Text="{Binding Name}"
|
||||
<TextBlock Grid.Column="2" Classes="meta tree-name" Text="{Binding Name}" Tag="{Binding StatusCode}"
|
||||
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center"
|
||||
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
IsVisible="{Binding ShowStats}">
|
||||
<TextBlock Foreground="{DynamicResource MossBrightBrush}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
namespace ClaudeDo.Ui.Tests;
|
||||
|
||||
public class DiffFileIconsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("src/Foo.cs", "mdi:LanguageCsharp")]
|
||||
[InlineData("Views/A.axaml", "mdi:CodeTags")]
|
||||
[InlineData("a.xaml", "mdi:CodeTags")]
|
||||
[InlineData("a.xml", "mdi:CodeTags")]
|
||||
[InlineData("package.json", "mdi:CodeJson")]
|
||||
[InlineData("README.md", "mdi:LanguageMarkdownOutline")]
|
||||
[InlineData("X.csproj", "mdi:FileCogOutline")]
|
||||
[InlineData("ClaudeDo.slnx", "mdi:FileCogOutline")]
|
||||
[InlineData("run.ps1", "mdi:Powershell")]
|
||||
[InlineData("build.sh", "mdi:Console")]
|
||||
[InlineData("logo.png", "mdi:FileImageOutline")]
|
||||
public void ForPath_MapsKnownExtensions(string path, string expected)
|
||||
=> Assert.Equal(expected, DiffFileIcons.ForPath(path));
|
||||
|
||||
[Theory]
|
||||
[InlineData("Foo.CS")]
|
||||
[InlineData("A.AXAML")]
|
||||
public void ForPath_IsCaseInsensitive(string path)
|
||||
=> Assert.NotEqual("mdi:FileOutline", DiffFileIcons.ForPath(path));
|
||||
|
||||
[Theory]
|
||||
[InlineData("Dockerfile")]
|
||||
[InlineData("notes.unknownext")]
|
||||
[InlineData("")]
|
||||
public void ForPath_FallsBackToGenericFile(string path)
|
||||
=> Assert.Equal("mdi:FileOutline", DiffFileIcons.ForPath(path));
|
||||
|
||||
[Fact]
|
||||
public void FolderIcon_FollowsIsExpanded()
|
||||
{
|
||||
var dir = new DiffTreeNodeViewModel { Name = "src", IsDirectory = true };
|
||||
Assert.Equal("mdi:FolderOpen", dir.IconKey);
|
||||
dir.IsExpanded = false;
|
||||
Assert.Equal("mdi:Folder", dir.IconKey);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user