1
0

feat: details the logic of issue commit.

This commit is contained in:
Ca2didi 2025-04-16 10:00:15 +08:00
parent f246aa17bd
commit 852d935e80
10 changed files with 186 additions and 74 deletions

View File

@ -12,6 +12,7 @@
<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" />
<entry key="Flawless.Client/Views/IssueDetailEditView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/IssueDetailView.axaml" value="Flawless.Client/Flawless.Client.csproj" /> <entry key="Flawless.Client/Views/IssueDetailView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/LoginView.axaml" value="Flawless.Client/Flawless.Client.csproj" /> <entry key="Flawless.Client/Views/LoginView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/MainView.axaml" value="Flawless.Client/Flawless.Client.csproj" /> <entry key="Flawless.Client/Views/MainView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
@ -21,6 +22,7 @@
<entry key="Flawless.Client/Views/ModalBox/CreateRepositoryDialog.axaml" value="Flawless.Client/Flawless.Client.csproj" /> <entry key="Flawless.Client/Views/ModalBox/CreateRepositoryDialog.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" /> <entry key="Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/ModalBox/EditRepositoryMemberDialogueView.axaml" value="Flawless.Client/Flawless.Client.csproj" /> <entry key="Flawless.Client/Views/ModalBox/EditRepositoryMemberDialogueView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/ModalBox/IssueDetailEditView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
<entry key="Flawless.Client/Views/ModalBox/SimpleMessageDialogView.axaml" value="Flawless.Client/Flawless.Client.csproj" /> <entry key="Flawless.Client/Views/ModalBox/SimpleMessageDialogView.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/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" />

View File

@ -346,7 +346,7 @@ public class RepositoryService : BaseService<RepositoryService>
return true; return true;
} }
public async ValueTask<bool> UpdateIssueDetailsFromServerAsync(RepositoryModel repo, ulong issueId) public async ValueTask<bool> UpdateIssueDetailsFromServerAsync(RepositoryModel repo, ulong issueId, bool titleOnly)
{ {
var api = Api.C; var api = Api.C;
try try
@ -358,8 +358,6 @@ public class RepositoryService : BaseService<RepositoryService>
} }
var issue = await api.Gateway.Issue(repo.Name, repo.OwnerName, (long)issueId); 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 entity = repo.Issues.FirstOrDefault(x => x.Id == issueId);
var tags = issue.Tag.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var tags = issue.Tag.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
@ -380,14 +378,20 @@ public class RepositoryService : BaseService<RepositoryService>
entity.Tags.AddRange(tags); entity.Tags.AddRange(tags);
repo.Issues.Add(entity); repo.Issues.Add(entity);
entity.Comments.AddRange(details.Select(x => new RepositoryModel.Comment if (!titleOnly)
{ {
Id = (ulong) x.Key, var details = (await api.Gateway.Comments(repo.Name, repo.OwnerName, (long)issueId))
Author = x.Value.Author, .Result.ToImmutableDictionary(x => x.CommentId);
Content = x.Value.Content,
CreatedAt = x.Value.CreatedAt.UtcDateTime, entity.Comments.AddRange(details.Select(x => new RepositoryModel.Comment
ReplyTo = x.Value.ReplyToId.HasValue ? (ulong) x.Value.ReplyToId.Value : null {
})); 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 else
{ {
@ -398,35 +402,41 @@ public class RepositoryService : BaseService<RepositoryService>
entity.Tags.Clear(); entity.Tags.Clear();
entity.Tags.AddRange(tags); entity.Tags.AddRange(tags);
for (var i = 0; i < entity.Comments.Count; i++) if (!titleOnly)
{ {
var c = entity.Comments[i]; var details = (await api.Gateway.Comments(repo.Name, repo.OwnerName, (long)issueId))
if (!details.ContainsKey((long) c.Id)) repo.Issues.RemoveAt(i); .Result.ToImmutableDictionary(x => x.CommentId);
}
for (var i = 0; i < entity.Comments.Count; i++)
foreach (var (key, value) in details)
{
var d = entity.Comments.FirstOrDefault(x => x.Id == (ulong) key);
if (d == null)
{ {
var c = new RepositoryModel.Comment var c = entity.Comments[i];
{ if (!details.ContainsKey((long) c.Id)) repo.Issues.RemoveAt(i);
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
foreach (var (key, value) in details)
{ {
d.Author = value.Author; var d = entity.Comments.FirstOrDefault(x => x.Id == (ulong) key);
d.Content = value.Content; if (d == null)
d.CreatedAt = value.CreatedAt.UtcDateTime; {
d.ReplyTo = value.ReplyToId.HasValue ? (ulong)value.ReplyToId.Value : 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<RepositoryService>
return true; return true;
} }
public async ValueTask<IssueInfo?> CreateIssueAsync(RepositoryModel repo, string title, string description, IEnumerable<string>? tags) public async ValueTask<ulong?> CreateIssueAsync(RepositoryModel repo, string title, string description, IEnumerable<string>? tags)
{ {
var api = Api.C; var api = Api.C;
try try
@ -460,10 +470,27 @@ public class RepositoryService : BaseService<RepositoryService>
new StringBuilder().AppendJoin(',', tags); new StringBuilder().AppendJoin(',', tags);
} }
return await api.Gateway.Create( var issue = await api.Gateway.Create(
repo.OwnerName, repo.OwnerName,
repo.Name, repo.Name,
new CreateIssueRequest { Title = title, Description = description, Tag = tagString?.ToString()}); 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) catch (Exception e)
{ {
@ -485,6 +512,7 @@ public class RepositoryService : BaseService<RepositoryService>
} }
await api.Gateway.Close(repo.OwnerName, repo.Name, (long) issueId); await api.Gateway.Close(repo.OwnerName, repo.Name, (long) issueId);
await UpdateIssueDetailsFromServerAsync(repo, issueId, true);
return true; return true;
} }
catch (Exception e) catch (Exception e)
@ -507,6 +535,7 @@ public class RepositoryService : BaseService<RepositoryService>
} }
await api.Gateway.Reopen(repo.OwnerName, repo.Name, (long) issueId); await api.Gateway.Reopen(repo.OwnerName, repo.Name, (long) issueId);
await UpdateIssueDetailsFromServerAsync(repo, issueId, true);
return true; return true;
} }
catch (Exception e) catch (Exception e)
@ -540,7 +569,7 @@ public class RepositoryService : BaseService<RepositoryService>
} }
} }
public async ValueTask<bool> UpdateIssueAsync(RepositoryModel repo, long issueId, string? title, string? description, IEnumerable<string>? tags) public async ValueTask<bool> UpdateIssueAsync(RepositoryModel repo, ulong issueId, string? title, string? description, IEnumerable<string>? tags)
{ {
var api = Api.C; var api = Api.C;
try try
@ -557,7 +586,7 @@ public class RepositoryService : BaseService<RepositoryService>
new StringBuilder().AppendJoin(',', tags); 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()}); new UpdateIssueRequest { Title = title, Description = description, Tag = tagString?.ToString()});
return true; return true;
} }

