diff --git a/Application/Api/SetFavoriteMusicCommand.cs b/Application/Api/SetFavoriteMusicCommand.cs index 5342c35..205b3e7 100644 --- a/Application/Api/SetFavoriteMusicCommand.cs +++ b/Application/Api/SetFavoriteMusicCommand.cs @@ -2,7 +2,7 @@ namespace Application.Api; -public record SetFavoriteMusicCommand(MusicDetailDto Data) : IRequestWrapper; +public record SetFavoriteMusicCommand(MusicFavoriteDto Data) : IRequestWrapper; public class SetFavoriteMusicCommandHandler : RequestHandlerBase { diff --git a/Application/Api/UnlockAllMusicCommand.cs b/Application/Api/UnlockAllMusicCommand.cs new file mode 100644 index 0000000..b0c1596 --- /dev/null +++ b/Application/Api/UnlockAllMusicCommand.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Logging; + +namespace Application.Api; + +public record UnlockAllMusicCommand(long CardId) : IRequestWrapper; + +public class UnlockAllMusicCommandHandler : RequestHandlerBase +{ + private readonly ILogger logger; + + public UnlockAllMusicCommandHandler(ICardDependencyAggregate aggregate, + ILogger logger) : base(aggregate) + { + this.logger = logger; + } + + public override async Task> Handle(UnlockAllMusicCommand request, CancellationToken cancellationToken) + { + var unlocks = await CardDbContext.CardDetails.Where( + detail => detail.CardId == request.CardId && + detail.Pcol1 == 10 && + detail.ScoreUi6 == 1).ToListAsync(cancellationToken: cancellationToken); + if (unlocks.Count == 0) + { + logger.LogWarning("Attempt to unlock for card {Card} that does not exist or is empty!", request.CardId); + return ServiceResult.Failed(ServiceError.CustomMessage("Unlock failed")); + } + + foreach (var unlock in unlocks) + { + unlock.ScoreUi2 = 1; + } + CardDbContext.CardDetails.UpdateRange(unlocks); + await CardDbContext.SaveChangesAsync(cancellationToken); + + return new ServiceResult(true); + } +} \ No newline at end of file diff --git a/MainServer/Controllers/API/ProfilesController.cs b/MainServer/Controllers/API/ProfilesController.cs index d71ff37..eed548c 100644 --- a/MainServer/Controllers/API/ProfilesController.cs +++ b/MainServer/Controllers/API/ProfilesController.cs @@ -16,7 +16,7 @@ public class ProfilesController : BaseController return result; } - [HttpGet("{cardId:long}")] + [HttpGet("TotalResult/{cardId:long}")] public async Task> GetCardTotalResultById(long cardId) { var result = await Mediator.Send(new GetTotalResultQuery(cardId)); @@ -24,9 +24,9 @@ public class ProfilesController : BaseController } [HttpPost("Favorite")] - public async Task> SetFavoriteMusic(MusicDetailDto detail) + public async Task> SetFavoriteMusic(MusicFavoriteDto favorite) { - var result = await Mediator.Send(new SetFavoriteMusicCommand(detail)); + var result = await Mediator.Send(new SetFavoriteMusicCommand(favorite)); return result; } @@ -36,4 +36,12 @@ public class ProfilesController : BaseController var result = await Mediator.Send(new SetPlayerNameCommand(card)); return result; } + + [HttpPost("UnlockAllMusic/{cardId:long}")] + public async Task> UnlockAllMusic(long cardId) + { + var result = await Mediator.Send(new UnlockAllMusicCommand(cardId)); + + return result; + } } \ No newline at end of file diff --git a/MainServer/Database/card.db3-shm b/MainServer/Database/card.db3-shm new file mode 100644 index 0000000..73e920c Binary files /dev/null and b/MainServer/Database/card.db3-shm differ diff --git a/MainServer/Database/card.db3-wal b/MainServer/Database/card.db3-wal new file mode 100644 index 0000000..f5f4685 Binary files /dev/null and b/MainServer/Database/card.db3-wal differ diff --git a/MainServer/Program.cs b/MainServer/Program.cs index 8da4f60..8fa9584 100644 --- a/MainServer/Program.cs +++ b/MainServer/Program.cs @@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore; using Serilog; using Serilog.Extensions.Logging; using Throw; -using Shared.SerializerContexts; Log.Logger = new LoggerConfiguration() .WriteTo.Console() @@ -58,7 +57,7 @@ try builder.Services.AddControllers(options => options.Filters.Add()) - .AddJsonOptions(options => options.JsonSerializerOptions.AddContext()); + .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/Shared/Dto/Api/MusicDetailDto.cs b/Shared/Dto/Api/MusicFavoriteDto.cs similarity index 83% rename from Shared/Dto/Api/MusicDetailDto.cs rename to Shared/Dto/Api/MusicFavoriteDto.cs index 137da92..fb3ae72 100644 --- a/Shared/Dto/Api/MusicDetailDto.cs +++ b/Shared/Dto/Api/MusicFavoriteDto.cs @@ -1,6 +1,6 @@ namespace Shared.Dto.Api; -public class MusicDetailDto +public class MusicFavoriteDto { public long CardId { get; set; } diff --git a/Shared/Models/ServiceError.cs b/Shared/Models/ServiceError.cs index ac21b50..f20e04c 100644 --- a/Shared/Models/ServiceError.cs +++ b/Shared/Models/ServiceError.cs @@ -1,4 +1,5 @@ -using Domain.Enums; +using System.Text.Json.Serialization; +using Domain.Enums; namespace Shared.Models; @@ -12,9 +13,7 @@ namespace Shared.Models; [Serializable] public class ServiceError { - /// - /// CTOR - /// + [JsonConstructor] public ServiceError(string message, int code) { Message = message; @@ -62,9 +61,10 @@ public class ServiceError } public static ServiceError DatabaseSaveFailed => new ServiceError("Database save failed", 800); - - public static ServiceError NotReissue => new ServiceError("Not reissue, registering a new card", (int)CardReturnCode.NotReissue); - + + public static ServiceError NotReissue => + new ServiceError("Not reissue, registering a new card", (int)CardReturnCode.NotReissue); + public static ServiceError UserNotFound => new("Card with this id does not exist", 996); public static ServiceError UserFailedToCreate => new("Failed to create User.", 995); diff --git a/Shared/Models/ServiceResult.cs b/Shared/Models/ServiceResult.cs index b1a960d..e795a0f 100644 --- a/Shared/Models/ServiceResult.cs +++ b/Shared/Models/ServiceResult.cs @@ -1,4 +1,6 @@ -namespace Shared.Models; +using System.Text.Json.Serialization; + +namespace Shared.Models; /// /// A standard response for service calls. @@ -14,7 +16,7 @@ public class ServiceResult : ServiceResult { Data = data; } - + public ServiceResult(T? data, ServiceError error) : base(error) { Data = data; @@ -22,7 +24,7 @@ public class ServiceResult : ServiceResult public ServiceResult(ServiceError error) : base(error) { - + } } diff --git a/Shared/SerializerContexts/SourceGenerationContext.cs b/Shared/SerializerContexts/SourceGenerationContext.cs deleted file mode 100644 index 47144e1..0000000 --- a/Shared/SerializerContexts/SourceGenerationContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; -using Shared.Dto.Api; -using Shared.Models; - -namespace Shared.SerializerContexts; - -[JsonSourceGenerationOptions(WriteIndented = true, GenerationMode = JsonSourceGenerationMode.Metadata)] -[JsonSerializable(typeof(ServiceResult>))] -[JsonSerializable(typeof(ServiceResult))] -[JsonSerializable(typeof(ServiceResult))] -[JsonSerializable(typeof(ServiceResult))] -[JsonSerializable(typeof(ServiceResult))] -public partial class SourceGenerationContext : JsonSerializerContext -{ - -} \ No newline at end of file diff --git a/WebUI/Common/Models/Title.cs b/WebUI/Common/Models/Title.cs index 6481163..fc4c0b0 100644 --- a/WebUI/Common/Models/Title.cs +++ b/WebUI/Common/Models/Title.cs @@ -31,7 +31,7 @@ public enum UnlockType Prefecture = 13, ChainMilestone = 14, Adlibs = 15, - ConsequtiveNoMiss = 16, + ConsecutiveNoMiss = 16, ClearsUsingItems = 17, Avatars = 18, MultiplayerStarsTotal = 19, diff --git a/WebUI/Pages/Cards.razor.cs b/WebUI/Pages/Cards.razor.cs index 119958f..ae20ddb 100644 --- a/WebUI/Pages/Cards.razor.cs +++ b/WebUI/Pages/Cards.razor.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Components; using Shared.Dto.Api; using Shared.Models; using Throw; -using Shared.SerializerContexts; using WebUI.Pages.Dialogs; namespace WebUI.Pages; @@ -26,11 +25,11 @@ public partial class Cards protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - await Task.Delay(3000); + var result = await Client.GetFromJsonAsync>>("api/Profiles"); result.ThrowIfNull(); - Logger.LogInformation("Result: {Result}", result.Data); + Logger.LogInformation("Result: {Result}", result.Succeeded); if (!result.Succeeded) { @@ -52,5 +51,9 @@ public partial class Cards var dialog = await DialogService.ShowAsync("Favorite", parameters, options); // ReSharper disable once UnusedVariable var result = await dialog.Result; + if (!result.Canceled) + { + StateHasChanged(); + } } } \ No newline at end of file diff --git a/WebUI/Pages/Dialogs/ChangeAvatarDialog.razor b/WebUI/Pages/Dialogs/ChangeAvatarDialog.razor new file mode 100644 index 0000000..69dcd49 --- /dev/null +++ b/WebUI/Pages/Dialogs/ChangeAvatarDialog.razor @@ -0,0 +1,99 @@ +@using WebUI.Services +@using Shared.Models +@using WebUI.Common.Models +@inject IDataService DataService + + + + + + + + + + + + + + + + Avatar Id + + + + + Avatar Name + + + + + + @{ + # pragma warning disable CS8602 + } + @context.AvatarId + @context.AvatarName + @{ + # pragma warning restore CS8602 + } + + + + + + + Selected Title: @selectedAvatar?.AvatarName + + + + Cancel + Ok + + + +@code{ + + [CascadingParameter] + public required MudDialogInstance MudDialog { get; set; } + + [Parameter] + public required PlayOptionData Data { get; set; } + + private Avatar? selectedAvatar; + + private IReadOnlyList avatars = new List(); + + private string searchString = string.Empty; + + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + selectedAvatar = DataService.GetAvatarById((uint)Data.OptionPart1.AvatarId); + avatars = DataService.GetAvatarsSortedById(); + } + + + private bool Filter(Avatar? avatar) + { + if (avatar is null) + { + return false; + } + return string.IsNullOrEmpty(searchString) || + avatar.AvatarName.Contains(searchString, StringComparison.OrdinalIgnoreCase); + } + + private void Submit() + { + if (selectedAvatar is not null) + { + Data.OptionPart1.AvatarId = (int)selectedAvatar.AvatarId; + } + MudDialog.Close(DialogResult.Ok(true)); + } + + private void Cancel() => MudDialog.Cancel(); +} \ No newline at end of file diff --git a/WebUI/Pages/Dialogs/ChangeNavigatorDialog.razor b/WebUI/Pages/Dialogs/ChangeNavigatorDialog.razor new file mode 100644 index 0000000..014a11a --- /dev/null +++ b/WebUI/Pages/Dialogs/ChangeNavigatorDialog.razor @@ -0,0 +1,123 @@ +@using WebUI.Services +@using Shared.Models +@using WebUI.Common.Models +@inject IDataService DataService + + + + + + + + + + + + + + + + Navigator Id + + + + + Navigator Name + + + + + Navigator Genre + + + + + Illustration Credit + + + + + Tooltip (Japanese) + + + + + Tooltip (English) + + + + + @{ + # pragma warning disable CS8602 + } + @context.Id + @context.NavigatorName + @context.Genre + @context.IllustrationCredit + @context.ToolTipJp + @context.ToolTipEn + @{ + # pragma warning restore CS8602 + } + + + + + + + Selected Navigator: @selectedNavigator?.NavigatorName + + + + Cancel + Ok + + + +@code{ + + [CascadingParameter] + public required MudDialogInstance MudDialog { get; set; } + + [Parameter] + public required PlayOptionData Data { get; set; } + + private Navigator? selectedNavigator; + + private IReadOnlyList navigators = new List(); + + private string searchString = string.Empty; + + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + selectedNavigator = DataService.GetNavigatorById((uint)Data.OptionPart2.NavigatorId); + navigators = DataService.GetNavigatorsSortedById(); + } + + + private bool Filter(Navigator? navigator) + { + if (navigator is null) + { + return false; + } + var aggregate = $"{navigator.NavigatorName}{navigator.IllustrationCredit}{navigator.ToolTipEn}{navigator.ToolTipJp}"; + return string.IsNullOrEmpty(searchString) || + aggregate.Contains(searchString, StringComparison.OrdinalIgnoreCase); + } + + private void Submit() + { + if (selectedNavigator is not null) + { + Data.OptionPart2.NavigatorId = (int)selectedNavigator.Id; + } + MudDialog.Close(DialogResult.Ok(true)); + } + + private void Cancel() => MudDialog.Cancel(); +} \ No newline at end of file diff --git a/WebUI/Pages/Dialogs/ChangeTitleDialog.razor b/WebUI/Pages/Dialogs/ChangeTitleDialog.razor new file mode 100644 index 0000000..1cb19cc --- /dev/null +++ b/WebUI/Pages/Dialogs/ChangeTitleDialog.razor @@ -0,0 +1,116 @@ +@using WebUI.Services +@using Shared.Models +@using WebUI.Common.Models +@inject IDataService DataService + + + + + + + + + + + + + + + + Title Id + + + + + Title Name + + + + Unlock Condition Type + + + + Unlock Requirement (Japanese) + + + + + Unlock Requirement (English) + + + + + + @{ + # pragma warning disable CS8602 + } + @context.Id + @context.TitleName + @context.UnlockType + @context.UnlockRequirementJp + @context.UnlockRequirementEn + @{ + # pragma warning restore CS8602 + } + + + + + + + Selected Title: @selectedTitle?.TitleName + + + + Cancel + Ok + + + +@code{ + + [CascadingParameter] + public required MudDialogInstance MudDialog { get; set; } + + [Parameter] + public required PlayOptionData Data { get; set; } + + private Title? selectedTitle; + + private IReadOnlyList titles = new List<Title>(); + + private string searchString = string.Empty; + + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + selectedTitle = DataService.GetTitleById((uint)Data.OptionPart1.TitleId); + titles = DataService.GetTitlesSortedById(); + } + + + private bool Filter(Title? title) + { + if (title is null) + { + return false; + } + var aggregate = $"{title.TitleName}{title.UnlockRequirementEn}{title.UnlockRequirementJp}"; + return string.IsNullOrEmpty(searchString) || + aggregate.Contains(searchString, StringComparison.OrdinalIgnoreCase); + } + + private void Submit() + { + if (selectedTitle is not null) + { + Data.OptionPart1.TitleId = (int)selectedTitle.Id; + } + MudDialog.Close(DialogResult.Ok(true)); + } + + private void Cancel() => MudDialog.Cancel(); +} \ No newline at end of file diff --git a/WebUI/Pages/Option.razor b/WebUI/Pages/Option.razor index f84210d..1ba65a9 100644 --- a/WebUI/Pages/Option.razor +++ b/WebUI/Pages/Option.razor @@ -6,38 +6,40 @@ <PageTitle>Option</PageTitle> <h1>Play Options</h1> -@if (playOptionData is null) -{ - <MudStack> - <MudSkeleton Width="100%"/> - <MudSkeleton Width="100%"/> - <MudSkeleton Width="100%"/> - <MudSkeleton Width="100%"/> - <MudSkeleton Width="100%"/> - <MudSkeleton Width="100%"/> - </MudStack> - return; -} -@if (errorMessage != string.Empty) +@if (errorMessage is not null) { <MudText Color="Color.Error" Typo="Typo.h3">@errorMessage</MudText> return; } + +@if (playOptionData is null) +{ + <MudStack> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + </MudStack> + return; +} + <MudStack> <MudStack Row="true"> <MudField Label="Avatar">@GetAvatarName((uint)playOptionData.OptionPart1.AvatarId)</MudField> - <MudButton Variant="Variant.Text">Change Avatar</MudButton> + <MudButton Variant="Variant.Text" OnClick="OpenChangeAvatarDialog">Change Avatar</MudButton> </MudStack> <MudStack Row="true"> <MudField Label="Title">@GetTitleName((uint)playOptionData.OptionPart1.TitleId)</MudField> - <MudButton Variant="Variant.Text">Change Title</MudButton> + <MudButton Variant="Variant.Text" OnClick="OpenChangeTitleDialog">Change Title</MudButton> </MudStack> <MudStack Row="true"> <MudField Label="Navigator">@GetNavigatorName((uint)playOptionData.OptionPart2.NavigatorId)</MudField> - <MudButton Variant="Variant.Text">Change Navigator</MudButton> + <MudButton Variant="Variant.Text" OnClick="OpenChangeNavigatorDialog">Change Navigator</MudButton> </MudStack> @@ -57,5 +59,20 @@ } </MudSelect> - <MudButton Color="Color.Info" Variant="Variant.Filled">Save Options</MudButton> + <MudButton Color="Color.Info" Variant="Variant.Filled" OnClick="SaveOptions"> + @if (isSaving) + { + <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/> + <MudText Class="ms-2">Saving...</MudText> + } + else + { + <MudIcon Icon="@Icons.Material.Filled.Save"></MudIcon> + <MudText>Save</MudText> + } + </MudButton> + <MudButton Color="Color.Default" Variant="Variant.Filled" OnClick="UnlockMusics"> + <MudIcon Icon="@Icons.Material.Filled.LockOpen"></MudIcon> + <MudText>Unlock All Musics</MudText> + </MudButton> </MudStack> \ No newline at end of file diff --git a/WebUI/Pages/Option.razor.cs b/WebUI/Pages/Option.razor.cs index 670b435..7d5cefa 100644 --- a/WebUI/Pages/Option.razor.cs +++ b/WebUI/Pages/Option.razor.cs @@ -2,8 +2,8 @@ using Microsoft.AspNetCore.Components; using Shared.Models; using Throw; +using WebUI.Pages.Dialogs; using WebUI.Services; -using SourceGenerationContext = Shared.SerializerContexts.SourceGenerationContext; namespace WebUI.Pages; @@ -20,6 +20,8 @@ public partial class Option [Inject] public required IDataService DataService { get; set; } + + private bool isSaving; private readonly List<BreadcrumbItem> breadcrumbs = new() { @@ -28,7 +30,15 @@ public partial class Option private PlayOptionData? playOptionData; - private string errorMessage = string.Empty; + private string? errorMessage; + + private static readonly DialogOptions OPTIONS = new() + { + CloseOnEscapeKey = false, + DisableBackdropClick = true, + FullWidth = true, + MaxWidth = MaxWidth.ExtraExtraLarge + }; protected override async Task OnInitializedAsync() { @@ -36,7 +46,6 @@ public partial class Option breadcrumbs.Add(new BreadcrumbItem($"Card: {CardId}", href:null, disabled:true)); breadcrumbs.Add(new BreadcrumbItem("Option", href: $"/Cards/Option/{CardId}", disabled: false)); - await Task.Delay(3000); var result = await Client.GetFromJsonAsync<ServiceResult<PlayOptionData>>($"api/PlayOption/{CardId}"); result.ThrowIfNull(); @@ -51,22 +60,78 @@ public partial class Option private string GetNavigatorName(uint navigatorId) { - var navigator = DataService.GetNavigators().GetValueOrDefault(navigatorId); + var navigator = DataService.GetNavigatorById(navigatorId); return navigator?.NavigatorName ?? "Navigator id unknown"; } private string GetAvatarName(uint avatarId) { - var avatar = DataService.GetAvatars().GetValueOrDefault(avatarId); + var avatar = DataService.GetAvatarById(avatarId); return avatar?.AvatarName ?? "Avatar id unknown"; } private string GetTitleName(uint titleId) { - var title = DataService.GetTitles().GetValueOrDefault(titleId); + var title = DataService.GetTitleById(titleId); return title?.TitleName ?? "Title id unknown"; } + + private async Task OpenChangeTitleDialog() + { + var parameters = new DialogParameters + { + ["Data"] = playOptionData + }; + + var dialog = await DialogService.ShowAsync<ChangeTitleDialog>("Change Title", parameters, OPTIONS); + var result = await dialog.Result; + if (!result.Canceled) + { + StateHasChanged(); + } + } + + private async Task OpenChangeNavigatorDialog() + { + var parameters = new DialogParameters + { + ["Data"] = playOptionData + }; + + var dialog = await DialogService.ShowAsync<ChangeNavigatorDialog>("Change Navigator", parameters, OPTIONS); + var result = await dialog.Result; + if (!result.Canceled) + { + StateHasChanged(); + } + } + private async Task OpenChangeAvatarDialog() + { + var parameters = new DialogParameters + { + ["Data"] = playOptionData + }; + + var dialog = await DialogService.ShowAsync<ChangeAvatarDialog>("Change Navigator", parameters, OPTIONS); + var result = await dialog.Result; + if (!result.Canceled) + { + StateHasChanged(); + } + } + + private async Task SaveOptions() + { + isSaving = true; + var result = await Client.PostAsJsonAsync("api/PlayOption", playOptionData); + isSaving = false; + } + + private async Task UnlockMusics() + { + await Client.PostAsync($"api/Profiles/UnlockAllMusic/{CardId}", null); + } } \ No newline at end of file diff --git a/WebUI/Pages/TotalResult.razor b/WebUI/Pages/TotalResult.razor new file mode 100644 index 0000000..92102e5 --- /dev/null +++ b/WebUI/Pages/TotalResult.razor @@ -0,0 +1,47 @@ +@page "/Cards/TotalResult/{cardId:long}" + +<MudBreadcrumbs Items="breadcrumbs" Class="px-0"></MudBreadcrumbs> + +<PageTitle>Total Result</PageTitle> +<h1>Total Result</h1> + +@if (errorMessage is not null) +{ + <MudText Color="Color.Error" Typo="Typo.h3">@errorMessage</MudText> + return; +} + +@if (totalResultData is null) +{ + <MudStack> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + <MudSkeleton Width="100%"/> + </MudStack> + return; +} + +@if (totalResultData.PlayerData.PlayedSongCount == 0) +{ + <MudText Typo="Typo.h3"> + No Play Record + </MudText> + return; +} + +<MudList> + <MudListSubheader>Player Name: @totalResultData.PlayerName</MudListSubheader> + <MudListItem>Total Score: @totalResultData.PlayerData.TotalScore</MudListItem> + <MudListItem>Average Score: @totalResultData.PlayerData.AverageScore</MudListItem> + <MudListItem>Played Song Count: @totalResultData.PlayerData.PlayedSongCount / @totalResultData.PlayerData.TotalSongCount</MudListItem> + <MudListItem>Cleared Stage Count: @totalResultData.StageCountData.Cleared / @totalResultData.StageCountData.Total</MudListItem> + <MudListItem>No Miss Stage Count: @totalResultData.StageCountData.NoMiss / @totalResultData.StageCountData.Total</MudListItem> + <MudListItem>Full Chain Stage Count: @totalResultData.StageCountData.FullChain / @totalResultData.StageCountData.Total</MudListItem> + <MudListItem>Perfect Stage Count: @totalResultData.StageCountData.Perfect / @totalResultData.StageCountData.Total</MudListItem> + <MudListItem>S and Above Stage Count: @totalResultData.StageCountData.S / @totalResultData.StageCountData.Total</MudListItem> + <MudListItem>S+ and Above Stage Count: @totalResultData.StageCountData.Ss / @totalResultData.StageCountData.Total</MudListItem> + <MudListItem>S++ and Above Stage Count: @totalResultData.StageCountData.Sss / @totalResultData.StageCountData.Total</MudListItem> +</MudList> \ No newline at end of file diff --git a/WebUI/Pages/TotalResult.razor.cs b/WebUI/Pages/TotalResult.razor.cs new file mode 100644 index 0000000..71ee5fc --- /dev/null +++ b/WebUI/Pages/TotalResult.razor.cs @@ -0,0 +1,43 @@ +using System.Net.Http.Json; +using Microsoft.AspNetCore.Components; +using Shared.Models; +using Throw; + +namespace WebUI.Pages; + +public partial class TotalResult +{ + private readonly List<BreadcrumbItem> breadcrumbs = new() + { + new BreadcrumbItem("Cards", href: "/Cards") + }; + + [Parameter] + public long CardId { get; set; } + + [Inject] + public required HttpClient Client { get; set; } + + private string? errorMessage; + + private TotalResultData? totalResultData; + + protected async override Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + breadcrumbs.Add(new BreadcrumbItem($"Card: {CardId}", href:null, disabled:true)); + breadcrumbs.Add(new BreadcrumbItem("TotalResult", href: $"/Cards/TotalResult/{CardId}", disabled: false)); + + var result = await Client.GetFromJsonAsync<ServiceResult<TotalResultData>>($"api/Profiles/TotalResult/{CardId}"); + result.ThrowIfNull(); + + if (!result.Succeeded) + { + errorMessage = result.Error!.Message; + return; + } + + totalResultData = result.Data; + } +} \ No newline at end of file diff --git a/WebUI/Services/DataService.cs b/WebUI/Services/DataService.cs index 54cb84c..80ab2ec 100644 --- a/WebUI/Services/DataService.cs +++ b/WebUI/Services/DataService.cs @@ -14,6 +14,12 @@ public class DataService : IDataService private Dictionary<uint, Title> titles = new(); + private List<Avatar> sortedAvatarList = new(); + + private List<Navigator> sortedNavigatorList = new(); + + private List<Title> sortedTitleList = new(); + private readonly HttpClient client; public DataService(HttpClient client) @@ -26,29 +32,47 @@ public class DataService : IDataService var avatarList = await client.GetFromJsonAsync("data/Avatars.json", SourceGenerationContext.Default.ListAvatar); avatarList.ThrowIfNull(); avatars = avatarList.ToDictionary(avatar => avatar.AvatarId); + sortedAvatarList = avatarList.OrderBy(avatar => avatar.AvatarId).ToList(); var navigatorList = await client.GetFromJsonAsync("data/Navigators.json", SourceGenerationContext.Default.ListNavigator); navigatorList.ThrowIfNull(); navigators = navigatorList.ToDictionary(navigator => navigator.Id); + sortedNavigatorList = navigatorList.OrderBy(navigator => navigator.Id).ToList(); var titleList = await client.GetFromJsonAsync("data/Titles.json", SourceGenerationContext.Default.ListTitle); titleList.ThrowIfNull(); titles = titleList.ToDictionary(title => title.Id); + sortedTitleList = titleList.OrderBy(title => title.Id).ToList(); } - public IReadOnlyDictionary<uint, Avatar> GetAvatars() + public IReadOnlyList<Avatar> GetAvatarsSortedById() { - return new ReadOnlyDictionary<uint, Avatar>(avatars); + return sortedAvatarList; } - public IReadOnlyDictionary<uint, Navigator> GetNavigators() + public IReadOnlyList<Navigator> GetNavigatorsSortedById() { - return new ReadOnlyDictionary<uint, Navigator>(navigators); + return sortedNavigatorList; } - public IReadOnlyDictionary<uint, Title> GetTitles() + public IReadOnlyList<Title> GetTitlesSortedById() { - return new ReadOnlyDictionary<uint, Title>(titles); + return sortedTitleList; + } + + public Avatar? GetAvatarById(uint id) + { + return avatars.GetValueOrDefault(id); + } + + public Title? GetTitleById(uint id) + { + return titles.GetValueOrDefault(id); + } + + public Navigator? GetNavigatorById(uint id) + { + return navigators.GetValueOrDefault(id); } } diff --git a/WebUI/Services/IDataService.cs b/WebUI/Services/IDataService.cs index 8815b41..f72fe15 100644 --- a/WebUI/Services/IDataService.cs +++ b/WebUI/Services/IDataService.cs @@ -7,9 +7,15 @@ public interface IDataService { public Task InitializeAsync(); - public IReadOnlyDictionary<uint, Avatar> GetAvatars(); + public IReadOnlyList<Avatar> GetAvatarsSortedById(); - public IReadOnlyDictionary<uint, Navigator> GetNavigators(); + public IReadOnlyList<Navigator> GetNavigatorsSortedById(); - public IReadOnlyDictionary<uint, Title> GetTitles(); + public IReadOnlyList<Title> GetTitlesSortedById(); + + public Avatar? GetAvatarById(uint id); + + public Title? GetTitleById(uint id); + + public Navigator? GetNavigatorById(uint id); } \ No newline at end of file diff --git a/doc/card_detail.md b/doc/card_detail.md index be7c28f..7fb9227 100644 --- a/doc/card_detail.md +++ b/doc/card_detail.md @@ -6,7 +6,7 @@ card_id, pcol1, pcol2, pcol3 are primary keys | :---: | :-----: | :--------: | :----------------------------------------------------------: | | 0 | 0 | 0 | score_i1:**avatar** score_ui1:**fast/slow** score_ui2:**fever/trance** fcol1: fcol2:**title** fcol3:**sound** | | 1 | 0 | 0 | score_i1:**navigator** fcol3:**unknown** | -| 10 | song id | 0 | score_i1:**skin** score_ui2s:**Whether the song is unlocked** , core_ui6: **The song need to be unlocked**, fcol1:**Favorite song** | +| 10 | song id | 0 | score_i1:**skin**, score_ui2:**Whether the song is unlocked**, score_ui6: **The song need to be unlocked**, fcol1:**Favorite song** | | 20 | song id | difficulty | score_ui1:**play count** score_ui2:**clear count** score_ui3:**no miss or higher count** score_ui4: **full_chain or higher count** score_ui5: **S+ or higher count** score_ui6: **perfect count** | | 21 | song id | difficulty | score_ui1/score_ui5: **highest score**, score_ui2/score_ui6:**highest_score time** score_ui3:**max_chain** score_ui4: **max chain time** fcol1:**max hit adlib count** fcol2:**time for fcol1** | | 30 | 0 | 0 | score_ui2:**Unknown, looks like some sort of count** |