1
0

278 lines
9.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using DynamicData;
using DynamicData.Binding;
using Flawless.Client.Models;
using Flawless.Client.Service;
using Flawless.Core.Modal;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using ChangeType = Flawless.Client.Service.LocalFileTreeAccessor.ChangeType;
namespace Flawless.Client.ViewModels;
public class LocalChangesNode
{
public required string FullPath { get; set; }
public required string Type { get; set; }
public DateTime? ModifiedTime { get; set; }
public bool Included
{
get
{
if (Contents != null) return Contents.All(c => c.Included);
return _actualIncluded;
}
set
{
if (Contents != null) foreach (var n in Contents) n.Included = value;
_actualIncluded = value;
}
}
private bool _actualIncluded;
public ObservableCollection<LocalChangesNode>? Contents { get; set; }
public static LocalChangesNode FromFolder(string folderPath)
{
return new LocalChangesNode
{
Type = "Folder",
FullPath = folderPath,
Contents = new()
};
}
public static LocalChangesNode FromWorkspaceFile(LocalFileTreeAccessor.ChangeRecord file)
{
return new LocalChangesNode
{
Type = file.Type.ToString(),
FullPath = file.File.WorkPath,
ModifiedTime = file.File.ModifyTime
};
}
}
public class CommitTransitNode
{
public required string Guid { get; set; }
public required string Author { get; set; }
public required string Message { get; set; }
public required DateTime? CommitAt { get; set; }
public static CommitTransitNode FromCommit(RepositoryModel.Commit cm)
{
string msg;
if (cm.Message.Length > 28)
{
msg = cm.Message.Substring(0, 28) + "...";
}
else
{
msg = cm.Message;
}
return new CommitTransitNode
{
Guid = cm.CommitId.ToString(),
Author = cm.Author,
CommitAt = cm.CommittedOn.ToLocalTime(),
Message = msg,
};
}
}
public partial class RepositoryViewModel : RoutableViewModelBase
{
public RepositoryModel Repository { get; }
public RepositoryLocalDatabaseModel LocalDatabase { get; }
public HierarchicalTreeDataGridSource<LocalChangesNode> LocalChange { get; }
public HierarchicalTreeDataGridSource<LocalChangesNode> FileTree { get; }
public FlatTreeDataGridSource<CommitTransitNode> Commits { get; }
public ObservableCollection<LocalChangesNode> LocalChangeSetRaw { get; } = new();
public UserModel User { get; }
[Reactive] private bool _autoDetectChanges = true;
[Reactive] private bool _isOwnerRole, _isDeveloperRole, _isReporterRole, _isGuestRole;
public RepositoryViewModel(RepositoryModel repo, IScreen hostScreen) : base(hostScreen)
{
Repository = repo;
LocalDatabase = RepositoryService.C.GetRepositoryLocalDatabase(repo);
User = UserService.C.GetUserInfoAsync(Api.C.Username.Value!)!;
// Setup repository permission change watcher
RefreshRepositoryRoleInfo();
Repository.Members.ObserveCollectionChanges().Subscribe(_ => RefreshRepositoryRoleInfo());
// Setup local change set
LocalChange = new HierarchicalTreeDataGridSource<LocalChangesNode>(LocalChangeSetRaw)
{
Columns =
{
new CheckBoxColumn<LocalChangesNode>(
string.Empty, n => n.Included, (n, v) => n.Included = v),
new HierarchicalExpanderColumn<LocalChangesNode>(
new TextColumn<LocalChangesNode, string>(
"Name",
n => Path.GetFileName(n.FullPath)),
n => n.Contents),
new TextColumn<LocalChangesNode, string>(
"Change",
n => n.Contents != null ? String.Empty : n.Type.ToString()),
new TextColumn<LocalChangesNode, string>(
"File Type",
n => n.Contents != null ? "Folder" : Path.GetExtension(n.FullPath)),
new TextColumn<LocalChangesNode, ulong>(
"Size",
n => 0),
new TextColumn<LocalChangesNode, DateTime?>(
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
}
};
Commits = new FlatTreeDataGridSource<CommitTransitNode>(Repository.Commits.Select(CommitTransitNode.FromCommit))
{
Columns =
{
new TextColumn<CommitTransitNode, string>(
string.Empty, n => n.Guid == LocalDatabase.CurrentCommit.ToString() ? "*" : String.Empty),
new TextColumn<CommitTransitNode, string>(
"Message", x => x.Message),
new TextColumn<CommitTransitNode, string>(
"Author", x => x.Author),
new TextColumn<CommitTransitNode, DateTime>(
"Time", x => x.CommitAt!.Value),
new TextColumn<CommitTransitNode, string>(
"Id", x => x.Guid.Substring(0, 13)),
}
};
DetectLocalChangesAsyncCommand.Execute();
}
private void CollectChanges(List<LocalFileTreeAccessor.ChangeRecord> store, IEnumerable<LocalChangesNode> changesNode)
{
foreach (var n in changesNode)
{
if (n.Contents != null) CollectChanges(store, n.Contents);
else if (n.Included)
{
store.Add(new LocalFileTreeAccessor.ChangeRecord(Enum.Parse<ChangeType>(n.Type), new WorkspaceFile
{
WorkPath = n.FullPath,
ModifyTime = n.ModifiedTime!.Value
}));
}
}
}
[ReactiveCommand]
private async Task CommitSelectedChangesAsync()
{
var changes = new List<LocalFileTreeAccessor.ChangeRecord>();
CollectChanges(changes, LocalChangeSetRaw);
if (changes.Count == 0) return;
var manifest = await RepositoryService.C.CommitWorkspaceAsBaselineAsync(Repository, changes,
LocalDatabase.CommitMessage ?? string.Empty);
if (manifest == null) return;
LocalDatabase.LocalAccessor.SetBaseline(manifest.Value.FilePaths);
LocalDatabase.CommitMessage = string.Empty;
await DetectLocalChangesAsyncCommand.Execute();
}
[ReactiveCommand]
private async Task CloseRepositoryAsync()
{
await RepositoryService.C.CloseRepositoryAsync(Repository);
await HostScreen.Router.NavigateBack.Execute();
}
[ReactiveCommand]
private void RefreshRepositoryRoleInfo()
{
var isOwner = Repository.OwnerName == User.Username;
var role = isOwner ?
RepositoryModel.RepositoryRole.Owner :
Repository.Members.First(p => p.Username == User.Username).Role;
if (role >= RepositoryModel.RepositoryRole.Owner) IsOwnerRole = true;
if (role >= RepositoryModel.RepositoryRole.Developer) IsDeveloperRole = true;
if (role >= RepositoryModel.RepositoryRole.Reporter) IsReporterRole = true;
if (role >= RepositoryModel.RepositoryRole.Guest) IsGuestRole = true;
}
[ReactiveCommand]
private async ValueTask DetectLocalChangesAsync()
{
var ns = await Task.Run(() =>
{
LocalDatabase.LocalAccessor.Refresh();
// Generate a map of all folders
var folderMap = new Dictionary<string, LocalChangesNode>();
foreach (var k in LocalDatabase.LocalAccessor.Changes.Keys)
AddParentToMap(k);
var nodes = new List<LocalChangesNode>();
foreach (var file in LocalDatabase.LocalAccessor.Changes.Values)
{
var directory = Path.GetDirectoryName(file.File.WorkPath);
var n = LocalChangesNode.FromWorkspaceFile(file);
if (string.IsNullOrEmpty(directory)) nodes.Add(n);
else folderMap[directory].Contents!.Add(n);
}
nodes.AddRange(folderMap.Values);
return nodes;
void AddParentToMap(string path)
{
var parent = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(parent) || folderMap.ContainsKey(parent)) return;
folderMap.Add(parent, LocalChangesNode.FromFolder(parent));
}
});
LocalChangeSetRaw.Clear();
LocalChangeSetRaw.AddRange(ns);
}
}