View File

@ -99,7 +99,7 @@ public static class UIHelper
} }
} }
public static OverlayDialogOptions DefaultOverlayDialogOptions() public static OverlayDialogOptions DefaultOverlayDialogOptionsYesNo()
{ {
return new OverlayDialogOptions return new OverlayDialogOptions
{ {

View File

@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using System.Reactive.Threading.Tasks; using System.Reactive.Threading.Tasks;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -6,7 +7,10 @@ using Flawless.Client.Models;
using ReactiveUI; using ReactiveUI;
using Flawless.Client.Service; using Flawless.Client.Service;
using Flawless.Client.ViewModels; using Flawless.Client.ViewModels;
using Flawless.Client.ViewModels.ModalBox;
using Flawless.Client.Views.ModalBox;
using ReactiveUI.SourceGenerators; using ReactiveUI.SourceGenerators;
using Ursa.Controls;
public partial class IssueDetailViewModel : RoutableViewModelBase public partial class IssueDetailViewModel : RoutableViewModelBase
{ {
@ -24,16 +28,16 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
{ {
_repo = repo; _repo = repo;
_service = RepositoryService.Current; _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..."); using var _ = UIHelper.MakeLoading("Fetching comments from server...");
if (!await _service.UpdateIssueDetailsFromServerAsync(repo, issueId)) if (!await _service.UpdateIssueDetailsFromServerAsync(repo, issueId, false))
{ {
await NavigateBackAsync(); if (quitIfFailed) await NavigateBackAsync();
UIHelper.NotifyError("Operation failed", "Can not open issue..."); UIHelper.NotifyError("Operation failed", "Can not load issue...");
return; return;
} }
@ -41,7 +45,7 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
} }
[ReactiveCommand] [ReactiveCommand]
private Task RefreshIssueAsync() => LoadDataAsync(_repo, CurrentIssue!.Id); private Task RefreshIssueAsync() => LoadDataAsync(_repo, CurrentIssue!.Id, false);
[ReactiveCommand] [ReactiveCommand]
private async Task ToggleIssueStatusAsync() private async Task ToggleIssueStatusAsync()
@ -52,7 +56,6 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
if (CurrentIssue.Closed) if (CurrentIssue.Closed)
{ {
if (!await _service.ReopenIssueAsync(_repo, CurrentIssue.Id)) return; if (!await _service.ReopenIssueAsync(_repo, CurrentIssue.Id)) return;
} }
else else
{ {
@ -62,6 +65,13 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
CurrentIssue.Closed = !CurrentIssue.Closed; CurrentIssue.Closed = !CurrentIssue.Closed;
} }
[ReactiveCommand]
private void MarkAsReplayTo(RepositoryModel.Comment? comment)
{
if (CurrentIssue == null) return;
ReplyTo = comment;
}
[ReactiveCommand] [ReactiveCommand]
private async Task AddCommentAsync() private async Task AddCommentAsync()
{ {
@ -80,10 +90,30 @@ public partial class IssueDetailViewModel : RoutableViewModelBase
NewComment = string.Empty; NewComment = string.Empty;
} }
} }
[ReactiveCommand] [ReactiveCommand]
private async Task EditIssueAsync() private async Task EditIssueAsync()
{ {
if (CurrentIssue == null) return;
var opt = UIHelper.DefaultOverlayDialogOptionsYesNo();
var vm = new IssueEditDialogViewModel(CurrentIssue);
var result = await OverlayDialog.ShowModal<IssueDetailEditView, IssueEditDialogViewModel>(
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] [ReactiveCommand]

View File

@ -422,25 +422,40 @@ public partial class RepositoryViewModel : RoutableViewModelBase
[ReactiveCommand] [ReactiveCommand]
private async Task CreateIssueAsync() private async Task CreateIssueAsync()
{ {
var opt = UIHelper.DefaultOverlayDialogOptionsYesNo();
var vm = new IssueEditDialogViewModel();
var r = await OverlayDialog.ShowModal<IssueDetailEditView, IssueEditDialogViewModel>(vm, AppDefaultValues.HostId, opt);
if (r == DialogResult.Yes)
{
using var l = UIHelper.MakeLoading("Sending issue...");
var tags = vm.Tag?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var issueId = await RepositoryService.C.CreateIssueAsync(Repository, vm.Title, vm.Description, tags);
if (issueId == null) return;
await HostScreen.Router.Navigate.Execute(new IssueDetailViewModel(HostScreen, Repository, issueId.Value));
}
} }
[ReactiveCommand] [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] [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] [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] [ReactiveCommand]
@ -553,6 +568,13 @@ public partial class RepositoryViewModel : RoutableViewModelBase
UpdatePermissionOfRepository(); UpdatePermissionOfRepository();
} }
[ReactiveCommand]
private async ValueTask RefreshRepositoryIssuesAsync()
{
using var l = UIHelper.MakeLoading("Refreshing issues...");
await RepositoryService.C.UpdateIssuesListFromServerAsync(Repository);
}
[ReactiveCommand] [ReactiveCommand]
private async ValueTask DeleteMemberFromServerAsync(RepositoryModel.Member member) private async ValueTask DeleteMemberFromServerAsync(RepositoryModel.Member member)
{ {
@ -581,7 +603,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
return; return;
} }
var style = UIHelper.DefaultOverlayDialogOptions(); var style = UIHelper.DefaultOverlayDialogOptionsYesNo();
var vm = new EditRepositoryMemberDialogViewModel(); var vm = new EditRepositoryMemberDialogViewModel();
vm.LockUsername = true; vm.LockUsername = true;
vm.Username = member.Username; vm.Username = member.Username;
@ -618,7 +640,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
return; return;
} }
var style = UIHelper.DefaultOverlayDialogOptions(); var style = UIHelper.DefaultOverlayDialogOptionsYesNo();
var vm = new EditRepositoryMemberDialogViewModel(); var vm = new EditRepositoryMemberDialogViewModel();
var result = await OverlayDialog.ShowModal<EditRepositoryMemberDialogueView,EditRepositoryMemberDialogViewModel> var result = await OverlayDialog.ShowModal<EditRepositoryMemberDialogueView,EditRepositoryMemberDialogViewModel>
(vm, AppDefaultValues.HostId, style); (vm, AppDefaultValues.HostId, style);

View File

@ -5,7 +5,8 @@
xmlns:vm="using:Flawless.Client.ViewModels" xmlns:vm="using:Flawless.Client.ViewModels"
xmlns:u="https://irihi.tech/ursa" xmlns:u="https://irihi.tech/ursa"
xmlns:global="clr-namespace:" xmlns:global="clr-namespace:"
x:DataType="global:IssueDetailViewModel"> x:DataType="global:IssueDetailViewModel"
x:Class="Flawless.Client.Views.IssueDetailView">
<DockPanel Margin="20"> <DockPanel Margin="20">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8"> <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8">

View File

@ -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<IssueDetailViewModel>
{
public IssueDetailView()
{
InitializeComponent();
}
}

View File

@ -2,11 +2,11 @@
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.ModalBox"
xmlns:u="https://irihi.tech/ursa" 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" x:DataType="vm:IssueEditDialogViewModel"
mc:Ignorable="d" x:Class="Flawless.Client.Views.ModalBox.IssueDetailEditView">
x:Class="Flawless.Client.Views.RepositoryPage.IssueEditDialogView">
<u:Form> <u:Form>
<u:FormItem Label="Title" IsRequired="True"> <u:FormItem Label="Title" IsRequired="True">
@ -22,4 +22,5 @@
SelectedItem="{Binding Tag}"/> SelectedItem="{Binding Tag}"/>
</u:FormItem> </u:FormItem>
</u:Form> </u:Form>
</UserControl>
</UserControl>

View File

@ -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();
}
}

