748 lines
27 KiB
C#
748 lines
27 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reactive.Linq;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Models.TreeDataGrid;
|
|
using DynamicData;
|
|
using DynamicData.Binding;
|
|
using Flawless.Abstraction;
|
|
using Flawless.Client.Models;
|
|
using Flawless.Client.Remote;
|
|
using Flawless.Client.Service;
|
|
using Flawless.Client.ViewModels.ModalBox;
|
|
using Flawless.Client.Views.ModalBox;
|
|
using ReactiveUI;
|
|
using ReactiveUI.SourceGenerators;
|
|
using Ursa.Controls;
|
|
using WorkspaceFile = Flawless.Core.Modal.WorkspaceFile;
|
|
|
|
namespace Flawless.Client.ViewModels;
|
|
|
|
public partial class LocalChangesNode : ReactiveModel
|
|
{
|
|
[Reactive] private string _fullPath;
|
|
|
|
[Reactive] private string _type;
|
|
|
|
[Reactive] private DateTime? _modifiedTime;
|
|
|
|
[Reactive] private LocalChangesNode? _parent;
|
|
|
|
[Reactive] private long _size;
|
|
|
|
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;
|
|
this.RaiseAndSetIfChanged(ref _actualIncluded, value, nameof(Included));
|
|
_parent?.RaisePropertyChanged(nameof(Included));
|
|
}
|
|
}
|
|
|
|
private bool _actualIncluded;
|
|
|
|
public ObservableCollection<LocalChangesNode>? Contents { get; set; }
|
|
|
|
public static LocalChangesNode FromFolder(LocalChangesNode? parent, string folderPath)
|
|
{
|
|
return new LocalChangesNode
|
|
{
|
|
Type = "Folder",
|
|
FullPath = folderPath,
|
|
Contents = new(),
|
|
Parent = parent,
|
|
Size = -1
|
|
};
|
|
}
|
|
|
|
public static LocalChangesNode FromWorkspaceFile(LocalChangesNode? parent, ChangeInfo file)
|
|
{
|
|
return new LocalChangesNode
|
|
{
|
|
Type = file.Type.ToString(),
|
|
FullPath = file.File.WorkPath,
|
|
ModifiedTime = file.File.ModifyTime,
|
|
Parent = parent,
|
|
Size = (long) file.File.Size
|
|
};
|
|
}
|
|
}
|
|
|
|
public class CommitTransitNode
|
|
{
|
|
public required string CommitId { get; set; }
|
|
|
|
public required string Author { get; set; }
|
|
|
|
public required string Message { get; set; }
|
|
|
|
public required DateTime? CommitAt { get; set; }
|
|
|
|
public required string FullCommitId { get; set; }
|
|
|
|
public required string FullMessage { get; set; }
|
|
|
|
public required string MainDepotId { 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
|
|
{
|
|
CommitId = cm.CommitId.ToString(),
|
|
Author = cm.Author,
|
|
CommitAt = cm.CommittedOn.ToLocalTime(),
|
|
Message = msg,
|
|
FullCommitId = cm.CommitId.ToString(),
|
|
FullMessage = cm.Message,
|
|
MainDepotId = cm.DepotId.ToString(),
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
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 ObservableCollectionExtended<LocalChangesNode> LocalChangeSetRaw { get; } = new();
|
|
|
|
public ObservableCollectionExtended<LocalChangesNode> CurrentCommitFileTreeRaw { get; } = new();
|
|
|
|
public ObservableCollectionExtended<CommitTransitNode> CommitsRaw { get; } = new();
|
|
|
|
public UserModel User { get; }
|
|
|
|
[Reactive] private bool _autoDetectChanges = true;
|
|
|
|
[Reactive] private bool _isOwnerRole, _isDeveloperRole, _isReporterRole, _isGuestRole, _showWebHook;
|
|
|
|
private string _wsRoot;
|
|
|
|
public RepositoryViewModel(RepositoryModel repo, IScreen hostScreen) : base(hostScreen)
|
|
{
|
|
Repository = repo;
|
|
LocalDatabase = RepositoryService.C.GetRepositoryLocalDatabase(repo);
|
|
User = UserService.C.GetUserInfoAsync(Api.C.Username.Value!)!;
|
|
_wsRoot = PathUtility.GetWorkspacePath(Api.C.Username.Value!, Repository.OwnerName, Repository.Name);
|
|
|
|
// Setup local change set
|
|
LocalChange = new HierarchicalTreeDataGridSource<LocalChangesNode>(LocalChangeSetRaw)
|
|
{
|
|
Columns =
|
|
{
|
|
new CheckBoxColumn<LocalChangesNode>(
|
|
null, n => n.Included, (n, v) => n.Included = v,
|
|
null, new CheckBoxColumnOptions<LocalChangesNode>
|
|
{
|
|
CanUserResizeColumn = false,
|
|
CanUserSortColumn = false,
|
|
}),
|
|
|
|
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, string>(
|
|
"Size",
|
|
n => PathUtility.ConvertBytesToBestDisplay(n.Size)),
|
|
|
|
new TextColumn<LocalChangesNode, DateTime?>(
|
|
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
|
|
}
|
|
};
|
|
|
|
Commits = new FlatTreeDataGridSource<CommitTransitNode>(CommitsRaw)
|
|
{
|
|
Columns =
|
|
{
|
|
new TextColumn<CommitTransitNode, string>(
|
|
string.Empty, n => n.CommitId == LocalDatabase.CurrentCommit.ToString() ? "HEAD" : 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.CommitId.Substring(0, 13)),
|
|
}
|
|
};
|
|
|
|
FileTree = new HierarchicalTreeDataGridSource<LocalChangesNode>(CurrentCommitFileTreeRaw)
|
|
{
|
|
Columns =
|
|
{
|
|
new HierarchicalExpanderColumn<LocalChangesNode>(
|
|
new TextColumn<LocalChangesNode, string>(
|
|
"Name",
|
|
n => Path.GetFileName(n.FullPath)),
|
|
n => n.Contents),
|
|
|
|
new TextColumn<LocalChangesNode, string>(
|
|
"File Type",
|
|
n => n.Contents != null ? "Folder" : Path.GetExtension(n.FullPath)),
|
|
|
|
new TextColumn<LocalChangesNode, string>(
|
|
"Size",
|
|
n => PathUtility.ConvertBytesToBestDisplay(n.Size)),
|
|
|
|
new TextColumn<LocalChangesNode, DateTime?>(
|
|
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
|
|
}
|
|
};
|
|
|
|
_ = StartupTasksAsync();
|
|
}
|
|
|
|
|
|
private async Task StartupTasksAsync()
|
|
{
|
|
await RefreshRepositoryRoleInfoAsyncCommand.Execute();
|
|
await RefreshRepositoryIssuesAsyncCommand.Execute();
|
|
await RefreshWebhooksCommand.Execute();
|
|
await DetectLocalChangesAsyncCommand.Execute();
|
|
await RendererFileTreeAsync();
|
|
SyncCommitsFromRepository();
|
|
}
|
|
|
|
private async ValueTask RendererFileTreeAsync()
|
|
{
|
|
if (LocalDatabase.RepoAccessor == null) return;
|
|
var accessor = LocalDatabase.RepoAccessor;
|
|
var nodes = await CalculateFileTreeOfChangesNodeAsync(accessor.Select(
|
|
f => new ChangeInfo(ChangeInfoType.Add, f)));
|
|
|
|
CurrentCommitFileTreeRaw.Clear();
|
|
CurrentCommitFileTreeRaw.AddRange(nodes);
|
|
}
|
|
|
|
private void SyncCommitsFromRepository()
|
|
{
|
|
CommitsRaw.Clear();
|
|
CommitsRaw.AddRange(Repository.Commits.Select(CommitTransitNode.FromCommit));
|
|
}
|
|
|
|
private void CollectChanges(List<ChangeInfo> store, IEnumerable<LocalChangesNode> changesNode)
|
|
{
|
|
foreach (var n in changesNode)
|
|
{
|
|
if (n.Contents != null) CollectChanges(store, n.Contents);
|
|
else if (n.Included)
|
|
{
|
|
store.Add(new ChangeInfo(Enum.Parse<ChangeInfoType>(n.Type), new WorkspaceFile
|
|
{
|
|
WorkPath = n.FullPath,
|
|
ModifyTime = n.ModifiedTime!.Value
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
private Task<List<LocalChangesNode>> CalculateFileTreeOfChangesNodeAsync(IEnumerable<ChangeInfo> changesNode)
|
|
{
|
|
return Task.Run(() =>
|
|
{
|
|
// Generate a map of all folders
|
|
var folderMap = new Dictionary<string, LocalChangesNode>();
|
|
var nodes = new List<LocalChangesNode>();
|
|
|
|
foreach (var file in changesNode)
|
|
{
|
|
var parent = AddParentToMap(file.File.WorkPath);
|
|
var n = LocalChangesNode.FromWorkspaceFile(parent, file);
|
|
if (parent == null) nodes.Add(n);
|
|
else parent.Contents!.Add(n);
|
|
}
|
|
|
|
return nodes;
|
|
|
|
LocalChangesNode? AddParentToMap(string path)
|
|
{
|
|
path = WorkPath.FormatPathDirectorySeparator(Path.GetDirectoryName(path) ?? string.Empty);
|
|
|
|
// 如果为空,则其直接文件夹就是根目录
|
|
if (string.IsNullOrEmpty(path)) return null;
|
|
|
|
// 如果直接文件夹已经存在,则不再生成,直接返回即可
|
|
if (folderMap.TryGetValue(path, out var node)) return node;
|
|
|
|
// 生成当前文件夹,并先找到这个文件夹的直接文件夹
|
|
var parent = AddParentToMap(path);
|
|
node = LocalChangesNode.FromFolder(parent, path);
|
|
folderMap.Add(path, node);
|
|
if (parent != null) parent.Contents!.Add(node);
|
|
else nodes.Add(node);
|
|
|
|
// 将新建的文件夹告知调用方
|
|
return node;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void UpdatePermissionOfRepository()
|
|
{
|
|
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 Task AddWebhookAsync()
|
|
{
|
|
if (!IsOwnerRole) return;
|
|
|
|
var vm = new WebhookEditDialogViewModel();
|
|
var result = await OverlayDialog.ShowModal<WebhookEditDialogView, WebhookEditDialogViewModel>(
|
|
vm, AppDefaultValues.HostId, UIHelper.DefaultOverlayDialogOptionsYesNo());
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Create Webhook...");
|
|
await RepositoryService.C.AddWebhookAsync(Repository, vm.Url, vm.Secret, vm.EventType);
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task DeleteWebhookAsync(Webhook webhook)
|
|
{
|
|
if (!IsOwnerRole) return;
|
|
|
|
var confirm = await UIHelper.SimpleAskAsync($"Do you sure to delete webhook with url at {webhook.TargetUrl} ?");
|
|
if (confirm == DialogResult.Yes)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Delete webhook...");
|
|
await RepositoryService.C.DeleteWebhookAsync(Repository, webhook.Id);
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
public async Task ToggleWebhookAsync(Webhook webhook)
|
|
{
|
|
if (!IsOwnerRole) return;
|
|
|
|
using var l = UIHelper.MakeLoading("Update Webhook...");
|
|
var success = await RepositoryService.C.ToggleWebhookAsync(
|
|
Repository,
|
|
webhook.Id,
|
|
!webhook.IsActive);
|
|
}
|
|
|
|
|
|
[ReactiveCommand]
|
|
private async Task RevertFileTreeToSelectedCommitKeepAsync()
|
|
{
|
|
var sel = Commits.RowSelection?.SelectedItem;
|
|
if (sel != null)
|
|
{
|
|
using var l = UIHelper.MakeLoading($"Reset (Keep) to Commit {sel.CommitId}");
|
|
|
|
var changes = new List<ChangeInfo>();
|
|
CollectChanges(changes, LocalChangeSetRaw);
|
|
|
|
var kid = Repository.Commits.First(x => x.CommitId.ToString() == sel.CommitId).CommitId;
|
|
var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File);
|
|
await RepositoryService.C
|
|
.SetPointerAndMergeDepotsWithLocalFromRemoteAsync(
|
|
Repository, kid, changeDict, RepositoryResetMethod.Keep);
|
|
|
|
await DetectLocalChangesAsyncCommand.Execute();
|
|
await RendererFileTreeAsync();
|
|
SyncCommitsFromRepository();
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task RevertFileTreeToSelectedCommitSoftAsync()
|
|
{
|
|
var sel = Commits.RowSelection?.SelectedItem;
|
|
if (sel != null)
|
|
{
|
|
using var l = UIHelper.MakeLoading($"Reset (Soft) to Commit {sel.CommitId}");
|
|
var changes = new List<ChangeInfo>();
|
|
CollectChanges(changes, LocalChangeSetRaw);
|
|
|
|
if (changes.Count != 0)
|
|
{
|
|
var result = await UIHelper.SimpleAskAsync(
|
|
"There are commits that has not been committed. Do you wish discard them at all?",
|
|
DialogMode.Warning);
|
|
|
|
if (result == DialogResult.No) return;
|
|
}
|
|
|
|
|
|
var kid = Repository.Commits.First(x => x.CommitId.ToString() == sel.CommitId).CommitId;
|
|
var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File);
|
|
await RepositoryService.C
|
|
.SetPointerAndMergeDepotsWithLocalFromRemoteAsync(
|
|
Repository, kid, changeDict, RepositoryResetMethod.Soft);
|
|
|
|
await DetectLocalChangesAsyncCommand.Execute();
|
|
await RendererFileTreeAsync();
|
|
SyncCommitsFromRepository();
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task RevertFileTreeToSelectedCommitHardAsync()
|
|
{
|
|
var sel = Commits.RowSelection?.SelectedItem;
|
|
if (sel != null)
|
|
{
|
|
using var l = UIHelper.MakeLoading($"Reset (Hard) to Commit {sel.CommitId}");
|
|
|
|
if (!await RepositoryService.C.UpdateCommitsHistoryFromServerAsync(Repository)) return;
|
|
var changes = new List<ChangeInfo>();
|
|
CollectChanges(changes, LocalChangeSetRaw);
|
|
|
|
var result = await UIHelper.SimpleAskAsync(
|
|
"All files will being matched with this commit. Do you wish to execute it?",
|
|
DialogMode.Warning);
|
|
|
|
if (result == DialogResult.No) return;
|
|
|
|
var kid = Repository.Commits.First(x => x.CommitId.ToString() == sel.CommitId).CommitId;
|
|
var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File);
|
|
await RepositoryService.C
|
|
.SetPointerAndMergeDepotsWithLocalFromRemoteAsync(
|
|
Repository, kid, changeDict, RepositoryResetMethod.Hard);
|
|
|
|
await DetectLocalChangesAsyncCommand.Execute();
|
|
await RendererFileTreeAsync();
|
|
SyncCommitsFromRepository();
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task CreateIssueAsync()
|
|
{
|
|
var opt = UIHelper.DefaultOverlayDialogOptionsYesNo();
|
|
var vm = new IssueEditDialogViewModel();
|
|
|
|
var r = await OverlayDialog.ShowModal<IssueDetailEditView, IssueEditDialogViewModel>(vm, AppDefaultValues.HostId, opt);
|
|
if (r == DialogResult.Yes)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Sending issue...");
|
|
var tags = vm.Tag?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
var issueId = await RepositoryService.C.CreateIssueAsync(Repository, vm.Title, vm.Description, tags);
|
|
if (issueId == null) return;
|
|
|
|
await HostScreen.Router.Navigate.Execute(new IssueDetailViewModel(HostScreen, Repository, issueId.Value));
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task OpenIssueAsync(RepositoryModel.Issue issue)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Config issue...");
|
|
await HostScreen.Router.Navigate.Execute(new IssueDetailViewModel(HostScreen, Repository, issue.Id));
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task CloseIssueAsync(RepositoryModel.Issue issue)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Config issue...");
|
|
await RepositoryService.C.CloseIssueAsync(Repository, issue.Id);
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task ReopenIssueAsync(RepositoryModel.Issue issue)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Config issue...");
|
|
await RepositoryService.C.ReopenIssueAsync(Repository, issue.Id);
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task PullLatestRepositoryAsync()
|
|
{
|
|
using var l = UIHelper.MakeLoading("Pulling latest changes...");
|
|
var mayUpdate = await RepositoryService.C.IsPointedToCommitNotPeekResultFromServerAsync(Repository);
|
|
if (!mayUpdate.HasValue) return;
|
|
if (mayUpdate.Value == false)
|
|
{
|
|
await UIHelper.SimpleAskAsync("Everything is new, no needs to pull.");
|
|
return;
|
|
}
|
|
|
|
if (!await RepositoryService.C.UpdateCommitsHistoryFromServerAsync(Repository)) return;
|
|
|
|
if (!await RepositoryService.C.UpdateCommitsHistoryFromServerAsync(Repository)) return;
|
|
var changes = new List<ChangeInfo>();
|
|
CollectChanges(changes, LocalChangeSetRaw);
|
|
|
|
var kid = Repository.Commits.MaxBy(k => k.CommittedOn)!.CommitId;
|
|
var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File);
|
|
await RepositoryService.C.SetPointerAndMergeDepotsWithLocalFromRemoteAsync(
|
|
Repository, kid, changeDict, RepositoryResetMethod.Keep);
|
|
|
|
await DetectLocalChangesAsyncCommand.Execute();
|
|
await RendererFileTreeAsync();
|
|
SyncCommitsFromRepository();
|
|
}
|
|
|
|
|
|
[ReactiveCommand]
|
|
private async Task CommitSelectedChangesAsync()
|
|
{
|
|
var changes = new List<ChangeInfo>();
|
|
CollectChanges(changes, LocalChangeSetRaw);
|
|
|
|
if (changes.Count == 0)
|
|
{
|
|
await UIHelper.SimpleAlert("You haven't choose any changes yet!");
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(LocalDatabase.CommitMessage))
|
|
{
|
|
await UIHelper.SimpleAlert("Commit message can not be empty!");
|
|
return;
|
|
}
|
|
|
|
using var l = UIHelper.MakeLoading("Committing changes...");
|
|
var manifest = await RepositoryService.C.CommitWorkspaceAsync(Repository, changes, LocalDatabase.CommitMessage!);
|
|
if (manifest == null) return;
|
|
|
|
LocalDatabase.LocalAccessor.SetBaseline(manifest.Value.FilePaths);
|
|
LocalDatabase.CommitMessage = string.Empty;
|
|
await DetectLocalChangesAsyncCommand.Execute();
|
|
await RendererFileTreeAsync();
|
|
SyncCommitsFromRepository();
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task RevertSelectedChangesAsync()
|
|
{
|
|
var changes = new List<ChangeInfo>();
|
|
CollectChanges(changes, LocalChangeSetRaw);
|
|
|
|
if (changes.Count == 0)
|
|
{
|
|
await UIHelper.SimpleAlert("You haven't choose any changes yet!");
|
|
return;
|
|
}
|
|
|
|
using var l = UIHelper.MakeLoading("Reverting changes...");
|
|
RepositoryService.C.RevertChangesToBaseline(Repository, changes);
|
|
await DetectLocalChangesAsyncCommand.Execute();
|
|
await RendererFileTreeAsync();
|
|
SyncCommitsFromRepository();
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task DeleteRepositoryBothServerAndLocalAsync()
|
|
{
|
|
var msg = "THIS IS AN UNREVERT ACTION, YOU SHOULD AWARE THAT THIS MAY CAUSE DATA LOSS. DO YOU CONTINUE DELETE THIS REPOSITORY?";
|
|
var confirm = await UIHelper.SimpleAskAsync(msg, DialogMode.Error) == DialogResult.Yes;
|
|
if (confirm)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Delete repository...");
|
|
if (!await RepositoryService.C.DeleteRepositoryOnServerAsync(Repository)) return;
|
|
await HostScreen.Router.NavigateBack.Execute();
|
|
await RepositoryService.C.CloseRepositoryAsync(Repository);
|
|
await RepositoryService.C.DeleteFromDiskAsync(Repository);
|
|
await RepositoryService.C.UpdateRepositoriesFromServerAsync();
|
|
await RepositoryService.C.UpdateRepositoriesDownloadedStatusFromDiskAsync();
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task CloseRepositoryAsync()
|
|
{
|
|
using var l = UIHelper.MakeLoading("Save changes...");
|
|
await RepositoryService.C.CloseRepositoryAsync(Repository);
|
|
await HostScreen.Router.NavigateBack.Execute();
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async Task RefreshWebhooksAsync()
|
|
{
|
|
ShowWebHook = IsDeveloperRole && (Api.C.Status.Value?.AllowWebHook ?? false);
|
|
if (!ShowWebHook) return;
|
|
using var l = UIHelper.MakeLoading("刷新Webhook列表...");
|
|
await RepositoryService.C.UpdateWebhooksFromServerAsync(Repository);
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async ValueTask RefreshRepositoryRoleInfoAsync()
|
|
{
|
|
using var l = UIHelper.MakeLoading("Refreshing member info...");
|
|
await RepositoryService.C.UpdateMembersFromServerAsync(Repository);
|
|
UpdatePermissionOfRepository();
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async ValueTask RefreshRepositoryIssuesAsync()
|
|
{
|
|
using var l = UIHelper.MakeLoading("Refreshing issues...");
|
|
await RepositoryService.C.UpdateIssuesListFromServerAsync(Repository);
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async ValueTask DeleteMemberFromServerAsync(RepositoryModel.Member member)
|
|
{
|
|
if (!IsOwnerRole)
|
|
{
|
|
UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue");
|
|
return;
|
|
}
|
|
|
|
var result = await UIHelper.SimpleAskAsync(
|
|
$"Do you really want to delete this member: \n{member.Username} ({member.Role})", DialogMode.Warning);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
using var l = UIHelper.MakeLoading("Delete member...");
|
|
await RepositoryService.C.DeleteMemberFromServerAsync(Repository, member);
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async ValueTask ModifyMemberFromServerAsync(RepositoryModel.Member member)
|
|
{
|
|
if (!IsOwnerRole)
|
|
{
|
|
UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue");
|
|
return;
|
|
}
|
|
|
|
var style = UIHelper.DefaultOverlayDialogOptionsYesNo();
|
|
var vm = new EditRepositoryMemberDialogViewModel();
|
|
vm.LockUsername = true;
|
|
vm.Username = member.Username;
|
|
vm.SafeRole = member.Role;
|
|
var result = await OverlayDialog.ShowModal<EditRepositoryMemberDialogueView,EditRepositoryMemberDialogViewModel>
|
|
(vm, AppDefaultValues.HostId, style);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner)
|
|
{
|
|
UIHelper.NotifyError("Invalid role level!", "Permission issue");
|
|
return;
|
|
}
|
|
|
|
if (vm.SafeRole == member.Role)
|
|
{
|
|
UIHelper.NotifyError("No modification yet.", "Modification issue");
|
|
return;
|
|
}
|
|
|
|
using var l = UIHelper.MakeLoading("Modify member...");
|
|
member.Role = vm.SafeRole;
|
|
await RepositoryService.C.ModifyMemberFromServerAsync(Repository, member);
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async ValueTask AddMemberFromServerAsync()
|
|
{
|
|
if (!IsOwnerRole)
|
|
{
|
|
UIHelper.NotifyError("Only repository owner can edit this!", "Permission issue");
|
|
return;
|
|
}
|
|
|
|
var style = UIHelper.DefaultOverlayDialogOptionsYesNo();
|
|
var vm = new EditRepositoryMemberDialogViewModel();
|
|
var result = await OverlayDialog.ShowModal<EditRepositoryMemberDialogueView,EditRepositoryMemberDialogViewModel>
|
|
(vm, AppDefaultValues.HostId, style);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
if (vm.SafeRole == RepositoryModel.RepositoryRole.Owner)
|
|
{
|
|
UIHelper.NotifyError("Invalid role level!", "Permission issue");
|
|
return;
|
|
}
|
|
|
|
vm.Username = vm.Username.Trim();
|
|
if (string.IsNullOrEmpty(vm.Username) || vm.Username.Length < 3)
|
|
{
|
|
UIHelper.NotifyError("Not a valid username!", "Parameter error");
|
|
return;
|
|
}
|
|
|
|
using var l = UIHelper.MakeLoading("Add member...");
|
|
await RepositoryService.C.AddMemberFromServerAsync(Repository, vm.Username, vm.SafeRole);
|
|
}
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private async ValueTask DetectLocalChangesAsync()
|
|
{
|
|
using var l = UIHelper.MakeLoading("Refreshing local changes...");
|
|
var ns = await Task.Run(async () =>
|
|
{
|
|
LocalDatabase.LocalAccessor.Refresh();
|
|
return await CalculateFileTreeOfChangesNodeAsync(LocalDatabase.LocalAccessor.Changes.Values);
|
|
});
|
|
|
|
LocalChangeSetRaw.Clear();
|
|
LocalChangeSetRaw.AddRange(ns);
|
|
}
|
|
|
|
[ReactiveCommand]
|
|
private void SelectAllChanges()
|
|
{
|
|
foreach (var n in LocalChangeSetRaw)
|
|
n.Included = true;
|
|
}
|
|
|
|
|
|
[ReactiveCommand]
|
|
private void DeselectAllChanges()
|
|
{
|
|
foreach (var n in LocalChangeSetRaw)
|
|
n.Included = false;
|
|
}
|
|
} |