Compare commits
2 Commits
14a4ccd6f6
...
738e8308a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 738e8308a8 | |||
| 11d690d22b |
@ -21,6 +21,7 @@
|
|||||||
<entry key="Flawless.Client/Views/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/RegisterView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/RegisterView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
<entry key="Flawless.Client/Views/RepositoryPage/RepoDashboardPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
<entry key="Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||||
|
|||||||
@ -8,5 +8,6 @@
|
|||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<semi:SemiTheme Locale="zh-cn"/>
|
<semi:SemiTheme Locale="zh-cn"/>
|
||||||
<ursa:SemiTheme Locale="zh-cn"/>
|
<ursa:SemiTheme Locale="zh-cn"/>
|
||||||
|
<StyleInclude Source="avares://Semi.Avalonia.TreeDataGrid/Index.axaml" />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
@ -36,6 +36,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Refit" Version="8.0.0" />
|
<PackageReference Include="Refit" Version="8.0.0" />
|
||||||
<PackageReference Include="Semi.Avalonia" Version="11.2.1.6" />
|
<PackageReference Include="Semi.Avalonia" Version="11.2.1.6" />
|
||||||
|
<PackageReference Include="Semi.Avalonia.TreeDataGrid" Version="11.0.10.2" />
|
||||||
<PackageReference Include="ValueTaskSupplement" Version="1.1.0" />
|
<PackageReference Include="ValueTaskSupplement" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -66,4 +67,8 @@
|
|||||||
<ProjectReference Include="..\Flawless.Abstract\Flawless.Abstract.csproj" />
|
<ProjectReference Include="..\Flawless.Abstract\Flawless.Abstract.csproj" />
|
||||||
<ProjectReference Include="..\Flawless.Core\Flawless.Core.csproj" />
|
<ProjectReference Include="..\Flawless.Core\Flawless.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Converters\" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using Flawless.Client.Service;
|
using Flawless.Client.Service;
|
||||||
using ReactiveUI.SourceGenerators;
|
using ReactiveUI.SourceGenerators;
|
||||||
|
|
||||||
@ -8,14 +9,17 @@ namespace Flawless.Client.Models;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public partial class RepositoryLocalDatabaseModel : ReactiveModel
|
public partial class RepositoryLocalDatabaseModel : ReactiveModel
|
||||||
{
|
{
|
||||||
public required RepositoryModel RootModal { get; init; }
|
[JsonIgnore]
|
||||||
|
public required RepositoryModel RootModal { get; set; }
|
||||||
|
|
||||||
public required LocalFileTreeAccessor LocalAccessor { get; init; }
|
[JsonIgnore]
|
||||||
|
public required LocalFileTreeAccessor LocalAccessor { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public RepositoryFileTreeAccessor? RepoAccessor { get; set; }
|
public RepositoryFileTreeAccessor? RepoAccessor { get; set; }
|
||||||
|
|
||||||
public ObservableCollection<string> CurrentLockedFiles { get; } = new();
|
|
||||||
|
|
||||||
[NonSerialized]
|
|
||||||
[Reactive] private Guid? _currentCommit;
|
[Reactive] private Guid? _currentCommit;
|
||||||
|
|
||||||
|
[Reactive] private string? _commitMessage;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ public partial class RepositoryModel : ReactiveModel
|
|||||||
|
|
||||||
public ObservableCollection<Lock> Locks { get; } = new();
|
public ObservableCollection<Lock> Locks { get; } = new();
|
||||||
|
|
||||||
public enum RepositoryRole
|
public enum RepositoryRole : byte
|
||||||
{
|
{
|
||||||
Guest = 0,
|
Guest = 0,
|
||||||
Reporter = 1,
|
Reporter = 1,
|
||||||
|
|||||||
@ -1,6 +1,33 @@
|
|||||||
namespace Flawless.Client.Models;
|
using System;
|
||||||
|
using ReactiveUI.SourceGenerators;
|
||||||
|
|
||||||
public record UserModel : ReactiveRecordModel
|
namespace Flawless.Client.Models;
|
||||||
|
|
||||||
|
public partial class UserModel : ReactiveModel
|
||||||
{
|
{
|
||||||
|
public enum SexType : byte
|
||||||
|
{
|
||||||
|
Unset = 0,
|
||||||
|
Male = 1,
|
||||||
|
Female = 2,
|
||||||
|
WalmartPlasticBag = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
[Reactive] private string _username;
|
||||||
|
|
||||||
|
[Reactive] private string _nickname;
|
||||||
|
|
||||||
|
[Reactive] private string _bio;
|
||||||
|
|
||||||
|
[Reactive] private SexType _sex;
|
||||||
|
|
||||||
|
[Reactive] private bool _emailIsVisible;
|
||||||
|
|
||||||
|
[Reactive] private bool _canEdit;
|
||||||
|
|
||||||
|
[Reactive] private string _email;
|
||||||
|
|
||||||
|
[Reactive] private string _phoneNumber;
|
||||||
|
|
||||||
|
[Reactive] private DateTime _joinDate;
|
||||||
}
|
}
|
||||||
@ -36,6 +36,8 @@ public class Api : BaseService<Api>
|
|||||||
|
|
||||||
public IObservable<TokenInfo?> Token => _token;
|
public IObservable<TokenInfo?> Token => _token;
|
||||||
|
|
||||||
|
public IReactiveProperty<string?> Username => _username;
|
||||||
|
|
||||||
private readonly ReactiveProperty<bool> _isLoggedIn = new(false);
|
private readonly ReactiveProperty<bool> _isLoggedIn = new(false);
|
||||||
|
|
||||||
private readonly ReactiveProperty<string?> _serverUrl = new(string.Empty);
|
private readonly ReactiveProperty<string?> _serverUrl = new(string.Empty);
|
||||||
@ -44,6 +46,8 @@ public class Api : BaseService<Api>
|
|||||||
|
|
||||||
private readonly ReactiveProperty<TokenInfo?> _token = new(null);
|
private readonly ReactiveProperty<TokenInfo?> _token = new(null);
|
||||||
|
|
||||||
|
private readonly ReactiveProperty<string?> _username = new(string.Empty);
|
||||||
|
|
||||||
#region GatewayConfig
|
#region GatewayConfig
|
||||||
|
|
||||||
private IFlawlessServer? _gateway;
|
private IFlawlessServer? _gateway;
|
||||||
@ -55,6 +59,7 @@ public class Api : BaseService<Api>
|
|||||||
public void ClearGateway()
|
public void ClearGateway()
|
||||||
{
|
{
|
||||||
_gateway = null;
|
_gateway = null;
|
||||||
|
_username.Value = null;
|
||||||
_isLoggedIn.Value = false;
|
_isLoggedIn.Value = false;
|
||||||
_status.Value = null;
|
_status.Value = null;
|
||||||
_serverUrl.Value = null;
|
_serverUrl.Value = null;
|
||||||
@ -82,7 +87,9 @@ public class Api : BaseService<Api>
|
|||||||
Password = password
|
Password = password
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_username.Value = username;
|
||||||
_isLoggedIn.Value = true;
|
_isLoggedIn.Value = true;
|
||||||
|
await UserService.C.DownloadUserInfoAsync(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
namespace Flawless.Client.Service;
|
using Flawless.Client.ViewModels;
|
||||||
|
|
||||||
|
namespace Flawless.Client.Service;
|
||||||
|
|
||||||
public class BaseService<TService> where TService : class, new()
|
public class BaseService<TService> where TService : class, new()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -11,6 +11,28 @@ namespace Flawless.Client.Service;
|
|||||||
|
|
||||||
public class LocalFileTreeAccessor
|
public class LocalFileTreeAccessor
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public enum ChangeType
|
||||||
|
{
|
||||||
|
Folder = 0,
|
||||||
|
Add,
|
||||||
|
Remove,
|
||||||
|
Modify
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ChangeRecord
|
||||||
|
{
|
||||||
|
public ChangeType Type { get; }
|
||||||
|
|
||||||
|
public WorkspaceFile File { get; }
|
||||||
|
|
||||||
|
public ChangeRecord(ChangeType type, WorkspaceFile file)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
File = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly string[] IgnoredDirectories =
|
private static readonly string[] IgnoredDirectories =
|
||||||
{
|
{
|
||||||
AppDefaultValues.RepoLocalStorageManagerFolder
|
AppDefaultValues.RepoLocalStorageManagerFolder
|
||||||
@ -22,11 +44,7 @@ public class LocalFileTreeAccessor
|
|||||||
|
|
||||||
private IReadOnlyDictionary<string, WorkspaceFile> _baseline;
|
private IReadOnlyDictionary<string, WorkspaceFile> _baseline;
|
||||||
|
|
||||||
private readonly Dictionary<string, WorkspaceFile> _newFiles = new();
|
private Dictionary<string, ChangeRecord> _difference;
|
||||||
|
|
||||||
private readonly Dictionary<string, WorkspaceFile> _deleteFiles = new();
|
|
||||||
|
|
||||||
private readonly Dictionary<string, WorkspaceFile> _modifyFiles = new();
|
|
||||||
|
|
||||||
private object _optLock = new();
|
private object _optLock = new();
|
||||||
|
|
||||||
@ -34,16 +52,12 @@ public class LocalFileTreeAccessor
|
|||||||
|
|
||||||
public IReadOnlyDictionary<string, WorkspaceFile> BaselineFiles => _baseline;
|
public IReadOnlyDictionary<string, WorkspaceFile> BaselineFiles => _baseline;
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, WorkspaceFile> NewFiles => _newFiles;
|
public IReadOnlyDictionary<string, ChangeRecord> Changes => _difference;
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, WorkspaceFile> DeletedFiles => _deleteFiles;
|
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, WorkspaceFile> ModifiedFiles => _modifyFiles;
|
|
||||||
|
|
||||||
|
|
||||||
public LocalFileTreeAccessor(RepositoryModel repo, IEnumerable<WorkspaceFile> baselines)
|
public LocalFileTreeAccessor(RepositoryModel repo, IEnumerable<WorkspaceFile> baselines)
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
|
_difference = new Dictionary<string, ChangeRecord>();
|
||||||
_baseline = baselines.ToImmutableDictionary(b => b.WorkPath);
|
_baseline = baselines.ToImmutableDictionary(b => b.WorkPath);
|
||||||
_rootDirectory = PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name);
|
_rootDirectory = PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name);
|
||||||
}
|
}
|
||||||
@ -53,9 +67,7 @@ public class LocalFileTreeAccessor
|
|||||||
lock (_optLock)
|
lock (_optLock)
|
||||||
{
|
{
|
||||||
_baseline = baselines.ToImmutableDictionary(b => b.WorkPath);
|
_baseline = baselines.ToImmutableDictionary(b => b.WorkPath);
|
||||||
_newFiles.Clear();
|
_difference.Clear();
|
||||||
_deleteFiles.Clear();
|
|
||||||
_modifyFiles.Clear();
|
|
||||||
|
|
||||||
RefreshInternal();
|
RefreshInternal();
|
||||||
}
|
}
|
||||||
@ -65,9 +77,7 @@ public class LocalFileTreeAccessor
|
|||||||
{
|
{
|
||||||
lock (_optLock)
|
lock (_optLock)
|
||||||
{
|
{
|
||||||
_newFiles.Clear();
|
_difference.Clear();
|
||||||
_deleteFiles.Clear();
|
|
||||||
_modifyFiles.Clear();
|
|
||||||
|
|
||||||
RefreshInternal();
|
RefreshInternal();
|
||||||
}
|
}
|
||||||
@ -91,8 +101,8 @@ public class LocalFileTreeAccessor
|
|||||||
var news = currentFiles.Values.Where(v => !_baseline.ContainsKey(v.WorkPath));
|
var news = currentFiles.Values.Where(v => !_baseline.ContainsKey(v.WorkPath));
|
||||||
var removed = _baseline.Values.Where(v => !currentFiles.ContainsKey(v.WorkPath));
|
var removed = _baseline.Values.Where(v => !currentFiles.ContainsKey(v.WorkPath));
|
||||||
|
|
||||||
foreach (var f in changes) _modifyFiles.Add(f.WorkPath, f);
|
foreach (var f in changes) _difference.Add(f.WorkPath, new ChangeRecord(ChangeType.Modify, f));
|
||||||
foreach (var f in news) _newFiles.Add(f.WorkPath, f);
|
foreach (var f in news) _difference.Add(f.WorkPath, new ChangeRecord(ChangeType.Add, f));
|
||||||
foreach (var f in removed) _deleteFiles.Add(f.WorkPath, f);
|
foreach (var f in removed) _difference.Add(f.WorkPath, new ChangeRecord(ChangeType.Remove, f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,8 +65,11 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
{
|
{
|
||||||
// Use existed target
|
// Use existed target
|
||||||
using var readFs = new StreamReader(new FileStream(dbPath, FileMode.Open));
|
using var readFs = new StreamReader(new FileStream(dbPath, FileMode.Open));
|
||||||
localRepo = JsonSerializer.CreateDefault().Deserialize(readFs, typeof(RepositoryLocalDatabaseModel))
|
localRepo = (JsonSerializer.CreateDefault().Deserialize(readFs, typeof(RepositoryLocalDatabaseModel))
|
||||||
as RepositoryLocalDatabaseModel; // todo add broken test.
|
as RepositoryLocalDatabaseModel)!; // todo add broken test.
|
||||||
|
|
||||||
|
localRepo.RootModal = repo;
|
||||||
|
localRepo.LocalAccessor = new LocalFileTreeAccessor(repo, []);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -185,7 +188,9 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
var isFolderExists = Directory.Exists(PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name));
|
var isFolderExists = Directory.Exists(PathUtility.GetWorkspacePath(repo.OwnerName, repo.Name));
|
||||||
var isDbFileExists = File.Exists(PathUtility.GetWorkspaceDbPath(repo.OwnerName, repo.Name));
|
var isDbFileExists = File.Exists(PathUtility.GetWorkspaceDbPath(repo.OwnerName, repo.Name));
|
||||||
|
|
||||||
return isFolderExists && isDbFileExists;
|
repo.IsDownloaded = isFolderExists && isDbFileExists;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<bool> UpdateMembersFromServerAsync(RepositoryModel repo)
|
public async ValueTask<bool> UpdateMembersFromServerAsync(RepositoryModel repo)
|
||||||
@ -282,10 +287,6 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
// Create basic structures.
|
// Create basic structures.
|
||||||
if (!TryCreateRepositoryBaseStorageStructure(repo)) return false;
|
if (!TryCreateRepositoryBaseStorageStructure(repo)) return false;
|
||||||
|
|
||||||
if (!await UpdateCommitsFromServerAsync(repo)) return false;
|
|
||||||
var peekCommit = repo.Commits.MaxBy(sl => sl.CommittedOn);
|
|
||||||
if (peekCommit == null) return false; // Should not use this function!
|
|
||||||
|
|
||||||
repo.IsDownloaded = true;
|
repo.IsDownloaded = true;
|
||||||
_openedRepos.Add(repo);
|
_openedRepos.Add(repo);
|
||||||
return true;
|
return true;
|
||||||
@ -351,7 +352,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rsp = await api.Gateway.PeekCommit(repo.Name, repo.OwnerName);
|
var rsp = await api.Gateway.PeekCommit(repo.OwnerName, repo.Name);
|
||||||
var emptyRepo = repo.Commits.Count == 0;
|
var emptyRepo = repo.Commits.Count == 0;
|
||||||
|
|
||||||
// If they both empty
|
// If they both empty
|
||||||
@ -378,7 +379,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rsp = await api.Gateway.ListCommit(repo.Name, repo.OwnerName);
|
var rsp = await api.Gateway.ListCommit(repo.OwnerName, repo.Name);
|
||||||
|
|
||||||
// Update existed
|
// Update existed
|
||||||
var dict = rsp.Result.ToDictionary(m => m.Id);
|
var dict = rsp.Result.ToDictionary(m => m.Id);
|
||||||
@ -575,6 +576,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repo.IsDownloaded = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
90
Flawless.Client/Service/UserService.cs
Normal file
90
Flawless.Client/Service/UserService.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Flawless.Client.Models;
|
||||||
|
using ReactiveUI.SourceGenerators;
|
||||||
|
|
||||||
|
namespace Flawless.Client.Service;
|
||||||
|
|
||||||
|
public partial class UserService : BaseService<UserService>
|
||||||
|
{
|
||||||
|
private Dictionary<string, UserModel> _cachedUsers = new();
|
||||||
|
|
||||||
|
public bool IsUserCached(string username)
|
||||||
|
=> _cachedUsers.ContainsKey(username);
|
||||||
|
|
||||||
|
public UserModel? GetUserInfoAsync(string username)
|
||||||
|
=> _cachedUsers.ContainsKey(username) ? _cachedUsers[username] : null;
|
||||||
|
|
||||||
|
public async ValueTask<UserModel?> GetOrDownloadUserInfoAsync(string username)
|
||||||
|
{
|
||||||
|
if (_cachedUsers.TryGetValue(username, out var userModel)) return userModel;
|
||||||
|
return await DownloadUserInfoAsync(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<UserModel?> DownloadUserInfoAsync(string username)
|
||||||
|
{
|
||||||
|
var api = Api.C;
|
||||||
|
UserModel user;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
||||||
|
{
|
||||||
|
api.ClearGateway();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = await api.Gateway.GetInfo(username);
|
||||||
|
user = new UserModel();
|
||||||
|
|
||||||
|
user.Username = info.Username;
|
||||||
|
user.Nickname = info.NickName;
|
||||||
|
user.Bio = info.Bio;
|
||||||
|
user.Sex = (UserModel.SexType) info.Gender;
|
||||||
|
user.EmailIsVisible = info.PublicEmail ?? false;
|
||||||
|
user.CanEdit = info.Authorized;
|
||||||
|
user.PhoneNumber = info.Phone;
|
||||||
|
user.Email = info.Email;
|
||||||
|
|
||||||
|
_cachedUsers.Add(username, user);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> RefreshUserInfoAsync(UserModel user)
|
||||||
|
{
|
||||||
|
var api = Api.C;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
||||||
|
{
|
||||||
|
api.ClearGateway();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = await api.Gateway.GetInfo(user.Username);
|
||||||
|
|
||||||
|
user.Nickname = info.NickName;
|
||||||
|
user.Bio = info.Bio;
|
||||||
|
user.Sex = (UserModel.SexType) info.Gender;
|
||||||
|
user.EmailIsVisible = info.PublicEmail ?? false;
|
||||||
|
user.CanEdit = info.Authorized;
|
||||||
|
user.PhoneNumber = info.Phone;
|
||||||
|
user.Email = info.Email;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -30,6 +30,8 @@ public partial class HomeViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
HostScreen = hostScreen;
|
HostScreen = hostScreen;
|
||||||
Api.C.ServerUrl.SubscribeOn(AvaloniaScheduler.Instance)
|
Api.C.ServerUrl.SubscribeOn(AvaloniaScheduler.Instance)
|
||||||
.Subscribe(v => ServerFriendlyName = v ?? "Unknown Server");
|
.Subscribe(v => ServerFriendlyName = v ?? "Unknown Server");
|
||||||
|
|
||||||
|
RefreshRepositoriesCommand.Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ReactiveCommand]
|
[ReactiveCommand]
|
||||||
@ -46,7 +48,7 @@ public partial class HomeViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
var opt = new OverlayDialogOptions
|
var opt = new OverlayDialogOptions
|
||||||
{
|
{
|
||||||
FullScreen = false,
|
FullScreen = false,
|
||||||
Buttons = DialogButton.OK,
|
Buttons = DialogButton.OKCancel,
|
||||||
CanResize = false,
|
CanResize = false,
|
||||||
CanDragMove = false,
|
CanDragMove = false,
|
||||||
IsCloseButtonVisible = true,
|
IsCloseButtonVisible = true,
|
||||||
@ -87,8 +89,26 @@ public partial class HomeViewModel : ViewModelBase, IRoutableViewModel
|
|||||||
private async Task DeleteRepositoryAsync()
|
private async Task DeleteRepositoryAsync()
|
||||||
{
|
{
|
||||||
if (_selectedRepository == null) return;
|
if (_selectedRepository == null) return;
|
||||||
if (await RepositoryService.C.DeleteFromDiskAsync(_selectedRepository))
|
var opt = new OverlayDialogOptions
|
||||||
{
|
{
|
||||||
|
FullScreen = false,
|
||||||
|
Buttons = DialogButton.YesNo,
|
||||||
|
CanResize = false,
|
||||||
|
CanDragMove = false,
|
||||||
|
IsCloseButtonVisible = true,
|
||||||
|
CanLightDismiss = true,
|
||||||
|
Mode = DialogMode.Question
|
||||||
|
};
|
||||||
|
|
||||||
|
var vm = new SimpleMessageDialogViewModel(
|
||||||
|
$"Do you really want to delete a CLONE of repository {_selectedRepository.StandaloneName} ?");
|
||||||
|
|
||||||
|
var mr = await OverlayDialog
|
||||||
|
.ShowModal<SimpleMessageDialogView, SimpleMessageDialogViewModel>(vm, AppDefaultValues.HostId, opt);
|
||||||
|
|
||||||
|
if (mr == DialogResult.Yes)
|
||||||
|
{
|
||||||
|
if (await RepositoryService.C.DeleteFromDiskAsync(_selectedRepository))
|
||||||
await RepositoryService.C.UpdateDownloadedStatusFromDiskAsync(_selectedRepository);
|
await RepositoryService.C.UpdateDownloadedStatusFromDiskAsync(_selectedRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,191 @@
|
|||||||
using System.Reactive.Linq;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.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.Models;
|
||||||
using Flawless.Client.Service;
|
using Flawless.Client.Service;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.SourceGenerators;
|
using ReactiveUI.SourceGenerators;
|
||||||
|
using ChangeType = Flawless.Client.Service.LocalFileTreeAccessor.ChangeType;
|
||||||
|
|
||||||
namespace Flawless.Client.ViewModels;
|
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 partial class RepositoryViewModel : RoutableViewModelBase
|
public partial class RepositoryViewModel : RoutableViewModelBase
|
||||||
{
|
{
|
||||||
public RepositoryModel Repository { get; }
|
public RepositoryModel Repository { get; }
|
||||||
|
|
||||||
|
public RepositoryLocalDatabaseModel LocalDatabase { get; }
|
||||||
|
|
||||||
|
public HierarchicalTreeDataGridSource<LocalChangesNode> LocalChange { 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
|
||||||
|
LocalChangeSetRaw.Add(new LocalChangesNode
|
||||||
|
{
|
||||||
|
Type = "Add",
|
||||||
|
FullPath = "test.md",
|
||||||
|
ModifiedTime = DateTime.Now,
|
||||||
|
});
|
||||||
|
LocalChange = new HierarchicalTreeDataGridSource<LocalChangesNode>(LocalChangeSetRaw)
|
||||||
|
{
|
||||||
|
Columns =
|
||||||
|
{
|
||||||
|
new CheckBoxColumn<LocalChangesNode>(
|
||||||
|
string.Empty, n => n.Included, (n, v) => n.Included = v),
|
||||||
|
|
||||||
|
new TextColumn<LocalChangesNode, string>(
|
||||||
|
"Change",
|
||||||
|
n => n.Contents != null ? String.Empty : n.Type),
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Do refresh when entered
|
||||||
|
// DetectLocalChangesAsyncCommand.Execute();
|
||||||
|
}
|
||||||
|
|
||||||
[ReactiveCommand]
|
[ReactiveCommand]
|
||||||
private async Task GoBackAsync()
|
private async Task CloseRepositoryAsync()
|
||||||
{
|
{
|
||||||
await RepositoryService.C.CloseRepositoryAsync(Repository);
|
await RepositoryService.C.CloseRepositoryAsync(Repository);
|
||||||
await HostScreen.Router.NavigateBack.Execute();
|
await HostScreen.Router.NavigateBack.Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RepositoryViewModel(RepositoryModel repo, IScreen hostScreen) : base(hostScreen)
|
[ReactiveCommand]
|
||||||
|
private void RefreshRepositoryRoleInfo()
|
||||||
{
|
{
|
||||||
Repository = repo;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@
|
|||||||
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconUser}"/>
|
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconUser}"/>
|
||||||
<Label Content="{Binding SelectedRepository.OwnerName, FallbackValue='Owner'}"/>
|
<Label Content="{Binding SelectedRepository.OwnerName, FallbackValue='Owner'}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ScrollViewer IsVisible="{Binding SelectedRepository}">
|
<ScrollViewer IsVisible="{Binding SelectedRepository, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<StackPanel Orientation="Vertical" Spacing="8">
|
<StackPanel Orientation="Vertical" Spacing="8">
|
||||||
<Label Content="{Binding SelectedRepository.Description, FallbackValue='Description as below.'}"/>
|
<Label Content="{Binding SelectedRepository.Description, FallbackValue='Description as below.'}"/>
|
||||||
<u:Divider Content="Recent Activities"/>
|
<u:Divider Content="Recent Activities"/>
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
|
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.RepoCommitPageView">
|
x:Class="Flawless.Client.Views.RepositoryPage.RepoCommitPageView">
|
||||||
<Grid ColumnDefinitions="2*, *">
|
<Grid ColumnDefinitions="2*, *">
|
||||||
|
|||||||
@ -3,25 +3,29 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:u="https://irihi.tech/ursa"
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
|
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.RepoDashboardPageView">
|
x:Class="Flawless.Client.Views.RepositoryPage.RepoDashboardPageView">
|
||||||
|
|
||||||
<Grid ColumnDefinitions="2*, *">
|
<Grid ColumnDefinitions="2*, *">
|
||||||
<Border Grid.Column="0" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
<Border Grid.Column="0" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||||
<StackPanel>
|
<Grid RowDefinitions="Auto, *">
|
||||||
<Label Content="No Readme File Existed"/>
|
<StackPanel Orientation="Horizontal" Spacing="8"
|
||||||
<!-- <md:MarkdownScrollViewer Markdown="## Hello World!!"/> -->
|
IsVisible="{Binding IsOwnerRole}">
|
||||||
|
<u:IconButton Icon="{StaticResource SemiIconEdit}" Content="Edit"/>
|
||||||
</StackPanel>
|
</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">
|
||||||
<u:Timeline
|
<u:Timeline
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Mode="Left">
|
Mode="Left">
|
||||||
<u:TimelineItem Header="New Commit"/>
|
|
||||||
<u:TimelineItem/>
|
|
||||||
<u:TimelineItem/>
|
|
||||||
<u:TimelineItem/>
|
|
||||||
</u:Timeline>
|
</u:Timeline>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
|
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.RepoFileTreePageView">
|
x:Class="Flawless.Client.Views.RepositoryPage.RepoFileTreePageView">
|
||||||
<Grid ColumnDefinitions="2*, *">
|
<Grid ColumnDefinitions="2*, *">
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
|
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.RepoIssuePageView">
|
x:Class="Flawless.Client.Views.RepositoryPage.RepoIssuePageView">
|
||||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
|
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">
|
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||||
|
|||||||
@ -2,49 +2,33 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
|
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.RepoWorkspacePageView">
|
x:Class="Flawless.Client.Views.RepositoryPage.RepoWorkspacePageView">
|
||||||
<DockPanel>
|
<Grid ColumnDefinitions="3*, 10, 2*" RowDefinitions="Auto, *">
|
||||||
<StackPanel DockPanel.Dock="Bottom" Spacing="6" Margin="8">
|
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Spacing="8" Margin="6" Orientation="Horizontal">
|
||||||
<StackPanel Orientation="Horizontal">
|
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Detect"
|
||||||
<Label Content="Commit Message"/>
|
Command="{Binding DetectLocalChangesAsyncCommand}"/>
|
||||||
|
<u:IconButton Icon="{StaticResource SemiIconDownload}" Content="Pull"/>
|
||||||
|
<ToggleButton Content="Auto Refresh" IsChecked="{Binding AutoDetectChanges}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TextBox Height="80"/>
|
<TreeDataGrid Grid.Row="1" Grid.Column="0" Source="{Binding LocalChange}" CanUserSortColumns="True"/>
|
||||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
<Border Grid.Row="1" Grid.Column="2" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||||
|
<Grid RowDefinitions="*, Auto">
|
||||||
</StackPanel>
|
<u:Form HorizontalAlignment="Stretch">
|
||||||
<Grid ColumnDefinitions="Auto, *, Auto">
|
<TextBox Text="{Binding LocalDatabase.CommitMessage}"
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left">
|
Classes="TextArea" MaxHeight="300" Watermark="Description of this commit"/>
|
||||||
<Button Content="Sync"></Button>
|
<SplitButton HorizontalAlignment="Stretch" Content="Commit">
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
|
||||||
<SplitButton Content="Commit">
|
|
||||||
<SplitButton.Flyout>
|
<SplitButton.Flyout>
|
||||||
<MenuFlyout Placement="Top">
|
<MenuFlyout Placement="TopEdgeAlignedRight">
|
||||||
<MenuItem Header="Lock"/>
|
<MenuItem Header="Force Commit as Baseline"/>
|
||||||
</MenuFlyout>
|
</MenuFlyout>
|
||||||
</SplitButton.Flyout>
|
</SplitButton.Flyout>
|
||||||
</SplitButton>
|
</SplitButton>
|
||||||
</StackPanel>
|
</u:Form>
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
<Border Classes="Shadow" Theme="{StaticResource CardBorder}">
|
|
||||||
<Grid ColumnDefinitions="*, 8, *">
|
|
||||||
<StackPanel Grid.Column="0" Orientation="Vertical" Spacing="8">
|
|
||||||
<Grid RowDefinitions="Auto, Auto">
|
|
||||||
<Label Grid.Row="0" FontSize="12" Content="Changes" HorizontalAlignment="Left"/>
|
|
||||||
<TreeDataGrid Grid.Row="1">
|
|
||||||
</TreeDataGrid>
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Column="2" Orientation="Vertical" Spacing="8">
|
|
||||||
<Grid RowDefinitions="Auto, Auto">
|
|
||||||
<Label Grid.Row="0" FontSize="12" Content="Ready" HorizontalAlignment="Right"/>
|
|
||||||
<TreeDataGrid Grid.Row="1">
|
|
||||||
</TreeDataGrid>
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
xmlns:u="https://irihi.tech/ursa"
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
xmlns:semi="https://irihi.tech/semi"
|
xmlns:semi="https://irihi.tech/semi"
|
||||||
xmlns:vm="using:Flawless.Client.ViewModels"
|
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||||
xmlns:page="using:Flawless.Client.Views.RepositoryPage"
|
|
||||||
x:DataType="vm:RepositoryViewModel"
|
x:DataType="vm:RepositoryViewModel"
|
||||||
|
xmlns:page="using:Flawless.Client.Views.RepositoryPage"
|
||||||
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
||||||
x:Class="Flawless.Client.Views.RepositoryView">
|
x:Class="Flawless.Client.Views.RepositoryView">
|
||||||
|
|
||||||
@ -14,9 +14,9 @@
|
|||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
|
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
|
||||||
<u:IconButton Height="4" Icon="{StaticResource SemiIconArrowLeft}"
|
<u:IconButton Height="4" Icon="{StaticResource SemiIconArrowLeft}"
|
||||||
Command="{Binding GoBackCommand}" Content="All Repositories"/>
|
Command="{Binding GoBackCommand}" Content="All Repositories"/>
|
||||||
<Label FontWeight="400" FontSize="28" Content="Name of Repository"/>
|
<Label FontWeight="400" FontSize="28" Content="{Binding Repository.StandaloneName}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TabControl TabStripPlacement="Top" Margin="0 20">
|
<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">
|
||||||
@ -24,16 +24,16 @@
|
|||||||
<Label Content="Dashboard"/>
|
<Label Content="Dashboard"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<page:RepoDashboardPageView Margin="18" DataContext="{Binding $self}"/>
|
<page:RepoDashboardPageView Margin="18"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem>
|
<TabItem IsVisible="{Binding IsDeveloperRole}">
|
||||||
<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 SemiIconSourceControl}"/>
|
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconSourceControl}"/>
|
||||||
<Label Content="Workspace"/>
|
<Label Content="Workspace"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<page:RepoWorkspacePageView Margin="18" DataContext="{Binding $self}"/>
|
<page:RepoWorkspacePageView Margin="18"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem>
|
<TabItem>
|
||||||
<TabItem.Header>
|
<TabItem.Header>
|
||||||
@ -42,25 +42,25 @@
|
|||||||
<Label Content="File Tree"/>
|
<Label Content="File Tree"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<page:RepoFileTreePageView Margin="18" DataContext="{Binding $self}"/>
|
<page:RepoFileTreePageView Margin="18"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<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 SemiIconBrackets}"/>
|
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconBrackets}"/>
|
||||||
<Label Content="Commit"/>
|
<Label Content="Commit History"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<page:RepoCommitPageView Margin="18" DataContext="{Binding $self}"/>
|
<page:RepoCommitPageView Margin="18"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem>
|
<TabItem IsVisible="{Binding IsReporterRole}">
|
||||||
<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 SemiIconConnectionPoint1}"/>
|
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconConnectionPoint1}"/>
|
||||||
<Label Content="Issue"/>
|
<Label Content="Issue"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<page:RepoIssuePageView Margin="18" DataContext="{Binding $self}"/>
|
<page:RepoIssuePageView Margin="18"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem>
|
<TabItem>
|
||||||
<TabItem.Header>
|
<TabItem.Header>
|
||||||
@ -69,7 +69,7 @@
|
|||||||
<Label Content="Setting"/>
|
<Label Content="Setting"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<page:RepoSettingPageView Margin="18" DataContext="{Binding $self}"/>
|
<page:RepoSettingPageView Margin="18"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Flawless.Client.ViewModels;
|
||||||
|
using Ursa.ReactiveUIExtension;
|
||||||
|
|
||||||
namespace Flawless.Client.Views;
|
namespace Flawless.Client.Views;
|
||||||
|
|
||||||
public partial class RepositoryView : UserControl
|
public partial class RepositoryView : ReactiveUrsaView<RepositoryViewModel>
|
||||||
{
|
{
|
||||||
public RepositoryView()
|
public RepositoryView()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -21,8 +21,7 @@ namespace Flawless.Server.Controllers;
|
|||||||
public class RepositoryInnieController(
|
public class RepositoryInnieController(
|
||||||
UserManager<AppUser> userManager,
|
UserManager<AppUser> userManager,
|
||||||
AppDbContext dbContext,
|
AppDbContext dbContext,
|
||||||
PathTransformer transformer,
|
PathTransformer transformer) : ControllerBase
|
||||||
JsonSerializerOptions serializerOpt) : ControllerBase
|
|
||||||
{
|
{
|
||||||
|
|
||||||
public class FormCommitRequest : CommitRequest
|
public class FormCommitRequest : CommitRequest
|
||||||
@ -212,8 +211,9 @@ public class RepositoryInnieController(
|
|||||||
string userName, string repositoryName, AppUser user, RepositoryRole role)
|
string userName, string repositoryName, AppUser user, RepositoryRole role)
|
||||||
{
|
{
|
||||||
var rp = await dbContext.Repositories
|
var rp = await dbContext.Repositories
|
||||||
.Include(r => r.Owner)
|
|
||||||
.Include(r => r.Members)
|
.Include(r => r.Members)
|
||||||
|
.ThenInclude(m => m.User)
|
||||||
|
.Include(r => r.Owner)
|
||||||
.FirstOrDefaultAsync(r => r.Owner.UserName == userName && r.Name == repositoryName);
|
.FirstOrDefaultAsync(r => r.Owner.UserName == userName && r.Name == repositoryName);
|
||||||
|
|
||||||
if (rp == null) return NotFound(new FailedResponse($"Could not find repository {userName}:{repositoryName}"));
|
if (rp == null) return NotFound(new FailedResponse($"Could not find repository {userName}:{repositoryName}"));
|
||||||
@ -508,7 +508,7 @@ public class RepositoryInnieController(
|
|||||||
};
|
};
|
||||||
|
|
||||||
await using (var manifestStream = System.IO.File.Create(manifestPath))
|
await using (var manifestStream = System.IO.File.Create(manifestPath))
|
||||||
await JsonSerializer.SerializeAsync(manifestStream, manifest, serializerOpt);
|
await JsonSerializer.SerializeAsync(manifestStream, manifest);
|
||||||
|
|
||||||
// Create commit info
|
// Create commit info
|
||||||
var commit = new RepositoryCommit
|
var commit = new RepositoryCommit
|
||||||
|
|||||||
@ -39,9 +39,10 @@ public class RepositoryOutieController(AppDbContext dbContext, UserManager<AppUs
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("repo_create")]
|
[HttpPost("repo_create")]
|
||||||
public async Task<ActionResult<RepositoryInfoResponse>> CreateRepositoryAsync([FromQuery] string repositoryName,[FromQuery] string description)
|
public async Task<ActionResult<RepositoryInfoResponse>> CreateRepositoryAsync([FromQuery] string repositoryName, [FromQuery] string description)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
|
repositoryName = repositoryName.Trim().Replace(' ', '_');
|
||||||
|
if (repositoryName.Length <= 3)
|
||||||
return BadRequest(new FailedResponse("Repository name is empty or too short!"));
|
return BadRequest(new FailedResponse("Repository name is empty or too short!"));
|
||||||
|
|
||||||
var u = (await userManager.GetUserAsync(HttpContext.User))!;
|
var u = (await userManager.GetUserAsync(HttpContext.User))!;
|
||||||
@ -61,7 +62,7 @@ public class RepositoryOutieController(AppDbContext dbContext, UserManager<AppUs
|
|||||||
{
|
{
|
||||||
RepositoryName = repositoryName,
|
RepositoryName = repositoryName,
|
||||||
OwnerUsername = u.UserName!,
|
OwnerUsername = u.UserName!,
|
||||||
Description = string.Empty,
|
Description = description,
|
||||||
IsArchived = false,
|
IsArchived = false,
|
||||||
Role = RepositoryRole.Owner
|
Role = RepositoryRole.Owner
|
||||||
});
|
});
|
||||||
|
|||||||
@ -37,11 +37,7 @@ public static class Program
|
|||||||
// Api related
|
// Api related
|
||||||
builder.Services.AddSingleton<PathTransformer>();
|
builder.Services.AddSingleton<PathTransformer>();
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
builder.Services.AddControllers()
|
builder.Services.AddControllers();
|
||||||
.AddJsonOptions(opt =>
|
|
||||||
{
|
|
||||||
opt.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
|
||||||
});
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen(opt =>
|
builder.Services.AddSwaggerGen(opt =>
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user