feat: Finate some details
This commit is contained in:
parent
c80b2759a9
commit
91097940fc
@ -8,6 +8,7 @@
|
|||||||
<entry key="Flawless.Client/Theme/ToggleSwitch.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Theme/ToggleSwitch.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/HelloSetup/LoginPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/HelloSetup/LoginPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/HelloSetup/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/HelloSetup/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/HelloSetup/ServerSetupPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/HelloView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/HelloView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/HelloWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/HelloWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/HomeView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/HomeView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
|||||||
@ -106,6 +106,12 @@ public static class WorkPath
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string FormatPathDirectorySeparator(string path)
|
||||||
|
{
|
||||||
|
return path.Replace(Path.DirectorySeparatorChar, DirectorySeparatorChar)
|
||||||
|
.Replace(Path.AltDirectorySeparatorChar, DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Split work path into path vector.
|
/// Split work path into path vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Avalonia.Controls.Notifications;
|
|
||||||
|
|
||||||
namespace Flawless.Client;
|
|
||||||
|
|
||||||
public static class ErrorGUIHandler
|
|
||||||
{
|
|
||||||
public static void OnError(Exception ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -99,7 +99,7 @@ public class Api : BaseService<Api>
|
|||||||
public bool RequireRefreshToken()
|
public bool RequireRefreshToken()
|
||||||
{
|
{
|
||||||
if (_token.Value == null) return true;
|
if (_token.Value == null) return true;
|
||||||
if (DateTime.UtcNow.AddMinutes(1) > _token.Value.Expiration) return true;
|
if (DateTime.UtcNow > _token.Value.Expiration!.Value.UtcDateTime.AddMinutes(-2)) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,6 +115,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -164,6 +165,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -226,6 +228,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -328,6 +331,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
await DeleteFromDiskAsync(repo);
|
await DeleteFromDiskAsync(repo);
|
||||||
return false;
|
return false;
|
||||||
@ -361,6 +365,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -417,6 +422,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -435,7 +441,6 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
if (manifest == null) return null;
|
if (manifest == null) return null;
|
||||||
|
|
||||||
// Prepare folders
|
// Prepare folders
|
||||||
var path = PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name);
|
|
||||||
var depotsRoot = PathUtility.GetWorkspaceDepotCachePath(repo.OwnerName, repo.Name);
|
var depotsRoot = PathUtility.GetWorkspaceDepotCachePath(repo.OwnerName, repo.Name);
|
||||||
Directory.CreateDirectory(depotsRoot);
|
Directory.CreateDirectory(depotsRoot);
|
||||||
|
|
||||||
@ -464,18 +469,22 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create mapping dictionary
|
// Create mapping dictionary
|
||||||
var mappingDict = downloadedDepots.ToDictionary(i => i.Item1, i => i.Item2!);
|
var streamMap = downloadedDepots.ToDictionary(i => i.Item1, i => i.Item2!);
|
||||||
foreach (var dl in mainDepotLabel)
|
foreach (var dl in mainDepotLabel)
|
||||||
{
|
{
|
||||||
if (mappingDict.ContainsKey(dl.Id)) continue;
|
// If this file is not being opened, open it from file system
|
||||||
var dst = Path.Combine(depotsRoot, dl.Id.ToString());
|
if (!streamMap.ContainsKey(dl.Id))
|
||||||
mappingDict.Add(dl.Id, new FileStream(dst, FileMode.Create));
|
{
|
||||||
|
var dst = Path.Combine(depotsRoot, dl.Id.ToString());
|
||||||
|
streamMap.Add(dl.Id, new FileStream(dst, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RepositoryFileTreeAccessor(mappingDict, manifest.Value);
|
return new RepositoryFileTreeAccessor(streamMap, manifest.Value);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
if (downloadedDepots != null)
|
if (downloadedDepots != null)
|
||||||
foreach (var t in downloadedDepots)
|
foreach (var t in downloadedDepots)
|
||||||
{
|
{
|
||||||
@ -537,6 +546,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -561,6 +571,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -575,6 +586,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -630,8 +642,15 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
if (accessor == null) return null; //todo this is a really fatal issue...
|
if (accessor == null) return null; //todo this is a really fatal issue...
|
||||||
if (localDb.RepoAccessor != null)
|
if (localDb.RepoAccessor != null)
|
||||||
{
|
{
|
||||||
try { await localDb.RepoAccessor.DisposeAsync(); }
|
try
|
||||||
catch (Exception e) { Console.WriteLine(e); }
|
{
|
||||||
|
await localDb.RepoAccessor.DisposeAsync();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Point to newest state.
|
// Point to newest state.
|
||||||
@ -644,6 +663,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -735,6 +755,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Directory.Delete(repoWs, true);
|
Directory.Delete(repoWs, true);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -50,6 +50,7 @@ public partial class UserService : BaseService<UserService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -80,6 +81,7 @@ public partial class UserService : BaseService<UserService>
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
UIHelper.NotifyError(e);
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
96
Flawless.Client/UIHelper.cs
Normal file
96
Flawless.Client/UIHelper.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Controls.Notifications;
|
||||||
|
using Flawless.Client.ViewModels.ModalBox;
|
||||||
|
using Flawless.Client.Views.ModalBox;
|
||||||
|
using Ursa.Controls;
|
||||||
|
using Notification = Ursa.Controls.Notification;
|
||||||
|
using WindowNotificationManager = Ursa.Controls.WindowNotificationManager;
|
||||||
|
|
||||||
|
namespace Flawless.Client;
|
||||||
|
|
||||||
|
public static class UIHelper
|
||||||
|
{
|
||||||
|
|
||||||
|
private static WindowNotificationManager _notificationManager = null!;
|
||||||
|
|
||||||
|
public static WindowNotificationManager Notify
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_notificationManager != null) return _notificationManager!;
|
||||||
|
|
||||||
|
var lf = ((IClassicDesktopStyleApplicationLifetime)App.Current.ApplicationLifetime);
|
||||||
|
if (!WindowNotificationManager.TryGetNotificationManager(lf.MainWindow!, out _notificationManager))
|
||||||
|
throw new Exception("Can not get notification manager");
|
||||||
|
|
||||||
|
_notificationManager!.Position = NotificationPosition.TopCenter;
|
||||||
|
return _notificationManager!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void NotifyError(Exception ex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var content = ex.ToString();
|
||||||
|
if (content.Length > 100) content = content.Substring(0, 100) + "...";
|
||||||
|
var nf = new Notification(ex.GetType().Name, content, NotificationType.Error);
|
||||||
|
Notify.Show(nf);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Can not notify error to users: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NotifyError(string title, string content)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nf = new Notification(title, content, NotificationType.Error);
|
||||||
|
Notify.Show(nf);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Can not notify error to users: {title} - {content}, {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Task<DialogResult> SimpleAskAsync(string content, DialogMode mode = DialogMode.None)
|
||||||
|
{
|
||||||
|
var opt = new OverlayDialogOptions
|
||||||
|
{
|
||||||
|
FullScreen = false,
|
||||||
|
Buttons = DialogButton.YesNo,
|
||||||
|
CanResize = false,
|
||||||
|
CanDragMove = false,
|
||||||
|
IsCloseButtonVisible = true,
|
||||||
|
CanLightDismiss = true,
|
||||||
|
Mode = mode
|
||||||
|
};
|
||||||
|
|
||||||
|
var vm = new SimpleMessageDialogViewModel(content);
|
||||||
|
return OverlayDialog.ShowModal<SimpleMessageDialogView, SimpleMessageDialogViewModel>(vm, AppDefaultValues.HostId, opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task SimpleAlert(string content, DialogMode mode = DialogMode.Error)
|
||||||
|
{
|
||||||
|
var opt = new OverlayDialogOptions
|
||||||
|
{
|
||||||
|
FullScreen = false,
|
||||||
|
Buttons = DialogButton.YesNo,
|
||||||
|
CanResize = false,
|
||||||
|
CanDragMove = false,
|
||||||
|
IsCloseButtonVisible = true,
|
||||||
|
CanLightDismiss = true,
|
||||||
|
Mode = mode
|
||||||
|
};
|
||||||
|
|
||||||
|
var vm = new SimpleMessageDialogViewModel(content);
|
||||||
|
return OverlayDialog.ShowModal<SimpleMessageDialogView, SimpleMessageDialogViewModel>(vm, AppDefaultValues.HostId, opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,8 +20,6 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
[Reactive] private string _username = "cardidi";
|
[Reactive] private string _username = "cardidi";
|
||||||
|
|
||||||
[Reactive] private string _password = "4453A2b33";
|
[Reactive] private string _password = "4453A2b33";
|
||||||
|
|
||||||
[Reactive(SetModifier = AccessModifier.Protected)] private string _issue = String.Empty;
|
|
||||||
|
|
||||||
public IObservable<bool> CanLogin;
|
public IObservable<bool> CanLogin;
|
||||||
|
|
||||||
@ -56,12 +54,12 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
catch (ApiException ex)
|
catch (ApiException ex)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"Login as '{Username}' Failed: {ex.Content}");
|
await Console.Error.WriteLineAsync($"Login as '{Username}' Failed: {ex.Content}");
|
||||||
Issue = ex.Content ?? String.Empty;
|
UIHelper.NotifyError(ex);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"Login as '{Username}' Failed: {ex}");
|
await Console.Error.WriteLineAsync($"Login as '{Username}' Failed: {ex}");
|
||||||
Issue = ex.Message;
|
UIHelper.NotifyError(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"Login as '{Username}' success!");
|
Console.WriteLine($"Login as '{Username}' success!");
|
||||||
|
|||||||
@ -23,8 +23,6 @@ public partial class RegisterPageViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
|
|
||||||
[Reactive] private string _password;
|
[Reactive] private string _password;
|
||||||
|
|
||||||
[Reactive(SetModifier = AccessModifier.Protected)] private string _issue;
|
|
||||||
|
|
||||||
public RegisterPageViewModel(IScreen hostScreen)
|
public RegisterPageViewModel(IScreen hostScreen)
|
||||||
{
|
{
|
||||||
HostScreen = hostScreen;
|
HostScreen = hostScreen;
|
||||||
@ -49,12 +47,12 @@ public partial class RegisterPageViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
catch (ApiException ex)
|
catch (ApiException ex)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex.Content}");
|
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex.Content}");
|
||||||
Issue = ex.Content ?? String.Empty;
|
UIHelper.NotifyError(ex);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex}");
|
await Console.Error.WriteLineAsync($"Register as '{Username}' Failed: {ex}");
|
||||||
Issue = ex.Message;
|
UIHelper.NotifyError(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"Register as '{Username}' success!");
|
Console.WriteLine($"Register as '{Username}' success!");
|
||||||
|
|||||||
@ -10,6 +10,7 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.Models.TreeDataGrid;
|
using Avalonia.Controls.Models.TreeDataGrid;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
|
using Flawless.Abstraction;
|
||||||
using Flawless.Client.Models;
|
using Flawless.Client.Models;
|
||||||
using Flawless.Client.Service;
|
using Flawless.Client.Service;
|
||||||
using Flawless.Core.Modal;
|
using Flawless.Core.Modal;
|
||||||
@ -106,13 +107,14 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
|
|
||||||
public HierarchicalTreeDataGridSource<LocalChangesNode> LocalChange { get; }
|
public HierarchicalTreeDataGridSource<LocalChangesNode> LocalChange { get; }
|
||||||
|
|
||||||
|
|
||||||
public HierarchicalTreeDataGridSource<LocalChangesNode> FileTree { get; }
|
public HierarchicalTreeDataGridSource<LocalChangesNode> FileTree { get; }
|
||||||
|
|
||||||
public FlatTreeDataGridSource<CommitTransitNode> Commits { get; }
|
public FlatTreeDataGridSource<CommitTransitNode> Commits { get; }
|
||||||
|
|
||||||
public ObservableCollection<LocalChangesNode> LocalChangeSetRaw { get; } = new();
|
public ObservableCollection<LocalChangesNode> LocalChangeSetRaw { get; } = new();
|
||||||
|
|
||||||
|
public ObservableCollection<LocalChangesNode> CurrentCommitFileTreeRaw { get; } = new();
|
||||||
|
|
||||||
public UserModel User { get; }
|
public UserModel User { get; }
|
||||||
|
|
||||||
[Reactive] private bool _autoDetectChanges = true;
|
[Reactive] private bool _autoDetectChanges = true;
|
||||||
@ -181,10 +183,49 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
DetectLocalChangesAsyncCommand.Execute();
|
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, ulong>(
|
||||||
|
"Size",
|
||||||
|
n => 0),
|
||||||
|
|
||||||
|
new TextColumn<LocalChangesNode, DateTime?>(
|
||||||
|
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = StartupTasksAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartupTasksAsync()
|
||||||
|
{
|
||||||
|
await DetectLocalChangesAsyncCommand.Execute();
|
||||||
|
await RendererFileTreeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask RendererFileTreeAsync()
|
||||||
|
{
|
||||||
|
if (LocalDatabase.RepoAccessor == null) return;
|
||||||
|
var accessor = LocalDatabase.RepoAccessor;
|
||||||
|
var nodes = await CalculateFileTreeOfChangesNodeAsync(accessor.Select(
|
||||||
|
f => new LocalFileTreeAccessor.ChangeRecord(ChangeType.Add, f)));
|
||||||
|
|
||||||
|
CurrentCommitFileTreeRaw.Clear();
|
||||||
|
CurrentCommitFileTreeRaw.AddRange(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
private void CollectChanges(List<LocalFileTreeAccessor.ChangeRecord> store, IEnumerable<LocalChangesNode> changesNode)
|
private void CollectChanges(List<LocalFileTreeAccessor.ChangeRecord> store, IEnumerable<LocalChangesNode> changesNode)
|
||||||
{
|
{
|
||||||
foreach (var n in changesNode)
|
foreach (var n in changesNode)
|
||||||
@ -201,21 +242,72 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task<List<LocalChangesNode>> CalculateFileTreeOfChangesNodeAsync(IEnumerable<LocalFileTreeAccessor.ChangeRecord> 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 n = LocalChangesNode.FromWorkspaceFile(file);
|
||||||
|
var parentNode = AddParentToMap(file.File.WorkPath);
|
||||||
|
if (parentNode == null) nodes.Add(n);
|
||||||
|
else parentNode.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;
|
||||||
|
|
||||||
|
// 生成当前文件夹,并先找到这个文件夹的直接文件夹
|
||||||
|
node = LocalChangesNode.FromFolder(path);
|
||||||
|
var parent = AddParentToMap(path);
|
||||||
|
folderMap.Add(path, node);
|
||||||
|
if (parent != null) parent.Contents!.Add(node);
|
||||||
|
else nodes.Add(node);
|
||||||
|
|
||||||
|
// 将新建的文件夹告知调用方
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[ReactiveCommand]
|
[ReactiveCommand]
|
||||||
private async Task CommitSelectedChangesAsync()
|
private async Task CommitSelectedChangesAsync()
|
||||||
{
|
{
|
||||||
var changes = new List<LocalFileTreeAccessor.ChangeRecord>();
|
var changes = new List<LocalFileTreeAccessor.ChangeRecord>();
|
||||||
CollectChanges(changes, LocalChangeSetRaw);
|
CollectChanges(changes, LocalChangeSetRaw);
|
||||||
|
|
||||||
if (changes.Count == 0) return;
|
if (changes.Count == 0)
|
||||||
var manifest = await RepositoryService.C.CommitWorkspaceAsBaselineAsync(Repository, changes,
|
{
|
||||||
LocalDatabase.CommitMessage ?? string.Empty);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifest = await RepositoryService.C.CommitWorkspaceAsBaselineAsync(Repository, changes, LocalDatabase.CommitMessage!);
|
||||||
if (manifest == null) return;
|
if (manifest == null) return;
|
||||||
|
|
||||||
LocalDatabase.LocalAccessor.SetBaseline(manifest.Value.FilePaths);
|
LocalDatabase.LocalAccessor.SetBaseline(manifest.Value.FilePaths);
|
||||||
LocalDatabase.CommitMessage = string.Empty;
|
LocalDatabase.CommitMessage = string.Empty;
|
||||||
await DetectLocalChangesAsyncCommand.Execute();
|
await DetectLocalChangesAsyncCommand.Execute();
|
||||||
|
await RendererFileTreeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ReactiveCommand]
|
[ReactiveCommand]
|
||||||
@ -242,37 +334,28 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
|||||||
[ReactiveCommand]
|
[ReactiveCommand]
|
||||||
private async ValueTask DetectLocalChangesAsync()
|
private async ValueTask DetectLocalChangesAsync()
|
||||||
{
|
{
|
||||||
var ns = await Task.Run(() =>
|
var ns = await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
LocalDatabase.LocalAccessor.Refresh();
|
LocalDatabase.LocalAccessor.Refresh();
|
||||||
|
return await CalculateFileTreeOfChangesNodeAsync(LocalDatabase.LocalAccessor.Changes.Values);
|
||||||
// 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.Clear();
|
||||||
LocalChangeSetRaw.AddRange(ns);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -14,8 +14,6 @@ public partial class ServerSetupPageViewModel : RoutableViewModelBase
|
|||||||
|
|
||||||
[Reactive] private string _host = "http://localhost:5256/";
|
[Reactive] private string _host = "http://localhost:5256/";
|
||||||
|
|
||||||
[Reactive(SetModifier = AccessModifier.Protected)] private string? _issue;
|
|
||||||
|
|
||||||
public IObservable<bool> CanSetHost { get; }
|
public IObservable<bool> CanSetHost { get; }
|
||||||
|
|
||||||
public ServerSetupPageViewModel(IScreen hostScreen) : base(hostScreen)
|
public ServerSetupPageViewModel(IScreen hostScreen) : base(hostScreen)
|
||||||
@ -33,19 +31,18 @@ public partial class ServerSetupPageViewModel : RoutableViewModelBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Issue = string.Empty;
|
|
||||||
await Api.C.SetGatewayAsync(Host);
|
await Api.C.SetGatewayAsync(Host);
|
||||||
HostScreen.Router.Navigate.Execute(new LoginPageViewModel(HostScreen));
|
HostScreen.Router.Navigate.Execute(new LoginPageViewModel(HostScreen));
|
||||||
}
|
}
|
||||||
catch (ApiException ex)
|
catch (ApiException ex)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync("Can not connect to server: " + ex.ToString());
|
await Console.Error.WriteLineAsync("Can not connect to server: " + ex.ToString());
|
||||||
Issue = ex.Content;
|
UIHelper.NotifyError(ex);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync("Can not connect to server: " + ex.ToString());
|
await Console.Error.WriteLineAsync("Can not connect to server: " + ex.ToString());
|
||||||
Issue = ex.Message;
|
UIHelper.NotifyError(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,5 @@
|
|||||||
<Button Content="Login" Command="{Binding LoginCommand}"/>
|
<Button Content="Login" Command="{Binding LoginCommand}"/>
|
||||||
<Button Content="Register" Command="{Binding RegisterCommand}"/>
|
<Button Content="Register" Command="{Binding RegisterCommand}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Label Content="{Binding Issue}" Foreground="Red"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -20,6 +20,5 @@
|
|||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="{DynamicResource Semi}">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="{DynamicResource Semi}">
|
||||||
<Button Content="Register" Command="{Binding RegisterCommand}"/>
|
<Button Content="Register" Command="{Binding RegisterCommand}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Label Content="{Binding Issue}" Foreground="Red"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -16,6 +16,5 @@
|
|||||||
<StackPanel Spacing="10" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
<StackPanel Spacing="10" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||||
<TextBox Watermark="Host" Text="{Binding Host, Mode=TwoWay}"/>
|
<TextBox Watermark="Host" Text="{Binding Host, Mode=TwoWay}"/>
|
||||||
<Button Content="Connect" Command="{Binding SetHostCommand}"/>
|
<Button Content="Connect" Command="{Binding SetHostCommand}"/>
|
||||||
<Label Content="{Binding Issue}" Foreground="Red"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -21,5 +21,6 @@
|
|||||||
<Panel>
|
<Panel>
|
||||||
<rxui:RoutedViewHost Router="{Binding Router}"/>
|
<rxui:RoutedViewHost Router="{Binding Router}"/>
|
||||||
<ursa:OverlayDialogHost HostId="Overlay"/>
|
<ursa:OverlayDialogHost HostId="Overlay"/>
|
||||||
|
<ursa:WindowNotificationManager/>
|
||||||
</Panel>
|
</Panel>
|
||||||
</ursa:UrsaWindow>
|
</ursa:UrsaWindow>
|
||||||
@ -10,15 +10,9 @@
|
|||||||
|
|
||||||
<Grid ColumnDefinitions="2*, *">
|
<Grid ColumnDefinitions="2*, *">
|
||||||
<Border Grid.Column="0" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
<Border Grid.Column="0" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||||
<Grid RowDefinitions="Auto, *">
|
<ScrollViewer Grid.Row="1">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8"
|
<SelectableTextBlock Text="{Binding Repository.Description}"/>
|
||||||
IsVisible="{Binding IsOwnerRole}">
|
</ScrollViewer>
|
||||||
<u:IconButton Icon="{StaticResource SemiIconEdit}" Content="Edit"/>
|
|
||||||
</StackPanel>
|
|
||||||
<ScrollViewer Grid.Row="1">
|
|
||||||
<SelectableTextBlock Text="{Binding Repository.Description}"/>
|
|
||||||
</ScrollViewer>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
</Border>
|
||||||
<ScrollViewer Grid.Column="1" Margin="36 20">
|
<ScrollViewer Grid.Column="1" Margin="36 20">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoFileTreePageView">
|
x:Class="Flawless.Client.Views.RepositoryPage.RepoFileTreePageView">
|
||||||
<Grid ColumnDefinitions="2*, *">
|
<Grid ColumnDefinitions="2*, *">
|
||||||
<TreeDataGrid Grid.Column="0">
|
<TreeDataGrid Grid.Column="0" Grid.ColumnSpan="2" Source="{Binding FileTree}">
|
||||||
</TreeDataGrid>
|
</TreeDataGrid>
|
||||||
<!-- <Border Grid.Column="1" Classes="Shadow" Theme="{StaticResource CardBorder}"> -->
|
<!-- <Border Grid.Column="1" Classes="Shadow" Theme="{StaticResource CardBorder}"> -->
|
||||||
<!-- <ScrollViewer> -->
|
<!-- <ScrollViewer> -->
|
||||||
|
|||||||
@ -6,7 +6,26 @@
|
|||||||
x:DataType="vm:RepositoryViewModel"
|
x:DataType="vm:RepositoryViewModel"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoSettingPageView">
|
x:Class="Flawless.Client.Views.RepositoryPage.RepoSettingPageView">
|
||||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
<TabControl TabStripPlacement="Left">
|
||||||
<Label Content="Sit down and wait patience."></Label>
|
<TabItem Header="Members">
|
||||||
</StackPanel>
|
<StackPanel Width="400" HorizontalAlignment="Stretch">
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Statics" IsVisible="{Binding IsDeveloperRole}">
|
||||||
|
<StackPanel Width="400" HorizontalAlignment="Stretch">
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Admin Area" IsVisible="{Binding IsOwnerRole}">
|
||||||
|
<StackPanel Width="400" HorizontalAlignment="Stretch">
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Hooks" IsVisible="{Binding IsOwnerRole}">
|
||||||
|
<StackPanel Width="400" HorizontalAlignment="Stretch">
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Detect"
|
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Detect"
|
||||||
Command="{Binding DetectLocalChangesAsyncCommand}"/>
|
Command="{Binding DetectLocalChangesAsyncCommand}"/>
|
||||||
<u:IconButton Icon="{StaticResource SemiIconDownload}" Content="Pull"/>
|
<u:IconButton Icon="{StaticResource SemiIconDownload}" Content="Pull"/>
|
||||||
|
<u:IconButton Icon="{StaticResource SemiIconCheckList}" Content="Select"/>
|
||||||
|
<u:IconButton Icon="{StaticResource SemiIconList}" Content="Deselect"/>
|
||||||
<!-- <ToggleButton Content="Auto Refresh" IsChecked="{Binding AutoDetectChanges}"/> -->
|
<!-- <ToggleButton Content="Auto Refresh" IsChecked="{Binding AutoDetectChanges}"/> -->
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TreeDataGrid Grid.Row="1" Grid.Column="0" Source="{Binding LocalChange}" CanUserSortColumns="True"/>
|
<TreeDataGrid Grid.Row="1" Grid.Column="0" Source="{Binding LocalChange}" CanUserSortColumns="True"/>
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<TabControl TabStripPlacement="Top" Margin="0 20 0 0">
|
<TabControl TabStripPlacement="Top" Margin="0 20 0 0">
|
||||||
<TabItem>
|
<TabItem>
|
||||||
<TabItem.Header>
|
<TabItem.Header>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||||
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconBox}"/>
|
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconBox}"/>
|
||||||
<Label Content="Dashboard"/>
|
<Label Content="Dashboard"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@ -382,6 +382,7 @@ public class RepositoryInnieController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("create_commit")]
|
[HttpPost("create_commit")]
|
||||||
|
[RequestSizeLimit(long.MaxValue)]
|
||||||
[ProducesResponseType<CommitSuccessResponse>(200)]
|
[ProducesResponseType<CommitSuccessResponse>(200)]
|
||||||
public async Task<IActionResult> CommitAsync(string userName, string repositoryName, [FromForm] FormCommitRequest req)
|
public async Task<IActionResult> CommitAsync(string userName, string repositoryName, [FromForm] FormCommitRequest req)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using Flawless.Server.Services;
|
|||||||
using Flawless.Server.Utility;
|
using Flawless.Server.Utility;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
@ -38,6 +39,14 @@ public static class Program
|
|||||||
builder.WebHost.ConfigureKestrel(opt =>
|
builder.WebHost.ConfigureKestrel(opt =>
|
||||||
{
|
{
|
||||||
opt.Limits.MaxRequestBodySize = long.MaxValue; // As big as possible...
|
opt.Limits.MaxRequestBodySize = long.MaxValue; // As big as possible...
|
||||||
|
opt.Limits.MaxRequestHeaderCount = int.MaxValue; // As big as possible...
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.Configure<FormOptions>(opt =>
|
||||||
|
{
|
||||||
|
opt.MultipartBodyLengthLimit = long.MaxValue;
|
||||||
|
opt.ValueLengthLimit = int.MaxValue;
|
||||||
|
opt.MultipartHeadersLengthLimit = int.MaxValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Api related
|
// Api related
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user