278 lines
9.0 KiB
C#
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);
|
|
}
|
|
} |