2024-07-06 02:17:41 -07:00
|
|
|
using System;
|
2024-02-05 23:08:37 -08:00
|
|
|
using System.Collections.Generic;
|
2024-05-14 03:50:36 -07:00
|
|
|
using System.IO;
|
2024-05-24 20:15:07 -07:00
|
|
|
|
|
|
|
using Avalonia;
|
2024-03-17 18:37:06 -07:00
|
|
|
using Avalonia.Collections;
|
2024-07-06 02:17:41 -07:00
|
|
|
using Avalonia.Media;
|
2024-03-17 18:37:06 -07:00
|
|
|
|
2024-05-24 20:15:07 -07:00
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
|
2024-05-13 20:47:56 -07:00
|
|
|
namespace SourceGit.ViewModels
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-24 20:15:07 -07:00
|
|
|
public class BranchTreeNode : ObservableObject
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-07-06 02:17:41 -07:00
|
|
|
public string Name { get; private set; } = string.Empty;
|
|
|
|
public object Backend { get; private set; } = null;
|
|
|
|
public int Depth { get; set; } = 0;
|
2024-07-06 02:29:24 -07:00
|
|
|
public bool IsSelected { get; set; } = false;
|
2024-07-06 02:17:41 -07:00
|
|
|
public List<BranchTreeNode> Children { get; private set; } = new List<BranchTreeNode>();
|
2024-07-09 21:11:51 -07:00
|
|
|
|
2024-09-25 05:30:48 -07:00
|
|
|
public bool IsFiltered
|
|
|
|
{
|
|
|
|
get => _isFiltered;
|
|
|
|
set => SetProperty(ref _isFiltered, value);
|
|
|
|
}
|
|
|
|
|
2024-07-06 02:17:41 -07:00
|
|
|
public bool IsExpanded
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-07-06 02:17:41 -07:00
|
|
|
get => _isExpanded;
|
|
|
|
set => SetProperty(ref _isExpanded, value);
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
2024-07-09 21:11:51 -07:00
|
|
|
|
2024-07-06 02:17:41 -07:00
|
|
|
public CornerRadius CornerRadius
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-07-06 02:17:41 -07:00
|
|
|
get => _cornerRadius;
|
|
|
|
set => SetProperty(ref _cornerRadius, value);
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
2024-07-09 21:11:51 -07:00
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
public bool IsBranch
|
|
|
|
{
|
2024-07-06 02:17:41 -07:00
|
|
|
get => Backend is Models.Branch;
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
2024-06-06 00:31:02 -07:00
|
|
|
|
2024-07-06 02:17:41 -07:00
|
|
|
public FontWeight NameFontWeight
|
2024-07-04 02:59:32 -07:00
|
|
|
{
|
2024-07-06 02:17:41 -07:00
|
|
|
get => Backend is Models.Branch { IsCurrent: true } ? FontWeight.Bold : FontWeight.Regular;
|
2024-07-04 02:59:32 -07:00
|
|
|
}
|
|
|
|
|
2024-07-01 19:23:21 -07:00
|
|
|
public string Tooltip
|
|
|
|
{
|
2024-07-06 02:17:41 -07:00
|
|
|
get => Backend is Models.Branch b ? b.FriendlyName : null;
|
2024-05-24 20:15:07 -07:00
|
|
|
}
|
2024-07-09 21:11:51 -07:00
|
|
|
|
2024-09-25 05:30:48 -07:00
|
|
|
private bool _isFiltered = false;
|
2024-07-04 02:59:32 -07:00
|
|
|
private bool _isExpanded = false;
|
2024-07-06 02:17:41 -07:00
|
|
|
private CornerRadius _cornerRadius = new CornerRadius(4);
|
2024-05-24 20:15:07 -07:00
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
public class Builder
|
|
|
|
{
|
2024-02-05 23:08:37 -08:00
|
|
|
public List<BranchTreeNode> Locals => _locals;
|
|
|
|
public List<BranchTreeNode> Remotes => _remotes;
|
|
|
|
|
2024-05-13 20:47:56 -07:00
|
|
|
public void Run(List<Models.Branch> branches, List<Models.Remote> remotes, bool bForceExpanded)
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
var folders = new Dictionary<string, BranchTreeNode>();
|
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
foreach (var remote in remotes)
|
|
|
|
{
|
2024-02-05 23:08:37 -08:00
|
|
|
var path = $"remote/{remote.Name}";
|
2024-03-17 18:37:06 -07:00
|
|
|
var node = new BranchTreeNode()
|
|
|
|
{
|
2024-02-05 23:08:37 -08:00
|
|
|
Name = remote.Name,
|
|
|
|
Backend = remote,
|
2024-05-13 20:47:56 -07:00
|
|
|
IsExpanded = bForceExpanded || _expanded.Contains(path),
|
2024-02-05 23:08:37 -08:00
|
|
|
};
|
|
|
|
|
2024-05-14 03:50:36 -07:00
|
|
|
folders.Add(path, node);
|
2024-02-05 23:08:37 -08:00
|
|
|
_remotes.Add(node);
|
|
|
|
}
|
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
foreach (var branch in branches)
|
|
|
|
{
|
2024-02-06 01:52:16 -08:00
|
|
|
var isFiltered = _filters.Contains(branch.FullName);
|
2024-03-17 18:37:06 -07:00
|
|
|
if (branch.IsLocal)
|
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
MakeBranchNode(branch, _locals, folders, "local", isFiltered, bForceExpanded);
|
2024-03-17 18:37:06 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-02-05 23:08:37 -08:00
|
|
|
var remote = _remotes.Find(x => x.Name == branch.Remote);
|
2024-03-31 01:54:29 -07:00
|
|
|
if (remote != null)
|
2024-05-14 03:50:36 -07:00
|
|
|
MakeBranchNode(branch, remote.Children, folders, $"remote/{remote.Name}", isFiltered, bForceExpanded);
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-14 03:50:36 -07:00
|
|
|
folders.Clear();
|
2024-02-05 23:08:37 -08:00
|
|
|
SortNodes(_locals);
|
|
|
|
SortNodes(_remotes);
|
|
|
|
}
|
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
public void SetFilters(AvaloniaList<string> filters)
|
|
|
|
{
|
2024-02-06 01:52:16 -08:00
|
|
|
_filters.AddRange(filters);
|
|
|
|
}
|
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
public void CollectExpandedNodes(List<BranchTreeNode> nodes, bool isLocal)
|
|
|
|
{
|
2024-02-05 23:08:37 -08:00
|
|
|
CollectExpandedNodes(nodes, isLocal ? "local" : "remote");
|
|
|
|
}
|
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
private void CollectExpandedNodes(List<BranchTreeNode> nodes, string prefix)
|
|
|
|
{
|
|
|
|
foreach (var node in nodes)
|
|
|
|
{
|
2024-07-06 02:17:41 -07:00
|
|
|
if (node.Backend is Models.Branch)
|
|
|
|
continue;
|
2024-07-09 21:11:51 -07:00
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
var path = prefix + "/" + node.Name;
|
2024-07-06 02:17:41 -07:00
|
|
|
if (node.IsExpanded)
|
2024-03-31 01:54:29 -07:00
|
|
|
_expanded.Add(path);
|
2024-07-09 21:11:51 -07:00
|
|
|
|
2024-02-05 23:08:37 -08:00
|
|
|
CollectExpandedNodes(node.Children, path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-14 03:50:36 -07:00
|
|
|
private void MakeBranchNode(Models.Branch branch, List<BranchTreeNode> roots, Dictionary<string, BranchTreeNode> folders, string prefix, bool isFiltered, bool bForceExpanded)
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
var sepIdx = branch.Name.IndexOf('/', StringComparison.Ordinal);
|
2024-09-18 01:02:38 -07:00
|
|
|
if (sepIdx == -1 || branch.IsDetachedHead)
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
roots.Add(new BranchTreeNode()
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
Name = branch.Name,
|
2024-02-05 23:08:37 -08:00
|
|
|
Backend = branch,
|
|
|
|
IsExpanded = false,
|
2024-02-06 01:52:16 -08:00
|
|
|
IsFiltered = isFiltered,
|
2024-05-14 03:50:36 -07:00
|
|
|
});
|
2024-02-05 23:08:37 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-14 03:50:36 -07:00
|
|
|
var lastFolder = null as BranchTreeNode;
|
|
|
|
var start = 0;
|
|
|
|
|
|
|
|
while (sepIdx != -1)
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
var folder = string.Concat(prefix, "/", branch.Name.Substring(0, sepIdx));
|
|
|
|
var name = branch.Name.Substring(start, sepIdx - start);
|
|
|
|
if (folders.TryGetValue(folder, out var val))
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
lastFolder = val;
|
2024-07-16 00:02:47 -07:00
|
|
|
if (!lastFolder.IsExpanded)
|
|
|
|
lastFolder.IsExpanded |= (branch.IsCurrent || _expanded.Contains(folder));
|
2024-03-17 18:37:06 -07:00
|
|
|
}
|
|
|
|
else if (lastFolder == null)
|
|
|
|
{
|
|
|
|
lastFolder = new BranchTreeNode()
|
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
Name = name,
|
|
|
|
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
2024-02-05 23:08:37 -08:00
|
|
|
};
|
|
|
|
roots.Add(lastFolder);
|
2024-05-14 03:50:36 -07:00
|
|
|
folders.Add(folder, lastFolder);
|
2024-03-17 18:37:06 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
var cur = new BranchTreeNode()
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
Name = name,
|
|
|
|
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
|
2024-02-05 23:08:37 -08:00
|
|
|
};
|
2024-05-14 03:50:36 -07:00
|
|
|
lastFolder.Children.Add(cur);
|
|
|
|
folders.Add(folder, cur);
|
|
|
|
lastFolder = cur;
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
2024-05-14 03:50:36 -07:00
|
|
|
|
|
|
|
start = sepIdx + 1;
|
|
|
|
sepIdx = branch.Name.IndexOf('/', start);
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
2024-06-06 00:31:02 -07:00
|
|
|
|
2024-07-06 02:17:41 -07:00
|
|
|
lastFolder?.Children.Add(new BranchTreeNode()
|
2024-03-17 18:37:06 -07:00
|
|
|
{
|
2024-05-14 03:50:36 -07:00
|
|
|
Name = Path.GetFileName(branch.Name),
|
2024-02-05 23:08:37 -08:00
|
|
|
Backend = branch,
|
|
|
|
IsExpanded = false,
|
2024-02-06 01:52:16 -08:00
|
|
|
IsFiltered = isFiltered,
|
2024-05-14 03:50:36 -07:00
|
|
|
});
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
private void SortNodes(List<BranchTreeNode> nodes)
|
|
|
|
{
|
|
|
|
nodes.Sort((l, r) =>
|
|
|
|
{
|
2024-08-09 01:06:28 -07:00
|
|
|
if (l.Backend is Models.Branch { IsDetachedHead: true })
|
2024-05-25 11:09:40 -07:00
|
|
|
return -1;
|
2024-06-06 00:31:02 -07:00
|
|
|
|
2024-07-06 02:17:41 -07:00
|
|
|
if (l.Backend is Models.Branch)
|
|
|
|
return r.Backend is Models.Branch ? string.Compare(l.Name, r.Name, StringComparison.Ordinal) : 1;
|
2024-07-09 21:11:51 -07:00
|
|
|
|
2024-07-06 02:17:41 -07:00
|
|
|
return r.Backend is Models.Branch ? -1 : string.Compare(l.Name, r.Name, StringComparison.Ordinal);
|
2024-02-05 23:08:37 -08:00
|
|
|
});
|
|
|
|
|
2024-03-31 01:54:29 -07:00
|
|
|
foreach (var node in nodes)
|
|
|
|
SortNodes(node.Children);
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
|
|
|
|
2024-03-17 18:37:06 -07:00
|
|
|
private readonly List<BranchTreeNode> _locals = new List<BranchTreeNode>();
|
|
|
|
private readonly List<BranchTreeNode> _remotes = new List<BranchTreeNode>();
|
|
|
|
private readonly HashSet<string> _expanded = new HashSet<string>();
|
|
|
|
private readonly List<string> _filters = new List<string>();
|
2024-02-05 23:08:37 -08:00
|
|
|
}
|
|
|
|
}
|
2024-03-31 01:54:29 -07:00
|
|
|
}
|