diff --git a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
index ba59aa9..ca6ba04 100644
--- a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
+++ b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
@@ -12,6 +12,7 @@
+
@@ -21,6 +22,7 @@
+
diff --git a/Flawless.Client/Service/RepositoryService.cs b/Flawless.Client/Service/RepositoryService.cs
index 61adb88..d1b5e96 100644
--- a/Flawless.Client/Service/RepositoryService.cs
+++ b/Flawless.Client/Service/RepositoryService.cs
@@ -346,7 +346,7 @@ public class RepositoryService : BaseService
return true;
}
- public async ValueTask UpdateIssueDetailsFromServerAsync(RepositoryModel repo, ulong issueId)
+ public async ValueTask UpdateIssueDetailsFromServerAsync(RepositoryModel repo, ulong issueId, bool titleOnly)
{
var api = Api.C;
try
@@ -358,8 +358,6 @@ public class RepositoryService : BaseService
}
var issue = await api.Gateway.Issue(repo.Name, repo.OwnerName, (long)issueId);
- var details = (await api.Gateway.Comments(repo.Name, repo.OwnerName, (long)issueId))
- .Result.ToImmutableDictionary(x => x.CommentId);
var entity = repo.Issues.FirstOrDefault(x => x.Id == issueId);
var tags = issue.Tag.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
@@ -380,14 +378,20 @@ public class RepositoryService : BaseService
entity.Tags.AddRange(tags);
repo.Issues.Add(entity);
- entity.Comments.AddRange(details.Select(x => new RepositoryModel.Comment
+ if (!titleOnly)
{
- Id = (ulong) x.Key,
- Author = x.Value.Author,
- Content = x.Value.Content,
- CreatedAt = x.Value.CreatedAt.UtcDateTime,
- ReplyTo = x.Value.ReplyToId.HasValue ? (ulong) x.Value.ReplyToId.Value : null
- }));
+ var details = (await api.Gateway.Comments(repo.Name, repo.OwnerName, (long)issueId))
+ .Result.ToImmutableDictionary(x => x.CommentId);
+
+ entity.Comments.AddRange(details.Select(x => new RepositoryModel.Comment
+ {
+ Id = (ulong)x.Key,
+ Author = x.Value.Author,
+ Content = x.Value.Content,
+ CreatedAt = x.Value.CreatedAt.UtcDateTime,
+ ReplyTo = x.Value.ReplyToId.HasValue ? (ulong)x.Value.ReplyToId.Value : null
+ }));
+ }
}
else
{
@@ -398,35 +402,41 @@ public class RepositoryService : BaseService
entity.Tags.Clear();
entity.Tags.AddRange(tags);
-
- for (var i = 0; i < entity.Comments.Count; i++)
+
+ if (!titleOnly)
{
- var c = entity.Comments[i];
- if (!details.ContainsKey((long) c.Id)) repo.Issues.RemoveAt(i);
- }
-
- foreach (var (key, value) in details)
- {
- var d = entity.Comments.FirstOrDefault(x => x.Id == (ulong) key);
- if (d == null)
+ var details = (await api.Gateway.Comments(repo.Name, repo.OwnerName, (long)issueId))
+ .Result.ToImmutableDictionary(x => x.CommentId);
+
+ for (var i = 0; i < entity.Comments.Count; i++)
{
- var c = new RepositoryModel.Comment
- {
- Id = (ulong)key,
- Author = value.Author,
- Content = value.Content,
- CreatedAt = value.CreatedAt.UtcDateTime,
- ReplyTo = value.ReplyToId.HasValue ? (ulong)value.ReplyToId.Value : null
- };
-
- entity.Comments.Add(c);
+ var c = entity.Comments[i];
+ if (!details.ContainsKey((long) c.Id)) repo.Issues.RemoveAt(i);
}
- else
+
+ foreach (var (key, value) in details)
{
- d.Author = value.Author;
- d.Content = value.Content;
- d.CreatedAt = value.CreatedAt.UtcDateTime;
- d.ReplyTo = value.ReplyToId.HasValue ? (ulong)value.ReplyToId.Value : null;
+ var d = entity.Comments.FirstOrDefault(x => x.Id == (ulong) key);
+ if (d == null)
+ {
+ var c = new RepositoryModel.Comment
+ {
+ Id = (ulong)key,
+ Author = value.Author,
+ Content = value.Content,
+ CreatedAt = value.CreatedAt.UtcDateTime,
+ ReplyTo = value.ReplyToId.HasValue ? (ulong)value.ReplyToId.Value : null
+ };
+
+ entity.Comments.Add(c);
+ }
+ else
+ {
+ d.Author = value.Author;
+ d.Content = value.Content;
+ d.CreatedAt = value.CreatedAt.UtcDateTime;
+ d.ReplyTo = value.ReplyToId.HasValue ? (ulong)value.ReplyToId.Value : null;
+ }
}
}
}
@@ -443,7 +453,7 @@ public class RepositoryService : BaseService
return true;
}
- public async ValueTask CreateIssueAsync(RepositoryModel repo, string title, string description, IEnumerable? tags)
+ public async ValueTask CreateIssueAsync(RepositoryModel repo, string title, string description, IEnumerable? tags)
{
var api = Api.C;
try
@@ -460,10 +470,27 @@ public class RepositoryService : BaseService
new StringBuilder().AppendJoin(',', tags);
}
- return await api.Gateway.Create(
+ var issue = await api.Gateway.Create(
repo.OwnerName,
repo.Name,
new CreateIssueRequest { Title = title, Description = description, Tag = tagString?.ToString()});
+
+ var entity = new RepositoryModel.Issue
+ {
+ Author = issue.Author,
+ Id = (ulong) issue.Id,
+ Title = issue.Title,
+ Description = null,
+ Closed = issue.Closed,
+ CreatedAt = issue.CreateAt.UtcDateTime,
+ LastUpdatedAt = issue.LastUpdate.UtcDateTime
+ };
+
+ entity.Tags.AddRange(tags ?? []);
+ repo.Issues.Add(entity);
+
+ repo.Issues.Sort((x, y) => (int) ((long) x.Id - (long) y.Id));
+ return entity.Id;
}
catch (Exception e)
{
@@ -485,6 +512,7 @@ public class RepositoryService : BaseService
}
await api.Gateway.Close(repo.OwnerName, repo.Name, (long) issueId);
+ await UpdateIssueDetailsFromServerAsync(repo, issueId, true);
return true;
}
catch (Exception e)
@@ -507,6 +535,7 @@ public class RepositoryService : BaseService
}
await api.Gateway.Reopen(repo.OwnerName, repo.Name, (long) issueId);
+ await UpdateIssueDetailsFromServerAsync(repo, issueId, true);
return true;
}
catch (Exception e)
@@ -540,7 +569,7 @@ public class RepositoryService : BaseService
}
}
- public async ValueTask UpdateIssueAsync(RepositoryModel repo, long issueId, string? title, string? description, IEnumerable? tags)
+ public async ValueTask UpdateIssueAsync(RepositoryModel repo, ulong issueId, string? title, string? description, IEnumerable? tags)
{
var api = Api.C;
try
@@ -557,7 +586,7 @@ public class RepositoryService : BaseService
new StringBuilder().AppendJoin(',', tags);
}
- await api.Gateway.Edit(repo.OwnerName, repo.Name, issueId,
+ await api.Gateway.Edit(repo.OwnerName, repo.Name, (long) issueId,
new UpdateIssueRequest { Title = title, Description = description, Tag = tagString?.ToString()});
return true;
}
diff --git a/Flawless.Client/UIHelper.cs b/Flawless.Client/UIHelper.cs
index 693981f..78c4150 100644
--- a/Flawless.Client/UIHelper.cs
+++ b/Flawless.Client/UIHelper.cs
@@ -99,7 +99,7 @@ public static class UIHelper
}
}
- public static OverlayDialogOptions DefaultOverlayDialogOptions()
+ public static OverlayDialogOptions DefaultOverlayDialogOptionsYesNo()
{
return new OverlayDialogOptions
{
diff --git a/Flawless.Client/ViewModels/IssueDetailViewModel.cs b/Flawless.Client/ViewModels/IssueDetailViewModel.cs
index d4bfac6..e5ba4cf 100644
--- a/Flawless.Client/ViewModels/IssueDetailViewModel.cs
+++ b/Flawless.Client/ViewModels/IssueDetailViewModel.cs
@@ -1,3 +1,4 @@
+using System;
using System.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;
@@ -6,7 +7,10 @@ using Flawless.Client.Models;
using ReactiveUI;
using Flawless.Client.Service;
using Flawless.Client.ViewModels;
+using Flawless.Client.ViewModels.ModalBox;
+using Flawless.Client.Views.ModalBox;
using ReactiveUI.SourceGenerators;
+using Ursa.Controls;
public partial class IssueDetailViewModel : RoutableViewModelBase
{
@@ -24,16 +28,16 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
{
_repo = repo;
_service = RepositoryService.Current;
- LoadDataAsync(repo, issueId);
+ LoadDataAsync(repo, issueId, true);
}
- private async Task LoadDataAsync(RepositoryModel repo, ulong issueId)
+ private async Task LoadDataAsync(RepositoryModel repo, ulong issueId, bool quitIfFailed)
{
using var _ = UIHelper.MakeLoading("Fetching comments from server...");
- if (!await _service.UpdateIssueDetailsFromServerAsync(repo, issueId))
+ if (!await _service.UpdateIssueDetailsFromServerAsync(repo, issueId, false))
{
- await NavigateBackAsync();
- UIHelper.NotifyError("Operation failed", "Can not open issue...");
+ if (quitIfFailed) await NavigateBackAsync();
+ UIHelper.NotifyError("Operation failed", "Can not load issue...");
return;
}
@@ -41,7 +45,7 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
}
[ReactiveCommand]
- private Task RefreshIssueAsync() => LoadDataAsync(_repo, CurrentIssue!.Id);
+ private Task RefreshIssueAsync() => LoadDataAsync(_repo, CurrentIssue!.Id, false);
[ReactiveCommand]
private async Task ToggleIssueStatusAsync()
@@ -52,7 +56,6 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
if (CurrentIssue.Closed)
{
if (!await _service.ReopenIssueAsync(_repo, CurrentIssue.Id)) return;
-
}
else
{
@@ -62,6 +65,13 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
CurrentIssue.Closed = !CurrentIssue.Closed;
}
+ [ReactiveCommand]
+ private void MarkAsReplayTo(RepositoryModel.Comment? comment)
+ {
+ if (CurrentIssue == null) return;
+ ReplyTo = comment;
+ }
+
[ReactiveCommand]
private async Task AddCommentAsync()
{
@@ -80,10 +90,30 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
NewComment = string.Empty;
}
}
-
+
[ReactiveCommand]
private async Task EditIssueAsync()
{
+ if (CurrentIssue == null) return;
+
+ var opt = UIHelper.DefaultOverlayDialogOptionsYesNo();
+ var vm = new IssueEditDialogViewModel(CurrentIssue);
+
+ var result = await OverlayDialog.ShowModal(
+ vm, AppDefaultValues.HostId, opt);
+
+ if (result == DialogResult.Yes)
+ {
+ using var l = UIHelper.MakeLoading("Updating issue...");
+ var tags = vm.Tag?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+
+ // 调用服务端更新接口
+ await RepositoryService.C.UpdateIssueAsync(
+ _repo, CurrentIssue.Id,
+ vm.Title, vm.Description, tags
+ );
+
+ }
}
[ReactiveCommand]
diff --git a/Flawless.Client/ViewModels/RepositoryViewModel.cs b/Flawless.Client/ViewModels/RepositoryViewModel.cs
index 4b34584..8f1276d 100644
--- a/Flawless.Client/ViewModels/RepositoryViewModel.cs
+++ b/Flawless.Client/ViewModels/RepositoryViewModel.cs
@@ -422,25 +422,40 @@ public partial class RepositoryViewModel : RoutableViewModelBase
[ReactiveCommand]
private async Task CreateIssueAsync()
{
-
+ var opt = UIHelper.DefaultOverlayDialogOptionsYesNo();
+ var vm = new IssueEditDialogViewModel();
+
+ var r = await OverlayDialog.ShowModal(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()
+ 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()
+ 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()
+ private async Task ReopenIssueAsync(RepositoryModel.Issue issue)
{
-
+ using var l = UIHelper.MakeLoading("Config issue...");
+ await RepositoryService.C.ReopenIssueAsync(Repository, issue.Id);
}
[ReactiveCommand]
@@ -553,6 +568,13 @@ public partial class RepositoryViewModel : RoutableViewModelBase
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)
{
@@ -581,7 +603,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
return;
}
- var style = UIHelper.DefaultOverlayDialogOptions();
+ var style = UIHelper.DefaultOverlayDialogOptionsYesNo();
var vm = new EditRepositoryMemberDialogViewModel();
vm.LockUsername = true;
vm.Username = member.Username;
@@ -618,7 +640,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
return;
}
- var style = UIHelper.DefaultOverlayDialogOptions();
+ var style = UIHelper.DefaultOverlayDialogOptionsYesNo();
var vm = new EditRepositoryMemberDialogViewModel();
var result = await OverlayDialog.ShowModal
(vm, AppDefaultValues.HostId, style);
diff --git a/Flawless.Client/Views/IssueDetailView.axaml b/Flawless.Client/Views/IssueDetailView.axaml
index 0c098b1..a74315d 100644
--- a/Flawless.Client/Views/IssueDetailView.axaml
+++ b/Flawless.Client/Views/IssueDetailView.axaml
@@ -5,7 +5,8 @@
xmlns:vm="using:Flawless.Client.ViewModels"
xmlns:u="https://irihi.tech/ursa"
xmlns:global="clr-namespace:"
- x:DataType="global:IssueDetailViewModel">
+ x:DataType="global:IssueDetailViewModel"
+ x:Class="Flawless.Client.Views.IssueDetailView">
diff --git a/Flawless.Client/Views/IssueDetailView.axaml.cs b/Flawless.Client/Views/IssueDetailView.axaml.cs
new file mode 100644
index 0000000..9865028
--- /dev/null
+++ b/Flawless.Client/Views/IssueDetailView.axaml.cs
@@ -0,0 +1,14 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Ursa.ReactiveUIExtension;
+
+namespace Flawless.Client.Views;
+
+public partial class IssueDetailView : ReactiveUrsaView
+{
+ public IssueDetailView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/Views/ModalBox/IssueEditDialogView.axaml b/Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml
similarity index 78%
rename from Flawless.Client/Views/ModalBox/IssueEditDialogView.axaml
rename to Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml
index 463bb25..ec32514 100644
--- a/Flawless.Client/Views/ModalBox/IssueEditDialogView.axaml
+++ b/Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml
@@ -2,11 +2,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:vm="using:Flawless.Client.ViewModels.ModalBox"
xmlns:u="https://irihi.tech/ursa"
+ xmlns:vm="clr-namespace:Flawless.Client.ViewModels.ModalBox"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="vm:IssueEditDialogViewModel"
- mc:Ignorable="d"
- x:Class="Flawless.Client.Views.RepositoryPage.IssueEditDialogView">
+ x:Class="Flawless.Client.Views.ModalBox.IssueDetailEditView">
@@ -22,4 +22,5 @@
SelectedItem="{Binding Tag}"/>
-
\ No newline at end of file
+
+
diff --git a/Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml.cs b/Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml.cs
new file mode 100644
index 0000000..e593858
--- /dev/null
+++ b/Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Flawless.Client.Views.ModalBox;
+
+public partial class IssueDetailEditView : UserControl
+{
+ public IssueDetailEditView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml b/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml
index de50354..21727bc 100644
--- a/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml
+++ b/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml
@@ -10,11 +10,13 @@
+
-
+
@@ -41,17 +43,15 @@
Foreground="{DynamicResource SemiSecondaryTextColor}"/>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+