View File

@ -10,11 +10,13 @@
<DockPanel> <DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0 0 0 12"> <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0 0 0 12">
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="刷新"
Command="{Binding RefreshRepositoryIssuesAsyncCommand}"/>
<u:IconButton Icon="{StaticResource SemiIconPlus}" Content="新建 Issue" <u:IconButton Icon="{StaticResource SemiIconPlus}" Content="新建 Issue"
Command="{Binding CreateIssueCommand}"/> Command="{Binding CreateIssueCommand}"/>
<!-- <ComboBox ItemsSource="{Binding IssueFilters}" SelectedIndex="0" --> <!-- <ComboBox ItemsSource="{Binding IssueFilters}" SelectedIndex="0" -->
<!-- Width="120" PlaceholderText="筛选状态"/> --> <!-- Width="120" PlaceholderText="筛选状态"/> -->
<!-- <ComboBox ItemsSource="{Binding IssueTags}" SelectedIndex="0" --> <!-- <ComboBox ItemsSource="{Binding IssueTags}" SelectedIndex="0" -->
<!-- Width="160" PlaceholderText="筛选标签"/> --> <!-- Width="160" PlaceholderText="筛选标签"/> -->
</StackPanel> </StackPanel>
@ -41,17 +43,15 @@
Foreground="{DynamicResource SemiSecondaryTextColor}"/> Foreground="{DynamicResource SemiSecondaryTextColor}"/>
</StackPanel> </StackPanel>
<!-- <WrapPanel Grid.Column="1" Grid.Row="2"> --> <WrapPanel Grid.Column="1" Grid.Row="2">
<!-- <Border CornerRadius="4" Padding="4 2" Background="{DynamicResource SemiInfoBgColor}"> --> <Border CornerRadius="4" Padding="4 2" Background="{DynamicResource SemiInfoBgColor}">
<!-- <TextBlock Text="{Binding Id, StringFormat='#{}'}" --> <TextBlock Text="{Binding Id, StringFormat='#{}'}"
<!-- Foreground="{DynamicResource SemiInfoColor}"/> --> Foreground="{DynamicResource SemiInfoColor}"/>
<!-- </Border> --> </Border>
<!-- <Border CornerRadius="4" Padding="4 2" Background="{DynamicResource SemiSuccessBgColor}" --> <!-- <Border CornerRadius="4" Padding="4 2" Background="{DynamicResource SemiSuccessBgColor}" -->
<!-- IsVisible="{Binding !!Tag}"> --> <!-- IsVisible="{Binding !!Tag}"> -->
<!-- <TextBlock Text="{Binding Tag}" --> <!-- </Border> -->
<!-- Foreground="{DynamicResource SemiSuccessColor}"/> --> </WrapPanel>
<!-- </Border> -->
<!-- </WrapPanel> -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8"> <StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8">
<u:IconButton Icon="{StaticResource SemiIconEdit}" <u:IconButton Icon="{StaticResource SemiIconEdit}"