refactor: show submodule as tree instead of list (#1307)

This commit is contained in:
leo 2025-05-14 17:55:28 +08:00
parent 5ec51eefb9
commit 463d161ac7
No known key found for this signature in database
6 changed files with 534 additions and 136 deletions

View file

@ -210,7 +210,7 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _submodules, value);
}
public List<Models.Submodule> VisibleSubmodules
public SubmoduleCollection VisibleSubmodules
{
get => _visibleSubmodules;
private set => SetProperty(ref _visibleSubmodules, value);
@ -2512,7 +2512,7 @@ namespace SourceGit.ViewModels
return visible;
}
private List<Models.Submodule> BuildVisibleSubmodules()
private SubmoduleCollection BuildVisibleSubmodules()
{
var visible = new List<Models.Submodule>();
if (string.IsNullOrEmpty(_filter))
@ -2527,7 +2527,8 @@ namespace SourceGit.ViewModels
visible.Add(s);
}
}
return visible;
return SubmoduleCollection.Build(visible, _visibleSubmodules);
}
private void RefreshHistoriesFilters(bool refresh)
@ -2759,7 +2760,7 @@ namespace SourceGit.ViewModels
private List<Models.Tag> _tags = new List<Models.Tag>();
private List<Models.Tag> _visibleTags = new List<Models.Tag>();
private List<Models.Submodule> _submodules = new List<Models.Submodule>();
private List<Models.Submodule> _visibleSubmodules = new List<Models.Submodule>();
private SubmoduleCollection _visibleSubmodules = new SubmoduleCollection();
private bool _isAutoFetching = false;
private Timer _autoFetchTimer = null;

View file

@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class SubmoduleTreeNode : ObservableObject
{
public string FullPath { get; set; } = string.Empty;
public int Depth { get; private set; } = 0;
public Models.Submodule Module { get; private set; } = null;
public List<SubmoduleTreeNode> Children { get; private set; } = [];
public int Counter = 0;
public bool IsFolder
{
get => Module == null;
}
public bool IsExpanded
{
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
public string ChildCounter
{
get => Counter > 0 ? $"({Counter})" : string.Empty;
}
public bool IsDirty
{
get => Module?.IsDirty ?? false;
}
public SubmoduleTreeNode(Models.Submodule module, int depth)
{
FullPath = module.Path;
Depth = depth;
Module = module;
IsExpanded = false;
}
public SubmoduleTreeNode(string path, int depth, bool isExpanded)
{
FullPath = path;
Depth = depth;
IsExpanded = isExpanded;
Counter = 1;
}
public static List<SubmoduleTreeNode> Build(IList<Models.Submodule> submodules, HashSet<string> expaneded)
{
var nodes = new List<SubmoduleTreeNode>();
var folders = new Dictionary<string, SubmoduleTreeNode>();
foreach (var module in submodules)
{
var sepIdx = module.Path.IndexOf('/', StringComparison.Ordinal);
if (sepIdx == -1)
{
nodes.Add(new SubmoduleTreeNode(module, 0));
}
else
{
SubmoduleTreeNode lastFolder = null;
int depth = 0;
while (sepIdx != -1)
{
var folder = module.Path.Substring(0, sepIdx);
if (folders.TryGetValue(folder, out var value))
{
lastFolder = value;
lastFolder.Counter++;
}
else if (lastFolder == null)
{
lastFolder = new SubmoduleTreeNode(folder, depth, expaneded.Contains(folder));
folders.Add(folder, lastFolder);
InsertFolder(nodes, lastFolder);
}
else
{
var cur = new SubmoduleTreeNode(folder, depth, expaneded.Contains(folder));
folders.Add(folder, cur);
InsertFolder(lastFolder.Children, cur);
lastFolder = cur;
}
depth++;
sepIdx = module.Path.IndexOf('/', sepIdx + 1);
}
lastFolder?.Children.Add(new SubmoduleTreeNode(module, depth));
}
}
folders.Clear();
return nodes;
}
private static void InsertFolder(List<SubmoduleTreeNode> collection, SubmoduleTreeNode subFolder)
{
for (int i = 0; i < collection.Count; i++)
{
if (!collection[i].IsFolder)
{
collection.Insert(i, subFolder);
return;
}
}
collection.Add(subFolder);
}
private bool _isExpanded = false;
}
public class SubmoduleCollection
{
public List<SubmoduleTreeNode> Tree
{
get;
set;
} = [];
public AvaloniaList<SubmoduleTreeNode> Rows
{
get;
set;
} = [];
public static SubmoduleCollection Build(List<Models.Submodule> submodules, SubmoduleCollection old)
{
var oldExpanded = new HashSet<string>();
foreach (var row in old.Rows)
{
if (row.IsFolder && row.IsExpanded)
oldExpanded.Add(row.FullPath);
}
var collection = new SubmoduleCollection();
collection.Tree = SubmoduleTreeNode.Build(submodules, oldExpanded);
var rows = new List<SubmoduleTreeNode>();
collection.MakeTreeRows(rows, collection.Tree);
collection.Rows.AddRange(rows);
return collection;
}
public void Clear()
{
Tree.Clear();
Rows.Clear();
}
public void ToggleExpand(SubmoduleTreeNode node)
{
node.IsExpanded = !node.IsExpanded;
var rows = Rows;
var depth = node.Depth;
var idx = rows.IndexOf(node);
if (idx == -1)
return;
if (node.IsExpanded)
{
var subrows = new List<SubmoduleTreeNode>();
MakeTreeRows(subrows, node.Children);
rows.InsertRange(idx + 1, subrows);
}
else
{
var removeCount = 0;
for (int i = idx + 1; i < rows.Count; i++)
{
var row = rows[i];
if (row.Depth <= depth)
break;
removeCount++;
}
rows.RemoveRange(idx + 1, removeCount);
}
}
private void MakeTreeRows(List<SubmoduleTreeNode> rows, List<SubmoduleTreeNode> nodes)
{
foreach (var node in nodes)
{
rows.Add(node);
if (!node.IsExpanded || !node.IsFolder)
continue;
MakeTreeRows(rows, node.Children);
}
}
}
}