Add last play time support

Support setting avatar, navigator and titles on web interface (unoptimized)
This commit is contained in:
jiych1
2022-07-06 00:52:38 +08:00
parent 95d9d0be5f
commit 4cd290aedc
24 changed files with 55725 additions and 350 deletions
+4 -1
View File
@@ -1,3 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Adlibs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Keynum/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unlockable/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Touhou/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unlockable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vocaloid/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+2 -1
View File
@@ -126,7 +126,8 @@ public static class Configs
public const string RANK_STATUS_XPATH = $"{ROOT_XPATH}/ranking_status";
public const int CONFIG_PCOL1 = 0;
public const int FIRST_CONFIG_PCOL1 = 0;
public const int SECOND_CONFIG_PCOL1 = 1;
public const int CONFIG_PCOL2 = 0;
public const int CONFIG_PCOL3 = 0;
@@ -65,26 +65,38 @@ public class ApiController : WebApiController
// ReSharper disable once UnusedMember.Global
public bool SetPlayOption([JsonData] PlayOption data)
{
var existing = cardSqLiteConnection.Table<CardDetail>()
var firstConfig = cardSqLiteConnection.Table<CardDetail>()
.Where(detail => detail.CardId == data.CardId
&& detail.Pcol1 == Configs.CONFIG_PCOL1
&& detail.Pcol1 == Configs.FIRST_CONFIG_PCOL1
&& detail.Pcol2 == Configs.CONFIG_PCOL2
&& detail.Pcol3 == Configs.CONFIG_PCOL3);
var secondConfig = cardSqLiteConnection.Table<CardDetail>()
.Where(detail => detail.CardId == data.CardId
&& detail.Pcol1 == Configs.SECOND_CONFIG_PCOL1
&& detail.Pcol2 == Configs.CONFIG_PCOL2
&& detail.Pcol3 == Configs.CONFIG_PCOL3);
if (!existing.Any())
if (!firstConfig.Any() || !secondConfig.Any())
{
$"Trying to update non existing card's config! Card id {data.CardId}".Warn();
return false;
}
var cardDetail = existing.First();
cardDetail.ScoreUi1 = (long)data.FastSlowIndicator;
cardDetail.ScoreUi2 = (long)data.FeverTrance;
var result = cardSqLiteConnection.Update(cardDetail);
var firstDetail = firstConfig.First();
firstDetail.ScoreUi1 = (long)data.FastSlowIndicator;
firstDetail.ScoreUi2 = (long)data.FeverTrance;
firstDetail.ScoreI1 = data.AvatarId;
firstDetail.Fcol2 = (int)data.TitleId;
return result == 1;
var secondDetail = secondConfig.First();
secondDetail.ScoreI1 = data.NavigatorId;
var firstResult = cardSqLiteConnection.Update(firstDetail);
var secondResult = cardSqLiteConnection.Update(secondDetail);
return firstResult == 1 && secondResult == 1;
}
[Route(HttpVerbs.Get, "/UserDetail/{cardId}")]
@@ -125,16 +137,26 @@ public class ApiController : WebApiController
private void ProcessCardDetail(UserDetail userDetail, IDictionary<int, SongPlayData> songPlayDataDict)
{
var option = cardSqLiteConnection.Table<CardDetail>()
.FirstOrDefault(detail => detail.CardId == userDetail.CardId
&& detail.Pcol1 == Configs.CONFIG_PCOL1
var firstOption = cardSqLiteConnection.Table<CardDetail>()
.FirstOrDefault(detail => detail.CardId == userDetail.CardId
&& detail.Pcol1 == Configs.FIRST_CONFIG_PCOL1
&& detail.Pcol2 == Configs.CONFIG_PCOL2
&& detail.Pcol3 == Configs.CONFIG_PCOL3
, new CardDetail
{
CardId = userDetail.CardId
});
SetOptions(option, userDetail);
, new CardDetail
{
CardId = userDetail.CardId
});
var secondOption = cardSqLiteConnection.Table<CardDetail>()
.FirstOrDefault(detail => detail.CardId == userDetail.CardId
&& detail.Pcol1 == Configs.SECOND_CONFIG_PCOL1
&& detail.Pcol2 == Configs.CONFIG_PCOL2
&& detail.Pcol3 == Configs.CONFIG_PCOL3
, new CardDetail
{
CardId = userDetail.CardId
});
SetOptions(firstOption, secondOption, userDetail);
var songCounts = cardSqLiteConnection.Table<CardDetail>()
.Where(detail => detail.CardId == userDetail.CardId && detail.Pcol1 == Configs.COUNT_PCOL1);
@@ -162,10 +184,10 @@ public class ApiController : WebApiController
}
}
private static void SetOptions(CardDetail cardDetail, UserDetail userDetail)
private static void SetOptions(CardDetail firstOptionCardDetail, CardDetail secondOptionCardDetail, UserDetail userDetail)
{
var fastSlow = (int)cardDetail.ScoreUi1;
var feverTrance = (int)cardDetail.ScoreUi2;
var fastSlow = (int)firstOptionCardDetail.ScoreUi1;
var feverTrance = (int)firstOptionCardDetail.ScoreUi2;
if (!Enum.IsDefined(typeof(PlayOptions.FastSlowIndicator), fastSlow))
{
@@ -179,9 +201,12 @@ public class ApiController : WebApiController
userDetail.PlayOption = new PlayOption
{
CardId = cardDetail.CardId,
CardId = firstOptionCardDetail.CardId,
FastSlowIndicator = (PlayOptions.FastSlowIndicator)fastSlow,
FeverTrance = (PlayOptions.FeverTranceShow)feverTrance
FeverTrance = (PlayOptions.FeverTranceShow)feverTrance,
AvatarId = firstOptionCardDetail.ScoreI1,
TitleId = firstOptionCardDetail.Fcol2,
NavigatorId = secondOptionCardDetail.ScoreI1
};
}
private void SetDetails(CardDetail cardDetail, IDictionary<int, SongPlayData> songPlayDataDict,
@@ -241,6 +266,7 @@ public class ApiController : WebApiController
}
songPlayDetailData.PlayCount = (int)cardDetail.ScoreUi1;
songPlayDetailData.LastPlayTime = cardDetail.LastPlayTime;
songPlayDetailData.ClearState = ClearState.Failed;
userDetail.PlayedStageCount++;
@@ -40,14 +40,14 @@ public class CardServiceController : WebApiController
{
if (!Enum.IsDefined(typeof(Command), cmdType))
{
throw new ArgumentOutOfRangeException(nameof(cmdType), cmdType, "Cmd type is unknown!");
throw new ArgumentOutOfRangeException(nameof(cmdType), cmdType, $"Cmd type is unknown!\n Data is {xmlData}");
}
var command = (Command)cmdType;
return command switch
{
Command.CardRequest => ProcessCardRequest(mac, cardId, xmlData, type),
Command.CardReadRequest or Command.CardWriteRequest => ProcessCardRequest(mac, cardId, xmlData, type),
Command.ReissueRequest => ProcessReissueRequest(),
Command.RegisterRequest => ProcessRegisterRequest(cardId, xmlData),
_ => throw new ArgumentOutOfRangeException(nameof(command), command, "Command unknown, should never happen!")
@@ -66,7 +66,7 @@ public class CardServiceController : WebApiController
"Get reissue request, returning not reissue".Info();
return ConstructResponse("", ReturnCode.NotReissue);
}
private string ProcessCardRequest(string mac, long cardId, string xmlData, int type)
{
if (!Enum.IsDefined(typeof(CardRequestType), type))
@@ -534,35 +534,56 @@ public class CardServiceController : WebApiController
$"Updated card play count, current count is {data.PlayCount}".Info();
}
private void WriteCardDetail(long cardId, string xmlData)
private void WriteCardDetail(long cardId, string xmlData)
{
var result = cardSqLiteConnection.Table<CardDetail>()
.Where(detail => detail.CardId == cardId);
// Unlock all unlockable songs in card details table when write for the first time
// Unlock all unlockable songs in card details table when write card detail for the first time
if (!result.Any())
{
var unlockableSongIds = Configs.SETTINGS.UnlockableSongIds;
if (unlockableSongIds is null)
{
unlockableSongIds = Configs.DEFAULT_UNLOCKABLE_SONGS;
}
var detailList = unlockableSongIds.Select(id => new CardDetail
{
CardId = cardId,
Pcol1 = 10,
Pcol2 = id,
Pcol3 = 0,
ScoreUi2 = 1,
ScoreUi6 = 1
})
.ToList();
cardSqLiteConnection.InsertOrIgnoreAll(detailList);
UnlockSongs(cardId);
}
Write<CardDetail>(cardId, xmlData);
var reader = new ChoXmlReader<CardDetail>(new StringReader(xmlData)).WithXPath(Configs.DATA_XPATH);
var cardDetail = reader.Read();
if (cardDetail is null)
{
throw new HttpRequestException("Write object is null");
}
cardDetail.SetCardId(cardId);
cardDetail.LastPlayTime = DateTime.Now;
var rowsAffected = cardSqLiteConnection.InsertOrReplace(cardDetail);
if (rowsAffected == 0)
{
throw new ApplicationException("Update database failed!");
}
"Updated card detail".Info();
}
private void UnlockSongs(long cardId)
{
var unlockableSongIds = Configs.SETTINGS.UnlockableSongIds;
if (unlockableSongIds is null)
{
unlockableSongIds = Configs.DEFAULT_UNLOCKABLE_SONGS;
}
var detailList = unlockableSongIds.Select(id => new CardDetail
{
CardId = cardId,
Pcol1 = 10,
Pcol2 = id,
Pcol3 = 0,
ScoreUi2 = 1,
ScoreUi6 = 1,
LastPlayTime = DateTime.Now
})
.ToList();
cardSqLiteConnection.InsertOrIgnoreAll(detailList);
}
#endregion
@@ -612,7 +633,8 @@ public class CardServiceController : WebApiController
private enum Command
{
CardRequest = 256,
CardReadRequest = 256,
CardWriteRequest = 768,
RegisterRequest = 512,
ReissueRequest = 1536
}
@@ -90,6 +90,10 @@ public class CardDetail : Record, ICardIdModel
[ChoXmlElementRecordField(FieldName = "fcol3")]
[XmlElement("fcol3")]
public int Fcol3 { get; set; }
[Column("last_play_time")]
[XmlIgnore]
public DateTime LastPlayTime { get; set; } = DateTime.MinValue;
public void SetCardId(long cardId)
{
+4
View File
@@ -36,5 +36,9 @@
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="Utils" />
</ItemGroup>
</Project>
+192 -195
View File
@@ -1,209 +1,206 @@
@page "/user/{CardId:long}"
@using SharedProject.models
@using SharedProject.enums
@using SharedProject.common
@inject HttpClient Client
@inject IDialogService DialogService
@inject ILogger<User> Logger
<PageTitle>User</PageTitle>
<MudContainer>
@if (userDetail == null)
{
<MudSkeleton Width="1184px" Height="57px"/>
<MudSkeleton Width="1184px" Height="57px"/>
<MudSkeleton Width="1184px" Height="57px"/>
}
else
{
<MudExpansionPanels>
<MudExpansionPanel Text="Total Result">
<MudList>
<MudListSubheader>Player Name: @userDetail.PlayerName</MudListSubheader>
<MudListItem>Total Score: @userDetail.TotalScore</MudListItem>
<MudListItem>Average Score: @userDetail.AverageScore</MudListItem>
<MudListItem>Played Song Count: @userDetail.PlayedSongCount / @userDetail.TotalSongCount</MudListItem>
<MudListItem>Cleared Stage Count: @userDetail.ClearedStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>No Miss Stage Count: @userDetail.NoMissStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>Full Chain Stage Count: @userDetail.FullChainStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>Perfect Stage Count: @userDetail.PerfectStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>S and Above Stage Count: @userDetail.SAboveStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>S+ and Above Stage Count: @userDetail.SPlusAboveStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>S++ and Above Stage Count: @userDetail.SPlusPlusAboveStageCount / @userDetail.TotalStageCount</MudListItem>
</MudList>
</MudExpansionPanel>
<MudExpansionPanel Text="PlayOptions">
<MudSelect @bind-Value="@fastSlowIndicator" Label="FAST/SLOW show setting">
@foreach (var item in Enum.GetValues<PlayOptions.FastSlowIndicator>())
{
<MudSelectItem Value="@item">@item.GetHelpText()</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@feverTranceShow" Label="FEVER/TRANCE show setting">
@foreach (var item in Enum.GetValues<PlayOptions.FeverTranceShow>())
{
<MudSelectItem Value="@item">@item.GetHelpText()</MudSelectItem>
}
</MudSelect>
<MudButton Disabled="@isSavingOptions" OnClick="SaveOptions" Variant="Variant.Filled" Color="Color.Info">
@if (isSavingOptions)
{
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/>
<MudText Class="ms-2">Saving...</MudText>
}
else
{
<MudIcon Icon="@Icons.Filled.Save"></MudIcon>
<MudText>Save</MudText>
}
</MudButton>
</MudExpansionPanel>
<MudExpansionPanel Text="SongPlayData">
<MudDataGrid T="SongPlayData" Items="@songPlayDataList" Sortable="true" Filterable="true">
<ToolBarContent>
<MudText Typo="Typo.h6">Played Songs</MudText>
</ToolBarContent>
<Columns>
<Column T="SongPlayData" Sortable="false" Filterable="false">
<CellTemplate>
<MudButton Variant="Variant.Outlined" Size="Size.Small"
OnClick="@(() => OnShowDetailsClick(context.Item))">
@(context.Item.ShowDetails ? "Hide" : "Show") Song Play Details
</MudButton>
</CellTemplate>
</Column>
<Column T="SongPlayData" Field="IsFavorite" Sortable="false" Title="Favorite">
<CellTemplate>
<MudToggleIconButton Toggled="@context.Item.IsFavorite"
ToggledChanged="@(()=>OnFavoriteToggled(context.Item))"
Icon="@Icons.Material.Filled.FavoriteBorder" Color="@Color.Secondary" Title="Add to favorite"
ToggledIcon="@Icons.Material.Filled.Favorite" ToggledColor="@Color.Secondary" ToggledTitle="Remove from favorite"/>
</CellTemplate>
</Column>
<Column T="SongPlayData" Field="Title" Title="Song Title"/>
<Column T="SongPlayData" Field="Artist" Title="Artist"/>
<Column T="SongPlayData" Field="TotalPlayCount" Title="Total Play Count" />
</Columns>
<ChildRowContent>
@if (context.ShowDetails)
{
<MudTr>
<td colspan="5">
<MudCard Elevation="0">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.body1">Song Play Details</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent Class="pa-0">
<MudTable Items="@context.SongPlaySubDataList" Context="SongPlayDetail" Elevation="0" Filter="data => data.ClearState != ClearState.NotPlayed">
<HeaderContent>
<MudTh>Difficulty</MudTh>
<MudTh>Clear State</MudTh>
<MudTh>Play Count</MudTh>
<MudTh>Rating</MudTh>
<MudTh>Score</MudTh>
<MudTh>Max Chain</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Difficulty">@SongPlayDetail.Difficulty</MudTd>
<MudTd DataLabel="Clear State">
@(SongPlayDetail.Score != 1000000 ? SongPlayDetail.ClearState : ClearState.Perfect)
</MudTd>
<MudTd DataLabel="Play Count">@SongPlayDetail.PlayCount</MudTd>
<MudTd DataLabel="Rating">@CalculateRating(SongPlayDetail.Score)</MudTd>
<MudTd DataLabel="Score">@SongPlayDetail.Score</MudTd>
<MudTd DataLabel="Max Chain">@SongPlayDetail.MaxChain</MudTd>
</RowTemplate>
</MudTable>
</MudCardContent>
</MudCard>
</td>
</MudTr>
}
</ChildRowContent>
<PagerContent>
<MudDataGridPager T="SongPlayData"/>
</PagerContent>
</MudDataGrid>
</MudExpansionPanel>
</MudExpansionPanels>
}
</MudContainer>
@code {
[Parameter]
public long CardId { get; set; }
private PlayOptions.FeverTranceShow feverTranceShow;
private PlayOptions.FastSlowIndicator fastSlowIndicator;
private UserDetail? userDetail;
private List<SongPlayData> songPlayDataList = new();
private bool isSavingOptions;
protected override async Task OnInitializedAsync()
@if (pageLoading)
{
await base.OnInitializedAsync();
userDetail = await Client.GetFromJsonAsync<UserDetail>($"api/UserDetail/{CardId}") ?? new();
songPlayDataList = userDetail.SongPlayDataList ?? new List<SongPlayData>();
feverTranceShow = userDetail.PlayOption.FeverTrance;
fastSlowIndicator = userDetail.PlayOption.FastSlowIndicator;
<MudSkeleton Width="1184px" Height="57px"/>
<MudSkeleton Width="1184px" Height="57px"/>
<MudSkeleton Width="1184px" Height="57px"/>
}
private void OnShowDetailsClick(SongPlayData data)
else
{
data.ShowDetails = !data.ShowDetails;
}
private async Task SaveOptions()
{
isSavingOptions = true;
var postData = new PlayOption
if (userDetail is null)
{
CardId = CardId,
FastSlowIndicator = fastSlowIndicator,
FeverTrance = feverTranceShow
};
var result = await Client.PostAsJsonAsync("api/UserDetail/SetPlayOption", postData);
isSavingOptions = false;
}
private static string CalculateRating(int score)
{
var grade = SharedConstants.GRADES.Where(g => g.Score <= score).Select(g => g.Grade).Last();
return grade;
}
private async Task OnFavoriteToggled(SongPlayData data)
{
var options = new DialogOptions
{
CloseOnEscapeKey = false,
DisableBackdropClick = true,
FullWidth = true
};
var parameters = new DialogParameters();
parameters.Add("Data", data);
parameters.Add("CardId", CardId);
var dialog = DialogService.Show<FavoriteDialog>("Favorite", parameters, options);
var result = await dialog.Result;
if (result.Cancelled)
{
return;
<MudText Typo="Typo.h3">No Data</MudText>
}
if ((bool)result.Data)
else
{
Logger.LogInformation("Changed!");
data.IsFavorite = !data.IsFavorite;
<MudExpansionPanels>
<MudExpansionPanel Text="Total Result">
<MudList>
<MudListSubheader>Player Name: @userDetail.PlayerName</MudListSubheader>
<MudListItem>Total Score: @userDetail.TotalScore</MudListItem>
<MudListItem>Average Score: @userDetail.AverageScore</MudListItem>
<MudListItem>Played Song Count: @userDetail.PlayedSongCount / @userDetail.TotalSongCount</MudListItem>
<MudListItem>Cleared Stage Count: @userDetail.ClearedStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>No Miss Stage Count: @userDetail.NoMissStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>Full Chain Stage Count: @userDetail.FullChainStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>Perfect Stage Count: @userDetail.PerfectStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>S and Above Stage Count: @userDetail.SAboveStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>S+ and Above Stage Count: @userDetail.SPlusAboveStageCount / @userDetail.TotalStageCount</MudListItem>
<MudListItem>S++ and Above Stage Count: @userDetail.SPlusPlusAboveStageCount / @userDetail.TotalStageCount</MudListItem>
</MudList>
</MudExpansionPanel>
<MudExpansionPanel Text="PlayOptions">
<MudSelect @bind-Value="@playOption.FastSlowIndicator"
Label="FAST/SLOW show setting">
@foreach (var item in Enum.GetValues<PlayOptions.FastSlowIndicator>())
{
<MudSelectItem Value="@item">@item.GetHelpText()</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@playOption.FeverTrance"
Label="FEVER/TRANCE show setting">
@foreach (var item in Enum.GetValues<PlayOptions.FeverTranceShow>())
{
<MudSelectItem Value="@item">@item.GetHelpText()</MudSelectItem>
}
</MudSelect>
<MudAutocomplete T="long" Label="Avatar setting"
@bind-Value="@playOption.AvatarId"
CoerceText="true" SearchFunc="@SearchAvatar"
ToStringFunc="@AvatarIdToString"
Dense="true"
MaxItems="@avatarMaxItems">
<MoreItemsTemplate>
@*<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
OnClick="() => { avatarMaxItems += 10; AvatarAutoComplete.Clear();}">
Load more options
</MudButton>*@
<MudText Align="Align.Center">
Only first 50 items are displayed
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
<MudAutocomplete T="long" Label="Navigator setting"
@bind-Value="@playOption.NavigatorId"
CoerceText="true" SearchFunc="@SearchNavigator"
ToStringFunc="@NavigatorIdToString"
Dense="true"
MaxItems="@avatarMaxItems">
<MoreItemsTemplate>
@*<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
OnClick="() => { avatarMaxItems += 10; AvatarAutoComplete.Clear();}">
Load more options
</MudButton>*@
<MudText Align="Align.Center">
Only first 50 items are displayed
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
<MudAutocomplete T="long" Label="Title setting"
@bind-Value="@playOption.TitleId"
CoerceText="true" SearchFunc="@SearchTitle"
ToStringFunc="@TitleIdToString"
Dense="true"
MaxItems="@avatarMaxItems">
<MoreItemsTemplate>
@*<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
OnClick="() => { avatarMaxItems += 10; AvatarAutoComplete.Clear();}">
Load more options
</MudButton>*@
<MudText Align="Align.Center">
Only first 50 items are displayed
</MudText>
</MoreItemsTemplate>
</MudAutocomplete>
<MudButton Disabled="@isSavingOptions"
OnClick="SaveOptions"
Variant="Variant.Filled"
Color="Color.Info">
@if (isSavingOptions)
{
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/>
<MudText Class="ms-2">Saving...</MudText>
}
else
{
<MudIcon Icon="@Icons.Filled.Save"></MudIcon>
<MudText>Save</MudText>
}
</MudButton>
</MudExpansionPanel>
<MudExpansionPanel Text="SongPlayData">
<MudDataGrid T="SongPlayData"
Items="@songPlayDataList"
Sortable="true"
Filterable="true">
<ToolBarContent>
<MudText Typo="Typo.h6">Played Songs</MudText>
</ToolBarContent>
<Columns>
<Column T="SongPlayData" Sortable="false" Filterable="false">
<CellTemplate>
<MudButton Variant="Variant.Outlined" Size="Size.Small"
OnClick="@(() => OnShowDetailsClick(context.Item))">
@(context.Item.ShowDetails ? "Hide" : "Show") Song Play Details
</MudButton>
</CellTemplate>
</Column>
<Column T="SongPlayData" Field="IsFavorite" Sortable="false" Title="Favorite">
<CellTemplate>
<MudToggleIconButton Toggled="@context.Item.IsFavorite"
ToggledChanged="@(() => OnFavoriteToggled(context.Item))"
Icon="@Icons.Material.Filled.FavoriteBorder"
Color="@Color.Secondary"
Title="Add to favorite"
ToggledIcon="@Icons.Material.Filled.Favorite"
ToggledColor="@Color.Secondary"
ToggledTitle="Remove from favorite"/>
</CellTemplate>
</Column>
<Column T="SongPlayData" Field="Title" Title="Song Title"/>
<Column T="SongPlayData" Field="Artist" Title="Artist"/>
<Column T="SongPlayData" Field="TotalPlayCount" Title="Total Play Count"/>
<Column T="SongPlayData" Field="LastPlayTime" Title="Last Play Time"/>
</Columns>
<ChildRowContent>
@if (context.ShowDetails)
{
<MudTr>
<td colspan="5">
<MudCard Elevation="0">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.body1">Song Play Details</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent Class="pa-0">
<MudTable Items="@context.SongPlaySubDataList"
Context="SongPlayDetail"
Elevation="0"
Filter="data => data.ClearState != ClearState.NotPlayed">
<HeaderContent>
<MudTh>Difficulty</MudTh>
<MudTh>Clear State</MudTh>
<MudTh>Play Count</MudTh>
<MudTh>Rating</MudTh>
<MudTh>Score</MudTh>
<MudTh>Max Chain</MudTh>
<MudTh>Last Play Time</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Difficulty">@SongPlayDetail.Difficulty</MudTd>
<MudTd DataLabel="Clear State">
@(SongPlayDetail.Score != 1000000 ? SongPlayDetail.ClearState : ClearState.Perfect)
</MudTd>
<MudTd DataLabel="Play Count">@SongPlayDetail.PlayCount</MudTd>
<MudTd DataLabel="Rating">@CalculateRating(SongPlayDetail.Score)</MudTd>
<MudTd DataLabel="Score">@SongPlayDetail.Score</MudTd>
<MudTd DataLabel="Max Chain">@SongPlayDetail.MaxChain</MudTd>
<MudTd DataLabel="Last Play Time">@SongPlayDetail.LastPlayTime</MudTd>
</RowTemplate>
</MudTable>
</MudCardContent>
</MudCard>
</td>
</MudTr>
}
</ChildRowContent>
<PagerContent>
<MudDataGridPager T="SongPlayData"/>
</PagerContent>
</MudDataGrid>
</MudExpansionPanel>
</MudExpansionPanels>
}
}
}
</MudContainer>
+163
View File
@@ -0,0 +1,163 @@
using System.Net.Http.Json;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using SharedProject.common;
using SharedProject.models;
namespace MudAdmin.Pages;
public partial class User
{
[Inject]
public HttpClient Client { get; set; } = null!;
[Inject]
public IDialogService DialogService { get; set; } = null!;
[Inject]
public ILogger<User> Logger { get; set; } = null!;
[Parameter]
public long CardId { get; set; }
private PlayOption playOption = new();
private UserDetail? userDetail;
private List<SongPlayData> songPlayDataList = new();
private Dictionary<long, Navigator> navigatorDictionary = new();
private Dictionary<long, Title> titleDictionary = new();
private Dictionary<long, Avatar> avatarDictionary = new();
private bool isSavingOptions;
private int avatarMaxItems = 50;
private bool pageLoading = true;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
userDetail = await Client.GetFromJsonAsync<UserDetail>($"api/UserDetail/{CardId}");
if (userDetail is null)
{
pageLoading = false;
return;
}
songPlayDataList = userDetail.SongPlayDataList ?? new List<SongPlayData>();
playOption = userDetail.PlayOption;
var navigators = await Client.GetFromJsonAsync<Navigators>("data/navigator.json");
if (navigators?.NavigatorList != null)
{
this.navigatorDictionary = navigators.NavigatorList.ToDictionary(navigator => (long)navigator.Id);
}
var avatars = await Client.GetFromJsonAsync<Avatar[]>("data/avatar.json");
if (avatars != null)
{
this.avatarDictionary = avatars.ToDictionary(avatar => (long)avatar.Id);
}
var titles = await Client.GetFromJsonAsync<Title[]>("data/title.json");
if (titles != null)
{
this.titleDictionary = titles.ToDictionary(title => (long)title.Id);
}
pageLoading = false;
}
private void OnShowDetailsClick(SongPlayData data)
{
data.ShowDetails = !data.ShowDetails;
}
private async Task SaveOptions()
{
isSavingOptions = true;
var postData = new PlayOption
{
CardId = CardId,
FastSlowIndicator = playOption.FastSlowIndicator,
FeverTrance = playOption.FeverTrance,
AvatarId = playOption.AvatarId,
NavigatorId = playOption.NavigatorId,
TitleId = playOption.TitleId
};
var result = await Client.PostAsJsonAsync("api/UserDetail/SetPlayOption", postData);
isSavingOptions = false;
}
private static string CalculateRating(int score)
{
var grade = SharedConstants.GRADES.Where(g => g.Score <= score).Select(g => g.Grade).Last();
return grade;
}
private async Task OnFavoriteToggled(SongPlayData data)
{
var options = new DialogOptions
{
CloseOnEscapeKey = false,
DisableBackdropClick = true,
FullWidth = true
};
var parameters = new DialogParameters();
parameters.Add("Data", data);
parameters.Add("CardId", CardId);
var dialog = DialogService.Show<FavoriteDialog>("Favorite", parameters, options);
var result = await dialog.Result;
if (result.Cancelled)
{
return;
}
if ((bool)result.Data)
{
Logger.LogInformation("Changed!");
data.IsFavorite = !data.IsFavorite;
}
}
private Task<IEnumerable<long>> SearchAvatar(string value)
{
var result = string.IsNullOrEmpty(value) ?
avatarDictionary.Keys :
avatarDictionary.Where(pair => pair.Value.ToString().Contains(value, StringComparison.InvariantCultureIgnoreCase)).Select(pair => pair.Key);
return Task.FromResult(result);
}
private Task<IEnumerable<long>> SearchTitle(string value)
{
var result = string.IsNullOrEmpty(value) ?
titleDictionary.Keys :
titleDictionary.Where(pair => pair.Value.ToString().Contains(value, StringComparison.InvariantCultureIgnoreCase)).Select(pair => pair.Key);
return Task.FromResult(result);
}
private Task<IEnumerable<long>> SearchNavigator(string value)
{
var result = string.IsNullOrEmpty(value) ?
navigatorDictionary.Keys :
navigatorDictionary.Where(pair => pair.Value.ToString().Contains(value, StringComparison.InvariantCultureIgnoreCase)).Select(pair => pair.Key);
return Task.FromResult(result);
}
private string AvatarIdToString(long id)
{
return avatarDictionary.ContainsKey(id) ? avatarDictionary[id].ToString() : $"No Data for {id}!";
}
private string NavigatorIdToString(long id)
{
return navigatorDictionary.ContainsKey(id) ? navigatorDictionary[id].ToString() : $"No Data for {id}!";
}
private string TitleIdToString(long id)
{
return titleDictionary.ContainsKey(id) ? titleDictionary[id].ToString() : $"No Data for {id}!";
}
}
+1 -1
View File
@@ -30,7 +30,7 @@
}
</MudGrid>
}
else if (!(users.Count == 0))
else if (users.Count != 0)
{
<MudGrid>
@foreach (var user in users)
-105
View File
@@ -1,105 +0,0 @@
using SharedProject.models;
using GenFu;
using SharedProject.enums;
namespace MudAdmin.Utils;
public class MockDataRepo
{
private static readonly MockDataRepo INSTANCE = new MockDataRepo();
public List<User> Users { get; }
public List<UserDetail> UserDetails { get; private set; } = null!;
public List<SongPlayData> SongPlayDataList { get; private set; } = null!;
private MockDataRepo()
{
ConfigureGenFu();
Users = GenFu.GenFu.ListOf<User>(10);
GenerateUserDetails();
GenerateSongPlayData();
}
private void GenerateSongPlayData()
{
SongPlayDataList = GenFu.GenFu.ListOf<SongPlayData>();
foreach (var songPlayData in SongPlayDataList)
{
var subDataList = new List<SongPlayDetailData>();
var random = new Random();
foreach (var difficulty in Enum.GetValues<Difficulty>())
{
if (random.Next() <= int.MaxValue / 2)
{
continue;
}
var subData = GenFu.GenFu.New<SongPlayDetailData>();
subData.Difficulty = difficulty;
if (subData.ClearState == ClearState.Perfect)
{
subData.ClearState = ClearState.FullChain;
}
subDataList.Add(subData);
}
songPlayData.SongPlaySubDataList = subDataList.ToArray();
}
}
private void GenerateUserDetails()
{
UserDetails = new List<UserDetail>();
foreach (var user in Users)
{
var detail = GenFu.GenFu.New<UserDetail>();
detail.CardId = user.CardId;
detail.PlayerName = user.PlayerName;
detail.PlayOption = new PlayOption
{
CardId = user.CardId,
FeverTrance = PlayOptions.FeverTranceShow.Show,
FastSlowIndicator = PlayOptions.FastSlowIndicator.NotUsed
};
detail.AverageScore = 900000;
detail.TotalScore = 10000000;
detail.TotalSongCount = 123;
detail.TotalStageCount = 390;
UserDetails.Add(detail);
}
}
public static MockDataRepo GetMockDataRepo()
{
return INSTANCE;
}
private void ConfigureGenFu()
{
GenFu.GenFu.Configure<User>()
.Fill(user => user.CardId, () => new Random().NextInt64(7000000000000000, 8000000000000000));
GenFu.GenFu.Configure<UserDetail>()
.Fill(detail => detail.PlayedSongCount).WithinRange(100, 123)
.Fill(detail => detail.ClearedStageCount).WithinRange(300, 390)
.Fill(detail => detail.NoMissStageCount).WithinRange(200, 300)
.Fill(detail => detail.FullChainStageCount).WithinRange(100, 200)
.Fill(detail => detail.PerfectStageCount).WithinRange(0, 100)
.Fill(detail => detail.SAboveStageCount).WithinRange(200, 300)
.Fill(detail => detail.SPlusAboveStageCount).WithinRange(100, 200)
.Fill(detail => detail.SPlusPlusAboveStageCount).WithinRange(0, 100);
GenFu.GenFu.Configure<SongPlayDetailData>()
.Fill(data => data.Score).WithinRange(0, 1000001);
GenFu.GenFu.Configure<SongPlayData>()
.Fill(data => data.ShowDetails, false);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
namespace SharedProject.enums;
public enum NavigatorDefaultAvailability
{
NotAvailable = 0,
Available = 1,
AvailableWithVoice = 2,
}
+11
View File
@@ -0,0 +1,11 @@
namespace SharedProject.enums;
public enum NavigatorGenre
{
Default = 1,
Original = 2,
Game = 3,
Touhou = 4,
Vocaloid = 5,
Collab = 6,
}
+36
View File
@@ -0,0 +1,36 @@
namespace SharedProject.enums;
public enum TitleUnlockType
{
Invalid = 0,
Default = 1,
Clear = 2,
NoMiss = 3,
FullChain = 4,
SRankSimpleStages = 5,
SRankNormalStages = 6,
SRankHardStages = 7,
SRankExtraStages = 8,
SRankAllDifficulties = 9,
SPlusRankAllDifficulties = 10,
SPlusPlusRankAllDifficulties = 11,
Event = 12,
Prefecture = 13,
ChainMilestone = 14,
Adlibs = 15,
ConsecutiveNoMiss = 16,
ClearsUsingItems = 17,
Avatars = 18,
MultiplayerStarsTotal = 19,
SongSet20 = 20,
SongSet21 = 21,
SongSet22 = 22,
SongSet23 = 23,
SongSet24 = 24,
SongSet25 = 25,
SongSet26 = 26,
ProfileLevel = 27,
Perfect = 28,
OnlineMatching = 29,
Trophies = 30,
}
+15
View File
@@ -0,0 +1,15 @@
namespace SharedProject.models;
public class Avatar
{
public uint Id { get; set; }
public string? IdString { get; set; }
public string? FullName { get; set; }
public string? Name { get; set; }
public string? Variant { get; set; }
public string? AcquireMethod { get; set; }
public override string ToString() {
return $"{Id}: {FullName}, {AcquireMethod}";
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace SharedProject.models;
public class NameEntry
{
public string? NameWithVariant { get; set; }
public string? NameWithoutVariant{ get; set; }
public string? Variant{ get; set; }
public string? IllustrationCredit{ get; set; }
public override string ToString() {
return $"{NameWithVariant}";
}
}
+25
View File
@@ -0,0 +1,25 @@
using SharedProject.enums;
namespace SharedProject.models;
public class Navigator
{
public uint Id { get; set; }
public string? IdString { get; set; }
public string? FileName { get; set; }
public NameEntry? NameEntry0 { get; set; }
public NameEntry? NameEntry1 { get; set; }
public NavigatorGenre Genre { get; set; }
public NavigatorDefaultAvailability DefaultAvailability { get; set; }
public string? ToolTipJp { get; set; }
public string? ToolTipEn { get; set; }
public override string ToString() {
return $"{Id}: {NameEntry1}, {ToolTipEn}";
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace SharedProject.models;
public class Navigators
{
public int Count { get; set; }
public List<Navigator>? NavigatorList { get; set; }
}
+9
View File
@@ -13,4 +13,13 @@ public class PlayOption
[JsonPropertyName(nameof(FeverTrance))]
public PlayOptions.FeverTranceShow FeverTrance { get; set; }
[JsonPropertyName(nameof(AvatarId))]
public long AvatarId { get; set; }
[JsonPropertyName(nameof(NavigatorId))]
public long NavigatorId { get; set; }
[JsonPropertyName(nameof(TitleId))]
public long TitleId { get; set; }
}
+12
View File
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using SharedProject.common;
using SharedProject.enums;
namespace SharedProject.models;
@@ -25,4 +26,15 @@ public class SongPlayData
return SongPlaySubDataList.Sum(data => data.PlayCount);
}
}
[JsonIgnore]
public DateTime LastPlayTime
{
get
{
var songPlayDetailData = SongPlaySubDataList.Where(data => data.ClearState != ClearState.NotPlayed)
.MinBy(data => data.LastPlayTime);
return songPlayDetailData?.LastPlayTime ?? DateTime.MaxValue;
}
}
}
@@ -13,4 +13,6 @@ public class SongPlayDetailData
public Difficulty Difficulty { get; set; }
public ClearState ClearState { get; set; }
public DateTime LastPlayTime { get; set; }
}
+17
View File
@@ -0,0 +1,17 @@
using SharedProject.enums;
namespace SharedProject.models;
public class Title
{
public uint Id { get; set; }
public string? IdString { get; set; }
public string? NameJp { get; set; }
public string? NameEng { get; set; }
public string? UnlockRequirementJp { get; set; }
public string? UnlockRequirementEng { get; set; }
public TitleUnlockType Type { get; set; }
public override string ToString() {
return $"{Id}: {NameEng}, {UnlockRequirementEng}";
}
}