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}" />
|
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}" />
|
||||||
</Style>
|
</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 -->
|
<!-- hover -->
|
||||||
<Style Selector=".icon-btn:pointerover PathIcon, .new-list-btn:pointerover PathIcon">
|
<Style Selector=".icon-btn:pointerover PathIcon, .new-list-btn:pointerover PathIcon">
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextBrush}" />
|
||||||
@@ -1071,6 +1088,19 @@
|
|||||||
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}" />
|
||||||
</Style>
|
</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 -->
|
<!-- Default body / list values / descriptions -->
|
||||||
<Style Selector="TextBlock.body">
|
<Style Selector="TextBlock.body">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Material.Icons;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.ViewModels.Modals;
|
namespace ClaudeDo.Ui.ViewModels.Modals;
|
||||||
|
|
||||||
@@ -63,7 +64,14 @@ public sealed partial class DiffTreeNodeViewModel : ViewModelBase
|
|||||||
public string RelativePath { get; init; } = "";
|
public string RelativePath { get; init; } = "";
|
||||||
public DiffFileViewModel? File { get; init; }
|
public DiffFileViewModel? File { get; init; }
|
||||||
public ObservableCollection<DiffTreeNodeViewModel> Children { get; } = new();
|
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 string? StatusCode => File?.StatusCode;
|
||||||
public bool ShowStats => File is { IsBinary: false, IsPureRename: false };
|
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;
|
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.
|
/// Builds a folder-grouped tree from a flat list of parsed diff files.
|
||||||
public static class DiffTree
|
public static class DiffTree
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -88,17 +88,20 @@
|
|||||||
<TreeView.ItemTemplate>
|
<TreeView.ItemTemplate>
|
||||||
<TreeDataTemplate DataType="vm:DiffTreeNodeViewModel" ItemsSource="{Binding Children}">
|
<TreeDataTemplate DataType="vm:DiffTreeNodeViewModel" ItemsSource="{Binding Children}">
|
||||||
<Border Background="Transparent" Tapped="OnNodeTapped" Cursor="Hand" Padding="0,2">
|
<Border Background="Transparent" Tapped="OnNodeTapped" Cursor="Hand" Padding="0,2">
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
<Grid ColumnDefinitions="Auto,Auto,*,Auto">
|
||||||
<Border Grid.Column="0" Tag="{Binding StatusCode}" CornerRadius="3" Padding="4,0" Margin="0,0,6,0"
|
<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"
|
VerticalAlignment="Center"
|
||||||
IsVisible="{Binding StatusCode, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
IsVisible="{Binding StatusCode, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||||
<TextBlock Text="{Binding StatusCode}"
|
<TextBlock Text="{Binding StatusCode}"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeEyebrow}"
|
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||||
Foreground="{DynamicResource TextBrush}"/>
|
Foreground="{DynamicResource TextBrush}"/>
|
||||||
</Border>
|
</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"/>
|
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"
|
Margin="0,0,8,0"
|
||||||
IsVisible="{Binding ShowStats}">
|
IsVisible="{Binding ShowStats}">
|
||||||
<TextBlock Foreground="{DynamicResource MossBrightBrush}" FontSize="{StaticResource FontSizeEyebrow}"
|
<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