Merge pull request #29 from asesidaa/LoginRefactor
Implemented better authentication system, completed more localization…
This commit is contained in:
commit
e5d30c8ca2
491
GameDatabase/Migrations/20240203182355_AddUnlockedUraSongIdListToUserDatum.Designer.cs
generated
Normal file
491
GameDatabase/Migrations/20240203182355_AddUnlockedUraSongIdListToUserDatum.Designer.cs
generated
Normal file
@ -0,0 +1,491 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GameDatabase.Context;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GameDatabase.Migrations
|
||||
{
|
||||
[DbContext(typeof(TaikoDbContext))]
|
||||
[Migration("20240203182355_AddUnlockedUraSongIdListToUserDatum")]
|
||||
partial class AddUnlockedUraSongIdListToUserDatum
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1");
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
|
||||
{
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SongId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Difficulty")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsWin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Baid", "SongId", "Difficulty");
|
||||
|
||||
b.ToTable("AiScoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
|
||||
{
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SongId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Difficulty")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SectionIndex")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Crown")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("DrumrollCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("GoodCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsWin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("MissCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("OkCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Score")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Baid", "SongId", "Difficulty", "SectionIndex");
|
||||
|
||||
b.ToTable("AiSectionScoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
|
||||
{
|
||||
b.Property<string>("AccessCode")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("AccessCode");
|
||||
|
||||
b.HasIndex("Baid");
|
||||
|
||||
b.ToTable("Card", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.Credential", b =>
|
||||
{
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Baid");
|
||||
|
||||
b.ToTable("Credential", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
|
||||
{
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("DanId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("DanType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<uint>("ArrivalSongCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ClearState")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(0u);
|
||||
|
||||
b.Property<uint>("ComboCountTotal")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SoulGaugeTotal")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Baid", "DanId", "DanType");
|
||||
|
||||
b.ToTable("DanScoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
|
||||
{
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("DanId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("DanType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<uint>("SongNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("BadCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ComboCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("DrumrollCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("GoodCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("HighScore")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("OkCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("PlayScore")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("TotalHitCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Baid", "DanId", "DanType", "SongNumber");
|
||||
|
||||
b.ToTable("DanStageScoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
|
||||
{
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SongId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Difficulty")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("BestCrown")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("BestRate")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("BestScore")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("BestScoreRank")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Baid", "SongId", "Difficulty");
|
||||
|
||||
b.ToTable("SongBestData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("Baid")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ComboCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Crown")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("Difficulty")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("DrumrollCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("GoodCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("HitCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("MissCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("OkCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("PlayTime")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<uint>("Score")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ScoreRank")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ScoreRate")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Skipped")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SongId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SongNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Baid");
|
||||
|
||||
b.ToTable("SongPlayData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.UserDatum", b =>
|
||||
{
|
||||
b.Property<ulong>("Baid")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("AchievementDisplayDifficulty")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AiWinCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ColorBody")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ColorFace")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("ColorLimb")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CostumeData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CostumeFlgArray")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DifficultyPlayedArray")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DifficultySettingArray")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DisplayAchievement")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DisplayDan")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("FavoriteSongsArray")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("GenericInfoFlgArray")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAdmin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsSkipOn")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsVoiceOn")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastPlayDatetime")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<uint>("LastPlayMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MyDonName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("MyDonNameLanguage")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("NotesPosition")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<short>("OptionSetting")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("SelectedToneId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TitleFlgArray")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("TitlePlateId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TokenCountDict")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ToneFlgArray")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UnlockedSongIdList")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Baid");
|
||||
|
||||
b.ToTable("UserData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
|
||||
.WithMany()
|
||||
.HasForeignKey("Baid")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ba");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.AiScoreDatum", "Parent")
|
||||
.WithMany("AiSectionScoreData")
|
||||
.HasForeignKey("Baid", "SongId", "Difficulty")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
|
||||
.WithMany()
|
||||
.HasForeignKey("Baid")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ba");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.Credential", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
|
||||
.WithMany()
|
||||
.HasForeignKey("Baid")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ba");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
|
||||
.WithMany()
|
||||
.HasForeignKey("Baid")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ba");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.DanScoreDatum", "Parent")
|
||||
.WithMany("DanStageScoreData")
|
||||
.HasForeignKey("Baid", "DanId", "DanType")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
|
||||
.WithMany()
|
||||
.HasForeignKey("Baid")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ba");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
|
||||
{
|
||||
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
|
||||
.WithMany()
|
||||
.HasForeignKey("Baid")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ba");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
|
||||
{
|
||||
b.Navigation("AiSectionScoreData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
|
||||
{
|
||||
b.Navigation("DanStageScoreData");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GameDatabase.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddUnlockedUraSongIdListToUserDatum : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
10
SharedProject/Models/Requests/ChangePasswordRequest.cs
Normal file
10
SharedProject/Models/Requests/ChangePasswordRequest.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace SharedProject.Models.Requests;
|
||||
|
||||
public class ChangePasswordRequest
|
||||
{
|
||||
public string AccessCode { get; set; } = string.Empty;
|
||||
|
||||
public string OldPassword { get; set; } = string.Empty;
|
||||
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
}
|
6
SharedProject/Models/Requests/GenerateOtpRequest.cs
Normal file
6
SharedProject/Models/Requests/GenerateOtpRequest.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace SharedProject.Models.Requests;
|
||||
|
||||
public class GenerateOtpRequest
|
||||
{
|
||||
public uint Baid { get; set; }
|
||||
}
|
7
SharedProject/Models/Requests/LoginRequest.cs
Normal file
7
SharedProject/Models/Requests/LoginRequest.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace SharedProject.Models.Requests;
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
public string AccessCode { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
10
SharedProject/Models/Requests/RegisterRequest.cs
Normal file
10
SharedProject/Models/Requests/RegisterRequest.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace SharedProject.Models.Requests;
|
||||
|
||||
public class RegisterRequest
|
||||
{
|
||||
public string AccessCode { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public bool RegisterWithLastPlayTime { get; set; }
|
||||
public DateTime LastPlayDateTime { get; set; }
|
||||
public string InviteCode { get; set; } = string.Empty;
|
||||
}
|
6
SharedProject/Models/Requests/ResetPasswordRequest.cs
Normal file
6
SharedProject/Models/Requests/ResetPasswordRequest.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace SharedProject.Models.Requests;
|
||||
|
||||
public class ResetPasswordRequest
|
||||
{
|
||||
public uint Baid { get; set; }
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace SharedProject.Models.Requests;
|
||||
|
||||
public class SetPasswordRequest
|
||||
{
|
||||
public uint Baid { get; set; }
|
||||
public string Password { get; set; } = default!;
|
||||
public string Salt { get; set; } = default!;
|
||||
}
|
8
SharedProject/Models/Requests/VerifyOtpRequest.cs
Normal file
8
SharedProject/Models/Requests/VerifyOtpRequest.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace SharedProject.Models.Requests;
|
||||
|
||||
public class VerifyOtpRequest
|
||||
{
|
||||
public string Otp { get; set; } = "";
|
||||
|
||||
public uint Baid { get; set; }
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace SharedProject.Models.Responses;
|
||||
|
||||
public class DashboardResponse
|
||||
{
|
||||
public List<User> Users { get; set; } = new();
|
||||
|
||||
public List<UserCredential> UserCredentials { get; set; } = new();
|
||||
}
|
6
SharedProject/Models/Responses/SongHistoryResponse.cs
Normal file
6
SharedProject/Models/Responses/SongHistoryResponse.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace SharedProject.Models.Responses;
|
||||
|
||||
public class SongHistoryResponse
|
||||
{
|
||||
public List<SongHistoryData> SongHistoryData { get; set; } = new();
|
||||
}
|
48
SharedProject/Models/SongHistoryData.cs
Normal file
48
SharedProject/Models/SongHistoryData.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using SharedProject.Enums;
|
||||
|
||||
namespace SharedProject.Models;
|
||||
|
||||
public class SongHistoryData
|
||||
{
|
||||
public uint SongId { get; set; }
|
||||
|
||||
public SongGenre Genre { get; set; }
|
||||
|
||||
public string MusicName { get; set; } = string.Empty;
|
||||
|
||||
public string MusicArtist { get; set; } = string.Empty;
|
||||
|
||||
public Difficulty Difficulty { get; set; }
|
||||
|
||||
public int Stars { get; set; }
|
||||
|
||||
public bool ShowDetails { get; set; }
|
||||
|
||||
public uint Score { get; set; }
|
||||
|
||||
public CrownType Crown { get; set; }
|
||||
|
||||
public ScoreRank ScoreRank { get; set; }
|
||||
|
||||
public DateTime PlayTime { get; set; }
|
||||
|
||||
public bool IsFavorite { get; set; }
|
||||
|
||||
public uint GoodCount { get; set; }
|
||||
|
||||
public uint OkCount { get; set; }
|
||||
|
||||
public uint MissCount { get; set; }
|
||||
|
||||
public uint ComboCount { get; set; }
|
||||
|
||||
public uint HitCount { get; set; }
|
||||
|
||||
public uint DrumrollCount { get; set; }
|
||||
|
||||
public uint SongNumber { get; set; }
|
||||
|
||||
//public List<AiSectionBestData> AiSectionBestData { get; set; } = new();
|
||||
|
||||
//public bool ShowAiData { get; set; }
|
||||
}
|
7
TaikoLocalServer/Configurations/AuthSettings.json
Normal file
7
TaikoLocalServer/Configurations/AuthSettings.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"AuthSettings": {
|
||||
"JwtKey": "SuperSecretKeyAndHeresItsPadding",
|
||||
"JwtIssuer": "http://localhost:5000",
|
||||
"JwtAudience": "http://localhost:5000"
|
||||
}
|
||||
}
|
280
TaikoLocalServer/Controllers/Api/AuthController.cs
Normal file
280
TaikoLocalServer/Controllers/Api/AuthController.cs
Normal file
@ -0,0 +1,280 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using TaikoLocalServer.Settings;
|
||||
using OtpNet;
|
||||
using SharedProject.Models.Requests;
|
||||
using TaikoLocalServer.Filters;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController(IAuthService authService, IUserDatumService userDatumService,
|
||||
IOptions<AuthSettings> settings) : BaseController<AuthController>
|
||||
{
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
private string GenerateJwtToken(uint baid, bool isAdmin)
|
||||
{
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.JwtKey));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, baid.ToString()),
|
||||
new(ClaimTypes.Role, isAdmin ? "Admin" : "User")
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: authSettings.JwtIssuer,
|
||||
audience: authSettings.JwtAudience,
|
||||
expires: DateTime.UtcNow.AddHours(24),
|
||||
signingCredentials: credentials,
|
||||
claims: claims
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
private static string ComputeHash(string inputPassword, string salt)
|
||||
{
|
||||
var encDataByte = Encoding.UTF8.GetBytes(inputPassword + salt);
|
||||
var encodedData = Convert.ToBase64String(encDataByte);
|
||||
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
||||
encodedData = Convert.ToBase64String(encDataByte);
|
||||
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
||||
encodedData = Convert.ToBase64String(encDataByte);
|
||||
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
||||
encodedData = Convert.ToBase64String(encDataByte);
|
||||
return encodedData;
|
||||
}
|
||||
|
||||
private static Totp MakeTotp(uint baid)
|
||||
{
|
||||
var secretKey = (baid * 765 + 2023).ToString();
|
||||
var base32String = Base32Encoding.ToString(Encoding.UTF8.GetBytes(secretKey));
|
||||
var base32Bytes = Base32Encoding.ToBytes(base32String);
|
||||
return new Totp(base32Bytes, step: 999999999);
|
||||
}
|
||||
|
||||
private static string CreateSalt()
|
||||
{
|
||||
//Generate a cryptographic random number.
|
||||
var randomNumber = new byte[32];
|
||||
var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(randomNumber);
|
||||
var salt = Convert.ToBase64String(randomNumber);
|
||||
|
||||
// Return a Base64 string representation of the random number.
|
||||
return salt;
|
||||
}
|
||||
|
||||
private static bool VerifyOtp(string otp, uint baid)
|
||||
{
|
||||
var totp = MakeTotp(baid);
|
||||
return totp.VerifyTotp(otp, out _);
|
||||
}
|
||||
|
||||
[HttpPost("Login")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(LoginRequest loginRequest)
|
||||
{
|
||||
var accessCode = loginRequest.AccessCode;
|
||||
var password = loginRequest.Password;
|
||||
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
return Unauthorized(new { message = "Access Code Not Found" });
|
||||
|
||||
var credential = await authService.GetCredentialByBaid(card.Baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
if (credential.Password == "")
|
||||
return Unauthorized(new { message = "User Not Registered" });
|
||||
|
||||
// Hash the password with the salt
|
||||
var hashedPassword = ComputeHash(password, credential.Salt);
|
||||
|
||||
if (credential.Password != hashedPassword)
|
||||
return Unauthorized(new { message = "Invalid Password" });
|
||||
|
||||
// Get User information
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(card.Baid);
|
||||
if (user == null)
|
||||
return Unauthorized(new { message = "User Does Not Exist" });
|
||||
|
||||
var authToken = GenerateJwtToken(card.Baid, user.IsAdmin);
|
||||
|
||||
// Return the token with key authToken
|
||||
return Ok(new { authToken });
|
||||
}
|
||||
|
||||
[HttpPost("LoginWithToken")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public IActionResult LoginWithToken()
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("Register")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register(RegisterRequest registerRequest)
|
||||
{
|
||||
var accessCode = registerRequest.AccessCode;
|
||||
var password = registerRequest.Password;
|
||||
var lastPlayDateTime = registerRequest.LastPlayDateTime;
|
||||
var registerWithLastPlayTime = registerRequest.RegisterWithLastPlayTime;
|
||||
var inviteCode = registerRequest.InviteCode;
|
||||
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
return Unauthorized(new { message = "Access Code Not Found" });
|
||||
|
||||
var credential = await authService.GetCredentialByBaid(card.Baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
if (credential.Password != "")
|
||||
return Unauthorized(new { message = "User Already Registered" });
|
||||
|
||||
if (registerWithLastPlayTime)
|
||||
{
|
||||
var invited = false;
|
||||
if (inviteCode != "")
|
||||
{
|
||||
invited = VerifyOtp(inviteCode, card.Baid);
|
||||
}
|
||||
|
||||
if (!invited)
|
||||
{
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(card.Baid);
|
||||
if (user == null)
|
||||
return Unauthorized(new { message = "User Does Not Exist" });
|
||||
|
||||
var diffMinutes = (lastPlayDateTime - user.LastPlayDatetime).Duration().TotalMinutes;
|
||||
if (diffMinutes > 5)
|
||||
return Unauthorized(new { message = "Wrong Last Play Time" });
|
||||
}
|
||||
}
|
||||
|
||||
// Hash the password with the salt
|
||||
var salt = CreateSalt();
|
||||
var hashedPassword = ComputeHash(password, salt);
|
||||
|
||||
var result = await authService.UpdatePassword(card.Baid, hashedPassword, salt);
|
||||
return result ? Ok() : Unauthorized( new { message = "Failed to Update Password" });
|
||||
}
|
||||
|
||||
[HttpPost("ChangePassword")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> ChangePassword(ChangePasswordRequest changePasswordRequest)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin)
|
||||
{
|
||||
var requestBaid = authService.GetCardByAccessCode(changePasswordRequest.AccessCode).Result?.Baid;
|
||||
if (requestBaid != tokenInfo.Value.baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var accessCode = changePasswordRequest.AccessCode;
|
||||
var oldPassword = changePasswordRequest.OldPassword;
|
||||
var newPassword = changePasswordRequest.NewPassword;
|
||||
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
return Unauthorized(new { message = "Access Code Not Found" });
|
||||
|
||||
var credential = await authService.GetCredentialByBaid(card.Baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
if (credential.Password == "")
|
||||
return Unauthorized(new { message = "User Not Registered" });
|
||||
|
||||
// Hash the password with the salt
|
||||
var hashedOldPassword = ComputeHash(oldPassword, credential.Salt);
|
||||
if (credential.Password != hashedOldPassword)
|
||||
return Unauthorized(new { message = "Wrong Old Password" });
|
||||
|
||||
// Hash the new password with the salt
|
||||
var salt = CreateSalt();
|
||||
var hashedNewPassword = ComputeHash(newPassword, salt);
|
||||
|
||||
var result = await authService.UpdatePassword(card.Baid, hashedNewPassword, salt);
|
||||
return result ? Ok() : Unauthorized( new { message = "Failed to Update Password" });
|
||||
}
|
||||
|
||||
[HttpPost("ResetPassword")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> ResetPassword(ResetPasswordRequest resetPasswordRequest)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && resetPasswordRequest.Baid != tokenInfo.Value.baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var baid = resetPasswordRequest.Baid;
|
||||
|
||||
var credential = await authService.GetCredentialByBaid(baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
var result = await authService.UpdatePassword(baid, "", "");
|
||||
return result ? Ok() : Unauthorized( new { message = "Failed to Reset Password" });
|
||||
}
|
||||
|
||||
[HttpPost("GenerateOtp")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public IActionResult GenerateOtp(GenerateOtpRequest generateOtpRequest)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var totp = MakeTotp(generateOtpRequest.Baid);
|
||||
return Ok(new { otp = totp.ComputeTotp() });
|
||||
}
|
||||
}
|
@ -1,42 +1,80 @@
|
||||
using SharedProject.Models.Requests;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models.Requests;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CardsController : BaseController<CardsController>
|
||||
public class CardsController(IAuthService authService, IOptions<AuthSettings> settings) : BaseController<CardsController>
|
||||
{
|
||||
private readonly ICardService cardService;
|
||||
|
||||
public CardsController(ICardService cardService)
|
||||
{
|
||||
this.cardService = cardService;
|
||||
}
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpDelete("{accessCode}")]
|
||||
public async Task<IActionResult> DeleteUser(string accessCode)
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> DeleteAccessCode(string accessCode)
|
||||
{
|
||||
var result = await cardService.DeleteCard(accessCode);
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (card.Baid != tokenInfo.Value.baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var result = await authService.DeleteCard(accessCode);
|
||||
|
||||
return result ? NoContent() : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> BindAccessCode(BindAccessCodeRequest request)
|
||||
[HttpPost("BindAccessCode")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> BindAccessCode(BindAccessCodeRequest bindAccessCodeRequest)
|
||||
{
|
||||
var accessCode = request.AccessCode;
|
||||
var baid = request.Baid;
|
||||
var existingCard = await cardService.GetCardByAccessCode(accessCode);
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != bindAccessCodeRequest.Baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var accessCode = bindAccessCodeRequest.AccessCode;
|
||||
var baid = bindAccessCodeRequest.Baid;
|
||||
var existingCard = await authService.GetCardByAccessCode(accessCode);
|
||||
if (existingCard is not null)
|
||||
{
|
||||
return BadRequest("Access code already exists");
|
||||
}
|
||||
|
||||
var newCard = new Card
|
||||
{
|
||||
Baid = baid,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
await cardService.AddCard(newCard);
|
||||
await authService.AddCard(newCard);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using SharedProject.Models.Requests;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CredentialsController : BaseController<CredentialsController>
|
||||
{
|
||||
private readonly ICredentialService credentialService;
|
||||
|
||||
public CredentialsController(ICredentialService credentialService)
|
||||
{
|
||||
this.credentialService = credentialService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> UpdatePassword(SetPasswordRequest request)
|
||||
{
|
||||
var baid = request.Baid;
|
||||
var password = request.Password;
|
||||
var salt = request.Salt;
|
||||
var result = await credentialService.UpdatePassword(baid, password, salt);
|
||||
return result ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
@ -1,23 +1,37 @@
|
||||
using SharedProject.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using SharedProject.Models.Responses;
|
||||
using Swan.Mapping;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class DanBestDataController : BaseController<DanBestDataController>
|
||||
public class DanBestDataController(IDanScoreDatumService danScoreDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<DanBestDataController>
|
||||
{
|
||||
private readonly IDanScoreDatumService danScoreDatumService;
|
||||
|
||||
public DanBestDataController(IDanScoreDatumService danScoreDatumService)
|
||||
{
|
||||
this.danScoreDatumService = danScoreDatumService;
|
||||
}
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> GetDanBestData(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Handle gaiden in here and web ui
|
||||
var danScores = await danScoreDatumService.GetDanScoreDataList(baid, DanType.Normal);
|
||||
var danDataList = new List<DanBestData>();
|
||||
|
@ -1,30 +0,0 @@
|
||||
using SharedProject.Models.Responses;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/[controller]")]
|
||||
public class DashboardController : BaseController<DashboardController>
|
||||
{
|
||||
private readonly ICardService cardService;
|
||||
private readonly ICredentialService credentialService;
|
||||
|
||||
public DashboardController(ICardService cardService, ICredentialService credentialService)
|
||||
{
|
||||
this.cardService = cardService;
|
||||
this.credentialService = credentialService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<DashboardResponse> GetDashboard()
|
||||
{
|
||||
var users = await cardService.GetUsersFromCards();
|
||||
var credentials = await credentialService.GetUserCredentialsFromCredentials();
|
||||
return new DashboardResponse
|
||||
{
|
||||
Users = users,
|
||||
UserCredentials = credentials
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +1,35 @@
|
||||
using SharedProject.Models.Requests;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models.Requests;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class FavoriteSongsController : BaseController<FavoriteSongsController>
|
||||
public class FavoriteSongsController(IUserDatumService userDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<FavoriteSongsController>
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
|
||||
public FavoriteSongsController(IUserDatumService userDatumService)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
}
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpPost]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> UpdateFavoriteSong(SetFavoriteRequest request)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != request.Baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid);
|
||||
|
||||
if (user is null)
|
||||
@ -28,8 +42,23 @@ public class FavoriteSongsController : BaseController<FavoriteSongsController>
|
||||
}
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> GetFavoriteSongs(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
|
||||
if (user is null)
|
||||
|
@ -1,32 +1,38 @@
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using SharedProject.Models.Responses;
|
||||
using SharedProject.Models;
|
||||
using GameDatabase.Entities;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PlayDataController(IUserDatumService userDatumService, ISongBestDatumService songBestDatumService,
|
||||
ISongPlayDatumService songPlayDatumService, IAuthService authService, IOptions<AuthSettings> settings)
|
||||
: BaseController<PlayDataController>
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PlayDataController : BaseController<PlayDataController>
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
private readonly ISongBestDatumService songBestDatumService;
|
||||
private readonly ISongPlayDatumService songPlayDatumService;
|
||||
private readonly SongBestResponseMapper _songBestResponseMapper; // Inject SongBestResponseMapper
|
||||
|
||||
public PlayDataController(IUserDatumService userDatumService, ISongBestDatumService songBestDatumService,
|
||||
ISongPlayDatumService songPlayDatumService, SongBestResponseMapper songBestResponseMapper)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
this.songBestDatumService = songBestDatumService;
|
||||
this.songPlayDatumService = songPlayDatumService;
|
||||
_songBestResponseMapper = songBestResponseMapper; // Assign the injected mapper
|
||||
}
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<ActionResult<SongBestResponse>> GetSongBestRecords(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
if (user is null)
|
||||
{
|
||||
@ -64,12 +70,10 @@ namespace TaikoLocalServer.Controllers.Api
|
||||
SongBestData = songBestRecords
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
|
||||
public partial class SongBestResponseMapper
|
||||
{
|
||||
public static partial SongPlayDatumDto MapToDto(SongPlayDatum entity);
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
|
||||
public partial class SongBestResponseMapper
|
||||
{
|
||||
public static partial SongPlayDatumDto MapToDto(SongPlayDatum entity);
|
||||
}
|
68
TaikoLocalServer/Controllers/Api/PlayHistoryController.cs
Normal file
68
TaikoLocalServer/Controllers/Api/PlayHistoryController.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using SharedProject.Models.Responses;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PlayHistoryController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
|
||||
IAuthService authService, IOptions<AuthSettings> settings) : BaseController<PlayDataController>
|
||||
{
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<ActionResult<SongHistoryResponse>> GetSongHistory(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var playLogs = await songPlayDatumService.GetSongPlayDatumByBaid(baid);
|
||||
var songHistory = playLogs.Select(play => new SongHistoryData
|
||||
{
|
||||
SongId = play.SongId,
|
||||
Difficulty = play.Difficulty,
|
||||
Score = play.Score,
|
||||
ScoreRank = play.ScoreRank,
|
||||
Crown = play.Crown,
|
||||
GoodCount = play.GoodCount,
|
||||
OkCount = play.OkCount,
|
||||
MissCount = play.MissCount,
|
||||
HitCount = play.HitCount,
|
||||
DrumrollCount = play.DrumrollCount,
|
||||
ComboCount = play.ComboCount,
|
||||
PlayTime = play.PlayTime,
|
||||
SongNumber = play.SongNumber
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var favoriteSongs = await userDatumService.GetFavoriteSongIds(baid);
|
||||
var favoriteSet = favoriteSongs.ToHashSet();
|
||||
foreach (var song in songHistory.Where(song => favoriteSet.Contains(song.SongId)))
|
||||
{
|
||||
song.IsFavorite = true;
|
||||
}
|
||||
|
||||
return Ok(new SongHistoryResponse{SongHistoryData = songHistory});
|
||||
}
|
||||
}
|
@ -1,22 +1,36 @@
|
||||
using SharedProject.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using SharedProject.Utils;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/[controller]/{baid}")]
|
||||
public class UserSettingsController : BaseController<UserSettingsController>
|
||||
public class UserSettingsController(IUserDatumService userDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<UserSettingsController>
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
|
||||
public UserSettingsController(IUserDatumService userDatumService)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
}
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<ActionResult<UserSetting>> GetUserSetting(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
|
||||
if (user is null)
|
||||
@ -75,8 +89,23 @@ public class UserSettingsController : BaseController<UserSettingsController>
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> SaveUserSetting(uint baid, UserSetting userSetting)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
|
||||
if (user is null)
|
||||
|
@ -1,19 +1,78 @@
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class UsersController : BaseController<UsersController>
|
||||
public class UsersController(IUserDatumService userDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<UsersController>
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
public UsersController(IUserDatumService userDatumService)
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<User?> GetUser(uint baid)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var user = await authService.GetUserByBaid(baid);
|
||||
return user;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IEnumerable<User>> GetUsers()
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Array.Empty<User>();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Array.Empty<User>();
|
||||
}
|
||||
}
|
||||
|
||||
return await authService.GetUsersFromCards();
|
||||
}
|
||||
|
||||
[HttpDelete("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> DeleteUser(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var result = await userDatumService.DeleteUser(baid);
|
||||
|
||||
return result ? NoContent() : NotFound();
|
||||
|
39
TaikoLocalServer/Filters/AuthorizeIfRequiredAttribute.cs
Normal file
39
TaikoLocalServer/Filters/AuthorizeIfRequiredAttribute.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Filters
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class AuthorizeIfRequiredAttribute(IOptions<AuthSettings> settings) : Attribute, IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly bool loginRequired = settings.Value.LoginRequired;
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (!loginRequired)
|
||||
{
|
||||
return; // Skip authorization if login is not required
|
||||
}
|
||||
|
||||
var authorizationService = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
|
||||
var policyProvider = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationPolicyProvider>();
|
||||
var policy = await policyProvider.GetPolicyAsync(AuthorizationPolicyNames.Default);
|
||||
|
||||
if (policy != null)
|
||||
{
|
||||
var authResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, policy);
|
||||
if (!authResult.Succeeded)
|
||||
{
|
||||
context.Result = new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthorizationPolicyNames
|
||||
{
|
||||
public const string Default = "Default";
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace TaikoLocalServer.Models;
|
||||
|
||||
public class MusicInfoes
|
||||
public class MusicInfos
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public List<MusicInfoEntry> MusicInfoEntries { get; set; } = new();
|
@ -1,9 +1,12 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Serilog.Sinks.File.Header;
|
||||
using TaikoLocalServer.Logging;
|
||||
using GameDatabase.Context;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.HttpLogging;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using TaikoLocalServer.Middlewares;
|
||||
using TaikoLocalServer.Services.Extentions;
|
||||
using TaikoLocalServer.Settings;
|
||||
@ -11,6 +14,7 @@ using Throw;
|
||||
using Serilog;
|
||||
using SharedProject.Utils;
|
||||
using TaikoLocalServer.Controllers.Api;
|
||||
using TaikoLocalServer.Filters;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console()
|
||||
@ -39,6 +43,8 @@ try
|
||||
builder.Configuration.AddJsonFile($"{configurationsDirectory}/Database.json", optional: false, reloadOnChange: false);
|
||||
builder.Configuration.AddJsonFile($"{configurationsDirectory}/ServerSettings.json", optional: false, reloadOnChange: false);
|
||||
builder.Configuration.AddJsonFile($"{configurationsDirectory}/DataSettings.json", optional: true, reloadOnChange: false);
|
||||
builder.Configuration.AddJsonFile($"{configurationsDirectory}/AuthSettings.json", optional: true, reloadOnChange: false);
|
||||
builder.Configuration.AddJsonFile("wwwroot/appsettings.json", optional: true, reloadOnChange: true); // Add appsettings.json
|
||||
|
||||
builder.Host.UseSerilog((context, configuration) =>
|
||||
{
|
||||
@ -65,6 +71,34 @@ try
|
||||
builder.Services.AddSingleton<IGameDataService, GameDataService>();
|
||||
builder.Services.Configure<ServerSettings>(builder.Configuration.GetSection(nameof(ServerSettings)));
|
||||
builder.Services.Configure<DataSettings>(builder.Configuration.GetSection(nameof(DataSettings)));
|
||||
builder.Services.Configure<AuthSettings>(builder.Configuration.GetSection(nameof(AuthSettings)));
|
||||
|
||||
// Read LoginRequired setting from appsettings.json
|
||||
var loginRequired = builder.Configuration.GetValue<bool>("LoginRequired");
|
||||
builder.Services.Configure<AuthSettings>(options => { options.LoginRequired = loginRequired; });
|
||||
|
||||
// Add Authentication with JWT
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = builder.Configuration.GetSection(nameof(AuthSettings))["JwtIssuer"],
|
||||
ValidAudience = builder.Configuration.GetSection(nameof(AuthSettings))["JwtAudience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection(nameof(AuthSettings))["JwtKey"] ?? throw new InvalidOperationException()))
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<AuthorizeIfRequiredAttribute>(); // Register the custom attribute
|
||||
|
||||
builder.Services.AddControllers().AddProtoBufNet();
|
||||
builder.Services.AddDbContext<TaikoDbContext>(option =>
|
||||
{
|
||||
@ -126,6 +160,9 @@ try
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
||||
// Enable Authentication and Authorization middleware
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseHttpLogging();
|
||||
app.Use(async (context, next) =>
|
||||
@ -136,6 +173,7 @@ try
|
||||
{
|
||||
Log.Error("Unknown request from: {RemoteIpAddress} {Method} {Path} {StatusCode}",
|
||||
context.Connection.RemoteIpAddress, context.Request.Method, context.Request.Path, context.Response.StatusCode);
|
||||
Log.Error("Request headers: {Headers}", context.Request.Headers);
|
||||
}
|
||||
});
|
||||
app.MapControllers();
|
||||
|
139
TaikoLocalServer/Services/AuthService.cs
Normal file
139
TaikoLocalServer/Services/AuthService.cs
Normal file
@ -0,0 +1,139 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using GameDatabase.Context;
|
||||
using SharedProject.Models;
|
||||
using Swan.Mapping;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class AuthService(TaikoDbContext context) : IAuthService
|
||||
{
|
||||
public async Task<Card?> GetCardByAccessCode(string accessCode)
|
||||
{
|
||||
return await context.Cards.FindAsync(accessCode);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserByBaid(uint baid)
|
||||
{
|
||||
var userDatum = await context.UserData.FindAsync(baid);
|
||||
if (userDatum == null) return null;
|
||||
var cardEntries = await context.Cards.Where(card => card.Baid == baid).ToListAsync();
|
||||
return new User
|
||||
{
|
||||
Baid = userDatum.Baid,
|
||||
AccessCodes = cardEntries.Select(card => card.AccessCode).ToList(),
|
||||
IsAdmin = userDatum.IsAdmin
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<User>> GetUsersFromCards()
|
||||
{
|
||||
var cardEntries = await context.Cards.ToListAsync();
|
||||
var userEntries = await context.UserData.ToListAsync();
|
||||
var users = userEntries.Select(userEntry => new User
|
||||
{
|
||||
Baid = userEntry.Baid,
|
||||
AccessCodes = cardEntries.Where(cardEntry => cardEntry.Baid == userEntry.Baid).Select(cardEntry => cardEntry.AccessCode).ToList(),
|
||||
IsAdmin = userEntry.IsAdmin
|
||||
}).ToList();
|
||||
return users;
|
||||
}
|
||||
|
||||
public async Task AddCard(Card card)
|
||||
{
|
||||
context.Add(card);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCard(string accessCode)
|
||||
{
|
||||
var card = await context.Cards.FindAsync(accessCode);
|
||||
if (card == null) return false;
|
||||
context.Cards.Remove(card);
|
||||
await context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<List<UserCredential>> GetUserCredentialsFromCredentials()
|
||||
{
|
||||
return await context.Credentials.Select(credential => credential.CopyPropertiesToNew<UserCredential>(null)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddCredential(Credential credential)
|
||||
{
|
||||
context.Add(credential);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCredential(uint baid)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
context.Credentials.Remove(credential);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePassword(uint baid, string password, string salt)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
credential.Password = password;
|
||||
credential.Salt = salt;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Credential?> GetCredentialByBaid(uint baid)
|
||||
{
|
||||
return await context.Credentials.FindAsync(baid);
|
||||
}
|
||||
|
||||
public (uint baid, bool isAdmin)? ExtractTokenInfo(HttpContext httpContext)
|
||||
{
|
||||
var authHeader = httpContext.Request.Headers.Authorization.FirstOrDefault();
|
||||
if (authHeader == null || !authHeader.StartsWith("Bearer "))
|
||||
{
|
||||
Console.WriteLine("Invalid auth header");
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = authHeader["Bearer ".Length..].Trim();
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
if (!handler.CanReadToken(token))
|
||||
{
|
||||
Console.WriteLine("Invalid token");
|
||||
return null;
|
||||
}
|
||||
|
||||
var jwtToken = handler.ReadJwtToken(token);
|
||||
if (jwtToken.ValidTo < DateTime.UtcNow)
|
||||
{
|
||||
Console.WriteLine("Token expired");
|
||||
return null;
|
||||
}
|
||||
|
||||
var claimBaid = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
var claimRole = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value;
|
||||
|
||||
if (claimBaid == null || claimRole == null)
|
||||
{
|
||||
Console.WriteLine("Invalid token claims");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uint.TryParse(claimBaid, out var baid))
|
||||
{
|
||||
Console.WriteLine("Invalid baid");
|
||||
return null;
|
||||
}
|
||||
var isAdmin = claimRole == "Admin";
|
||||
return (baid, isAdmin);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
using GameDatabase.Context;
|
||||
using SharedProject.Models;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class CardService : ICardService
|
||||
{
|
||||
private readonly TaikoDbContext context;
|
||||
|
||||
public CardService(TaikoDbContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public async Task<Card?> GetCardByAccessCode(string accessCode)
|
||||
{
|
||||
return await context.Cards.FindAsync(accessCode);
|
||||
}
|
||||
|
||||
public async Task<List<User>> GetUsersFromCards()
|
||||
{
|
||||
var cardEntries = await context.Cards.ToListAsync();
|
||||
var userEntries = await context.UserData.ToListAsync();
|
||||
var users = userEntries.Select(userEntry => new User
|
||||
{
|
||||
Baid = (uint)userEntry.Baid,
|
||||
AccessCodes = cardEntries.Where(cardEntry => cardEntry.Baid == userEntry.Baid).Select(cardEntry => cardEntry.AccessCode).ToList(),
|
||||
IsAdmin = userEntry.IsAdmin
|
||||
}).ToList();
|
||||
return users;
|
||||
}
|
||||
|
||||
public async Task AddCard(Card card)
|
||||
{
|
||||
context.Add(card);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCard(string accessCode)
|
||||
{
|
||||
var card = await context.Cards.FindAsync(accessCode);
|
||||
if (card == null) return false;
|
||||
context.Cards.Remove(card);
|
||||
await context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
using GameDatabase.Context;
|
||||
using SharedProject.Models;
|
||||
using Swan.Mapping;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class CredentialService : ICredentialService
|
||||
{
|
||||
private readonly TaikoDbContext context;
|
||||
|
||||
public CredentialService(TaikoDbContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public async Task<List<UserCredential>> GetUserCredentialsFromCredentials()
|
||||
{
|
||||
return await context.Credentials.Select(credential => credential.CopyPropertiesToNew<UserCredential>(null)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddCredential(Credential credential)
|
||||
{
|
||||
context.Add(credential);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCredential(uint baid)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
context.Credentials.Remove(credential);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePassword(uint baid, string password, string salt)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
credential.Password = password;
|
||||
credential.Salt = salt;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -4,8 +4,7 @@ public static class ServiceExtensions
|
||||
{
|
||||
public static IServiceCollection AddTaikoDbServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICardService, CardService>();
|
||||
services.AddScoped<ICredentialService, CredentialService>();
|
||||
services.AddScoped<IAuthService, AuthService>();
|
||||
services.AddScoped<IUserDatumService, UserDatumService>();
|
||||
services.AddScoped<ISongPlayDatumService, SongPlayDatumService>();
|
||||
services.AddScoped<ISongBestDatumService, SongBestDatumService>();
|
||||
|
@ -65,11 +65,6 @@ public class GameDataService : IGameDataService
|
||||
return musicsWithUra;
|
||||
}
|
||||
|
||||
public ImmutableDictionary<uint, MusicInfoEntry> GetMusicInfoes()
|
||||
{
|
||||
return musicInfoes;
|
||||
}
|
||||
|
||||
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary()
|
||||
{
|
||||
return movieDataDictionary;
|
||||
@ -161,7 +156,7 @@ public class GameDataService : IGameDataService
|
||||
var shopFolderDataPath = Path.Combine(dataPath, settings.ShopFolderDataFileName);
|
||||
var tokenDataPath = Path.Combine(dataPath, settings.TokenDataFileName);
|
||||
var lockedSongsDataPath = Path.Combine(dataPath, settings.LockedSongsDataFileName);
|
||||
var qrCodeDataPath = Path.Combine(dataPath, settings.QRCodeDataFileName);
|
||||
var qrCodeDataPath = Path.Combine(dataPath, settings.QrCodeDataFileName);
|
||||
|
||||
var encryptedFiles = new List<string>
|
||||
{
|
||||
@ -213,7 +208,7 @@ public class GameDataService : IGameDataService
|
||||
await using var neiroFile = File.OpenRead(neiroPath);
|
||||
await using var qrCodeDataFile = File.OpenRead(qrCodeDataPath);
|
||||
|
||||
var infoesData = await JsonSerializer.DeserializeAsync<MusicInfoes>(musicInfoFile);
|
||||
var infosData = await JsonSerializer.DeserializeAsync<MusicInfos>(musicInfoFile);
|
||||
var danData = await JsonSerializer.DeserializeAsync<List<DanData>>(danDataFile);
|
||||
var gaidenData = await JsonSerializer.DeserializeAsync<List<DanData>>(gaidenDataFile);
|
||||
var introData = await JsonSerializer.DeserializeAsync<List<SongIntroductionData>>(songIntroDataFile);
|
||||
@ -227,7 +222,7 @@ public class GameDataService : IGameDataService
|
||||
var neiroData = await JsonSerializer.DeserializeAsync<Neiros>(neiroFile);
|
||||
var qrCodeData = await JsonSerializer.DeserializeAsync<List<QRCodeData>>(qrCodeDataFile);
|
||||
|
||||
InitializeMusicInfoes(infoesData);
|
||||
InitializeMusicInfos(infosData);
|
||||
|
||||
InitializeDanData(danData);
|
||||
|
||||
@ -305,11 +300,11 @@ public class GameDataService : IGameDataService
|
||||
eventFolderDictionary = eventFolderData.ToImmutableDictionary(d => d.FolderId);
|
||||
}
|
||||
|
||||
private void InitializeMusicInfoes(MusicInfoes? infoesData)
|
||||
private void InitializeMusicInfos(MusicInfos? infosData)
|
||||
{
|
||||
infoesData.ThrowIfNull("Shouldn't happen!");
|
||||
infosData.ThrowIfNull("Shouldn't happen!");
|
||||
|
||||
musicInfoes = infoesData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
|
||||
musicInfoes = infosData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
|
||||
|
||||
musics = musicInfoes.Select(pair => pair.Key)
|
||||
.ToList();
|
||||
|
28
TaikoLocalServer/Services/Interfaces/IAuthService.cs
Normal file
28
TaikoLocalServer/Services/Interfaces/IAuthService.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using SharedProject.Models;
|
||||
|
||||
namespace TaikoLocalServer.Services.Interfaces;
|
||||
|
||||
public interface IAuthService
|
||||
{
|
||||
public Task<User?> GetUserByBaid(uint baid);
|
||||
|
||||
public Task<Card?> GetCardByAccessCode(string accessCode);
|
||||
|
||||
public Task<List<User>> GetUsersFromCards();
|
||||
|
||||
public Task AddCard(Card card);
|
||||
|
||||
public Task<bool> DeleteCard(string accessCode);
|
||||
|
||||
public Task<List<UserCredential>> GetUserCredentialsFromCredentials();
|
||||
|
||||
public Task AddCredential(Credential credential);
|
||||
|
||||
public Task<bool> DeleteCredential(uint baid);
|
||||
|
||||
public Task<bool> UpdatePassword(uint baid, string password, string salt);
|
||||
|
||||
public Task<Credential?> GetCredentialByBaid(uint baid);
|
||||
|
||||
public (uint baid, bool isAdmin)? ExtractTokenInfo(HttpContext httpContext);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using SharedProject.Models;
|
||||
|
||||
namespace TaikoLocalServer.Services.Interfaces;
|
||||
|
||||
public interface ICardService
|
||||
{
|
||||
public Task<Card?> GetCardByAccessCode(string accessCode);
|
||||
|
||||
public Task<List<User>> GetUsersFromCards();
|
||||
|
||||
public Task AddCard(Card card);
|
||||
|
||||
public Task<bool> DeleteCard(string accessCode);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using SharedProject.Models;
|
||||
|
||||
namespace TaikoLocalServer.Services.Interfaces;
|
||||
|
||||
public interface ICredentialService
|
||||
{
|
||||
public Task<List<UserCredential>> GetUserCredentialsFromCredentials();
|
||||
|
||||
public Task AddCredential(Credential credential);
|
||||
|
||||
public Task<bool> DeleteCredential(uint baid);
|
||||
|
||||
public Task<bool> UpdatePassword(uint baid, string password, string salt);
|
||||
}
|
@ -11,8 +11,6 @@ public interface IGameDataService
|
||||
|
||||
public List<uint> GetMusicWithUraList();
|
||||
|
||||
public ImmutableDictionary<uint, MusicInfoEntry> GetMusicInfoes();
|
||||
|
||||
public ImmutableDictionary<uint, SongIntroductionData> GetSongIntroductionDictionary();
|
||||
|
||||
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary();
|
||||
@ -37,4 +35,3 @@ public interface IGameDataService
|
||||
|
||||
public ImmutableDictionary<string, uint> GetQRCodeDataDictionary();
|
||||
}
|
||||
|
||||
|
@ -3,18 +3,8 @@ using Throw;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class UserDatumService : IUserDatumService
|
||||
public class UserDatumService(TaikoDbContext context) : IUserDatumService
|
||||
{
|
||||
private readonly TaikoDbContext context;
|
||||
|
||||
private readonly ILogger<UserDatumService> logger;
|
||||
|
||||
public UserDatumService(TaikoDbContext context, ILogger<UserDatumService> logger)
|
||||
{
|
||||
this.context = context;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<UserDatum?> GetFirstUserDatumOrNull(uint baid)
|
||||
{
|
||||
return await context.UserData
|
||||
|
12
TaikoLocalServer/Settings/AuthSettings.cs
Normal file
12
TaikoLocalServer/Settings/AuthSettings.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace TaikoLocalServer.Settings;
|
||||
|
||||
public class AuthSettings
|
||||
{
|
||||
public string JwtKey { get; set; } = string.Empty;
|
||||
|
||||
public string JwtIssuer { get; set; } = string.Empty;
|
||||
|
||||
public string JwtAudience { get; set; } = string.Empty;
|
||||
|
||||
public bool LoginRequired { get; set; }
|
||||
}
|
@ -18,5 +18,9 @@ public class DataSettings
|
||||
|
||||
public string LockedSongsDataFileName { get; set; } = "locked_songs_data.json";
|
||||
|
||||
public string QRCodeDataFileName { get; set; } = "qrcode_data.json";
|
||||
public string QrCodeDataFileName { get; set; } = "qrcode_data.json";
|
||||
|
||||
public string LockedCostumeDataFileName { get; set; } = "locked_costume_data.json";
|
||||
|
||||
public string LockedTitleDataFileName { get; set; } = "locked_title_data.json";
|
||||
}
|
@ -15,26 +15,29 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetZip" Version="1.16.0" />
|
||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.0-rc.1.23421.29" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0-rc.2.23480.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0-rc.2.23480.1">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.30" />
|
||||
<PackageReference Include="protobuf-net.AspNetCore" Version="3.2.12" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="3.5.0-next.1" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="3.5.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2-dev-00334" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="4.0.0-dev-00137" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File.Header" Version="1.0.2" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2" />
|
||||
<PackageReference Include="Swan.Logging" Version="6.0.2-beta.96" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Throw" Version="1.4.0" />
|
||||
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.0.0" />
|
||||
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -45,6 +48,9 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Content Remove="Configurations\ServerSettings.json" />
|
||||
<None Include="Configurations\AuthSettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Configurations\ServerSettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
@ -136,6 +142,12 @@
|
||||
<Content Update="wwwroot\data\datatable\wordlist.bin">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\locked_costume_data.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\locked_title_data.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\data\datatable-bak\don_cos_reward.bin">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
17
TaikoLocalServer/wwwroot/data/locked_costume_data.json
Normal file
17
TaikoLocalServer/wwwroot/data/locked_costume_data.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"Kigurumi": [
|
||||
|
||||
],
|
||||
"Head": [
|
||||
|
||||
],
|
||||
"Body": [
|
||||
|
||||
],
|
||||
"Face": [
|
||||
|
||||
],
|
||||
"Puchi": [
|
||||
|
||||
]
|
||||
}
|
8
TaikoLocalServer/wwwroot/data/locked_title_data.json
Normal file
8
TaikoLocalServer/wwwroot/data/locked_title_data.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"TitleNo": [
|
||||
|
||||
],
|
||||
"TitlePlateNo": [
|
||||
|
||||
]
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
<MudMenu Icon="@Icons.Material.Filled.Translate" Color="Color.Inherit" Size="Size.Small" Dense="true">
|
||||
@foreach (var culture in SupportedCultures)
|
||||
{
|
||||
<MudMenuItem OnClick="() => RequestCultureChange(culture.Key)">@culture.Value</MudMenuItem>
|
||||
<MudMenuItem OnClick="() => RequestCultureChange(culture.Key)" OnTouch="() => RequestCultureChange(culture.Key)">@culture.Value</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inject Blazored.LocalStorage.ILocalStorageService localStorage
|
||||
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
|
||||
@inject HttpClient Client
|
||||
@inject AuthService AuthService
|
||||
|
||||
<MudThemeProvider IsDarkMode="@_isDarkMode" Theme="@TaikoWebUiTheme" />
|
||||
<MudThemeProvider IsDarkMode="@isDarkMode" Theme="@taikoWebUiTheme" />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@ -14,7 +16,7 @@
|
||||
<ChooseLanguage />
|
||||
</MudStack>
|
||||
</MudAppBar>
|
||||
<MudDrawer Elevation="0" Style="border-right:1px solid #ededf0" @bind-Open="_drawerOpen">
|
||||
<MudDrawer Elevation="0" Style="border-right:1px solid #ededf0" @bind-Open="drawerOpen">
|
||||
<MudDrawerHeader>
|
||||
<MudText Typo="Typo.h6">TaikoWebUI</MudText>
|
||||
</MudDrawerHeader>
|
||||
@ -32,41 +34,47 @@
|
||||
</MudLayout>
|
||||
|
||||
@code {
|
||||
bool _drawerOpen = true;
|
||||
bool _isDarkMode = false;
|
||||
bool drawerOpen = true;
|
||||
bool isDarkMode = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var hasDrawerOpenEntry = await localStorage.ContainKeyAsync("drawerOpen");
|
||||
var hasDrawerOpenEntry = await LocalStorage.ContainKeyAsync("drawerOpen");
|
||||
|
||||
if (hasDrawerOpenEntry)
|
||||
{
|
||||
_drawerOpen = await localStorage.GetItemAsync<bool>("drawerOpen");
|
||||
drawerOpen = await LocalStorage.GetItemAsync<bool>("drawerOpen");
|
||||
}
|
||||
|
||||
var hasDarkModeEntry = await localStorage.ContainKeyAsync("isDarkMode");
|
||||
var hasDarkModeEntry = await LocalStorage.ContainKeyAsync("isDarkMode");
|
||||
|
||||
if (hasDarkModeEntry)
|
||||
{
|
||||
_isDarkMode = await localStorage.GetItemAsync<bool>("isDarkMode");
|
||||
isDarkMode = await LocalStorage.GetItemAsync<bool>("isDarkMode");
|
||||
}
|
||||
|
||||
if (AuthService.LoginRequired)
|
||||
{
|
||||
// If not logged in, attempt to use JwtToken from local storage to log in
|
||||
await AuthService.LoginWithAuthToken();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DrawerToggle()
|
||||
{
|
||||
_drawerOpen = !_drawerOpen;
|
||||
await localStorage.SetItemAsync("drawerOpen", _drawerOpen);
|
||||
drawerOpen = !drawerOpen;
|
||||
await LocalStorage.SetItemAsync("drawerOpen", drawerOpen);
|
||||
}
|
||||
|
||||
private async Task ToggleDarkMode()
|
||||
{
|
||||
_isDarkMode = !_isDarkMode;
|
||||
await localStorage.SetItemAsync("isDarkMode", _isDarkMode);
|
||||
isDarkMode = !isDarkMode;
|
||||
await LocalStorage.SetItemAsync("isDarkMode", isDarkMode);
|
||||
}
|
||||
|
||||
private string DarkModeIcon => _isDarkMode ? Icons.Material.Filled.BrightnessLow : Icons.Material.Filled.Brightness2;
|
||||
private string DarkModeIcon => isDarkMode ? Icons.Material.Filled.BrightnessLow : Icons.Material.Filled.Brightness2;
|
||||
|
||||
MudTheme TaikoWebUiTheme = new MudTheme()
|
||||
readonly MudTheme taikoWebUiTheme = new()
|
||||
{
|
||||
Palette = new PaletteLight()
|
||||
{
|
||||
|
@ -1,42 +1,43 @@
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IDialogService DialogService
|
||||
|
||||
@using TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
<MudNavMenu Rounded="true" Class="pa-2" Margin="Margin.Dense" Color="Color.Primary">
|
||||
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">@Localizer["dashboard"]</MudNavLink>
|
||||
@if (LoginService.IsAdmin || !LoginService.LoginRequired)
|
||||
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">@Localizer["Dashboard"]</MudNavLink>
|
||||
@if (AuthService.IsAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
<MudNavLink Href="/Users" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.People">@Localizer["Users"]</MudNavLink>
|
||||
}
|
||||
|
||||
@{
|
||||
var currentUser = LoginService.GetLoggedInUser();
|
||||
var baid = AuthService.GetLoggedInBaid();
|
||||
|
||||
if (LoginService.LoginRequired && !LoginService.OnlyAdmin && !LoginService.IsLoggedIn) {
|
||||
if (AuthService.LoginRequired && !AuthService.OnlyAdmin && !AuthService.IsLoggedIn) {
|
||||
<MudDivider />
|
||||
<MudNavLink Href="/Login" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Login">@Localizer["Log In"]</MudNavLink>
|
||||
<MudNavLink Href="/Register" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.AddCard">@Localizer["Register"]</MudNavLink>
|
||||
}
|
||||
|
||||
if (LoginService.IsLoggedIn && currentUser != null)
|
||||
if (AuthService.IsLoggedIn)
|
||||
{
|
||||
<MudDivider />
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/Profile")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Person">@Localizer["Profile"]</MudNavLink>
|
||||
<MudNavGroup Title="Play Data" Expanded="true" Icon="@Icons.Material.Filled.EmojiEvents">
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/Songs")" Match="NavLinkMatch.All">@Localizer["Key_01"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/HighScores")" Match="NavLinkMatch.All">@Localizer["High Scores"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/DaniDojo")" Match="NavLinkMatch.All">@Localizer["Key_03"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/Profile")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Person">@Localizer["Profile"]</MudNavLink>
|
||||
<MudNavGroup Title=@Localizer["Play Data"] Expanded="true" Icon="@Icons.Material.Filled.EmojiEvents">
|
||||
<MudNavLink Href="@($"Users/{baid}/Songs")" Match="NavLinkMatch.All">@Localizer["Song List"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/HighScores")" Match="NavLinkMatch.All">@Localizer["High Scores"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/PlayHistory")" Match="NavLinkMatch.All">@Localizer["Play History"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/DaniDojo")" Match="NavLinkMatch.All">@Localizer["Dani Dojo"]</MudNavLink>
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="Settings" Expanded="_settingsOpen" Icon="@Icons.Material.Filled.Settings">
|
||||
<MudNavGroup Title=@Localizer["Settings"] Expanded="settingsOpen" Icon="@Icons.Material.Filled.Settings">
|
||||
<MudNavLink OnClick="ShowQrCode">@Localizer["Show QR Code"]</MudNavLink>
|
||||
<MudNavLink Href="/ChangePassword" Match="NavLinkMatch.All">@Localizer["Change Password"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/AccessCode")" Match="NavLinkMatch.All">@Localizer["Access Codes"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/AccessCode")" Match="NavLinkMatch.All">@Localizer["Access Codes"]</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
|
||||
if (LoginService.IsLoggedIn)
|
||||
if (AuthService.IsLoggedIn)
|
||||
{
|
||||
<MudDivider />
|
||||
<MudNavLink Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Logout" IconColor="Color.Error" OnClick="Logout">@Localizer["Log Out"]</MudNavLink>
|
||||
@ -45,14 +46,14 @@
|
||||
</MudNavMenu>
|
||||
|
||||
@code {
|
||||
private bool _settingsOpen = false;
|
||||
private bool settingsOpen = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
LoginService.LoginStatusChanged += HandleLoginStatusChanged;
|
||||
AuthService.LoginStatusChanged += HandleAuthStatusChanged;
|
||||
}
|
||||
|
||||
private void HandleLoginStatusChanged(object? sender, EventArgs e)
|
||||
private void HandleAuthStatusChanged(object? sender, EventArgs e)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -61,22 +62,25 @@
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
LoginService.LoginStatusChanged += HandleLoginStatusChanged;
|
||||
AuthService.LoginStatusChanged += HandleAuthStatusChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowQrCode()
|
||||
private async Task ShowQrCode()
|
||||
{
|
||||
var user = await AuthService.GetLoggedInUser();
|
||||
if (user == null) return;
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["user"] = LoginService.GetLoggedInUser()
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
DialogService.Show<UserQrCodeDialog>("QR Code", parameters, options);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
await DialogService.ShowAsync<UserQrCodeDialog>(Localizer["QR Code"], parameters, options);
|
||||
|
||||
// Prevent the settings menu from closing
|
||||
_settingsOpen = true;
|
||||
settingsOpen = true;
|
||||
}
|
||||
|
||||
private async Task Logout()
|
||||
@ -89,7 +93,7 @@
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
LoginService.Logout();
|
||||
await AuthService.Logout();
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
|
@ -2,35 +2,84 @@
|
||||
|
||||
<MudCard Outlined="true" Elevation="0">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">Play History</MudText>
|
||||
<MudGrid Spacing="2">
|
||||
<MudItem xs="12" md="4">
|
||||
<MudText Typo="Typo.h6">@Localizer["Play History"]</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="8">
|
||||
<MudText Typo="Typo.h6">@Localizer["Total Plays"]:@Items.Count</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardHeader>
|
||||
|
||||
@if (Items != null && Items.Count > 0)
|
||||
@if (Items.Count > 0)
|
||||
{
|
||||
<MudCardContent Class="p-0">
|
||||
<MudTable Items="Items" Elevation="0" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>@Localizer["Play Time"]</MudTh>
|
||||
<MudTh>@Localizer["Difficulty"]</MudTh>
|
||||
<MudTh>@Localizer["Crown"]</MudTh>
|
||||
<MudTh>@Localizer["Rank"]</MudTh>
|
||||
<MudTh>@Localizer["Score"]</MudTh>
|
||||
<MudTh>@Localizer["Good"]</MudTh>
|
||||
<MudTh>@Localizer["Ok"]</MudTh>
|
||||
<MudTh>@Localizer["Bad"]</MudTh>
|
||||
<MudTh>@Localizer["Drumroll"]</MudTh>
|
||||
<MudTh>@Localizer["Max Combo"]</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" T="SongHistoryData" SortBy="x => x.PlayTime">
|
||||
@Localizer["Play Time"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Difficulty">
|
||||
@Localizer["Difficulty"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Crown">
|
||||
@Localizer["Crown"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.ScoreRank">
|
||||
@Localizer["Rank"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Score">
|
||||
@Localizer["Score"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.GoodCount">
|
||||
@Localizer["Good"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.OkCount">
|
||||
@Localizer["OK"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.MissCount">
|
||||
@Localizer["Bad"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.DrumrollCount">
|
||||
@Localizer["Drumroll"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.ComboCount">
|
||||
@Localizer["MAX Combo"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.PlayTime.ToString(Localizer["DateFormat"])</MudTd>
|
||||
<MudTd>
|
||||
<img src="@ScoreUtils.GetDifficultyIcon(context.Difficulty)" alt="@context.Difficulty" title="@context.Difficulty" style="@IconStyle" />
|
||||
<img src="@ScoreUtils.GetDifficultyIcon(context.Difficulty)" alt="@context.Difficulty" title="@context.Difficulty" style="@IconStyle"/>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<img src="@($"/images/crown_{context.Crown}.png")" alt="@(ScoreUtils.GetCrownText(context.Crown))" title="@(ScoreUtils.GetCrownText(context.Crown))" style="@IconStyle" />
|
||||
<img src="@($"/images/crown_{context.Crown}.png")" alt="@(ScoreUtils.GetCrownText(context.Crown))" title="@(ScoreUtils.GetCrownText(context.Crown))" style="@IconStyle"/>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<img src="@($"/images/rank_{context.ScoreRank}.png")" alt="@(ScoreUtils.GetRankText(context.ScoreRank))" title="@(ScoreUtils.GetRankText(context.ScoreRank))" style="@IconStyle" />
|
||||
@if (context.ScoreRank is not ScoreRank.None)
|
||||
{
|
||||
<img src="@($"/images/rank_{context.ScoreRank}.png")" alt="@(ScoreUtils.GetRankText(context.ScoreRank))" title="@(ScoreUtils.GetRankText(context.ScoreRank))" style="@IconStyle"/>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@context.Score</MudTd>
|
||||
<MudTd>@context.GoodCount</MudTd>
|
||||
@ -46,13 +95,13 @@
|
||||
{
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.body2" Class="pt-4 pb-8" Color="Color.Surface" Align="Align.Center">
|
||||
@Localizer["No play history found."]
|
||||
@Localizer["No Play History Found"]
|
||||
</MudText>
|
||||
</MudCardContent>
|
||||
}
|
||||
</MudCard>
|
||||
|
||||
@code {
|
||||
[Parameter] public List<SongPlayDatumDto> Items { get; set; } = new List<SongPlayDatumDto>();
|
||||
[Parameter] public List<SongHistoryData> Items { get; set; } = new();
|
||||
private const string IconStyle = "width:25px; height:25px;";
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
@using Microsoft.AspNetCore.Components;
|
||||
@using System.Text.Json
|
||||
@using TaikoWebUI.Pages.Dialogs;
|
||||
@inject TaikoWebUI.Utilities.StringUtil StringUtil;
|
||||
@inject Utilities.StringUtil StringUtil;
|
||||
@inject IDialogService DialogService;
|
||||
@inject LoginService LoginService;
|
||||
@inject AuthService AuthService;
|
||||
@inject HttpClient Client
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
|
||||
@if (user is not null)
|
||||
@if (User is not null)
|
||||
{
|
||||
<MudCard Outlined="true">
|
||||
<MudCardHeader>
|
||||
@ -21,30 +21,30 @@
|
||||
<MudSkeleton Width="35%" Height="32px" />
|
||||
}
|
||||
|
||||
@if (LoginService.LoginRequired && user?.IsAdmin == true)
|
||||
@if (AuthService.LoginRequired && User?.IsAdmin == true)
|
||||
{
|
||||
<MudChip Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small" Icon="@Icons.Material.TwoTone.AdminPanelSettings">@Localizer["Admin"]</MudChip>
|
||||
}
|
||||
</div>
|
||||
<MudText Typo="Typo.caption">User ID: @user?.Baid</MudText>
|
||||
<MudText Typo="Typo.caption">@Localizer["User"] BAID: @User?.Baid</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Dense="true" AnchorOrigin="Origin.BottomLeft"
|
||||
TransformOrigin="Origin.TopLeft" Size="Size.Small">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.QrCode"
|
||||
OnClick="@(_ => ShowQrCode(user))"
|
||||
OnTouch="@(_ => ShowQrCode(user))"
|
||||
OnClick="@(_ => ShowQrCode(User))"
|
||||
OnTouch="@(_ => ShowQrCode(User))"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Show QR Code"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.FeaturedPlayList"
|
||||
Href="@($"Users/{user.Baid}/AccessCode")"
|
||||
Href="@($"Users/{User.Baid}/AccessCode")"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Access Codes"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
@if (LoginService.OnlyAdmin || LoginService.LoginRequired)
|
||||
@if (AuthService.OnlyAdmin || AuthService.LoginRequired)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Lock"
|
||||
Href="@($"/ChangePassword")"
|
||||
@ -53,21 +53,31 @@
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
}
|
||||
@if (LoginService.LoginRequired && LoginService.IsAdmin)
|
||||
@if (AuthService.LoginRequired && AuthService.IsAdmin)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Password"
|
||||
OnClick="@(_ => GenerateInviteCode(User.Baid))"
|
||||
OnTouch="@(_ => GenerateInviteCode(User.Baid))"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Generate Invite Code"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
}
|
||||
@if (AuthService.LoginRequired && AuthService.IsAdmin)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.LockReset"
|
||||
OnClick="@(_ => ResetPassword(user))"
|
||||
OnTouch="@(_ => ResetPassword(user))"
|
||||
OnClick="@(_ => ResetPassword(User))"
|
||||
OnTouch="@(_ => ResetPassword(User))"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Unregister"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
}
|
||||
@if (LoginService.AllowUserDelete)
|
||||
@if (AuthService.AllowUserDelete)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => DeleteUser(user))"
|
||||
OnTouch="@(_ => DeleteUser(user))"
|
||||
OnClick="@(_ => DeleteUser(User))"
|
||||
OnTouch="@(_ => DeleteUser(User))"
|
||||
IconColor="@Color.Error">
|
||||
@Localizer["Delete User"]
|
||||
</MudMenuItem>
|
||||
@ -76,11 +86,11 @@
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.body2" Style="font-weight:bold">Access Code</MudText>
|
||||
<MudText Typo="Typo.body2" Style="font-weight:bold">@Localizer["Access Code"]</MudText>
|
||||
<MudText Style="font-family:monospace;overflow:hidden;overflow-x:scroll">
|
||||
@if (user.AccessCodes.Count > 0)
|
||||
@if (User.AccessCodes.Count > 0)
|
||||
{
|
||||
@foreach (var digitGroup in StringUtil.SplitIntoGroups(user.AccessCodes[0], 4))
|
||||
@foreach (var digitGroup in StringUtil.SplitIntoGroups(User.AccessCodes[0], 4))
|
||||
{
|
||||
<span class="mr-2">@digitGroup</span>
|
||||
}
|
||||
@ -89,14 +99,19 @@
|
||||
<span class="mr-2">@Localizer["N/A"]</span>
|
||||
}
|
||||
</MudText>
|
||||
@if (user.AccessCodes.Count > 1)
|
||||
@if (User.AccessCodes.Count > 1)
|
||||
{
|
||||
<MudText Typo="Typo.caption">... and @(user.AccessCodes.Count - 1) other access code(s)</MudText>
|
||||
<MudText Typo="Typo.caption">... @Localizer["and"] @(User.AccessCodes.Count - 1) @Localizer["other access code(s)"]</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
// Empty line to keep the layout consistent
|
||||
<MudText Typo="Typo.caption"> </MudText>
|
||||
}
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
|
||||
<MudButton Href="@($"Users/{user.Baid}/Profile")"
|
||||
<MudButton Href="@($"Users/{User.Baid}/Profile")"
|
||||
Size="Size.Small" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Edit"
|
||||
Color="Color.Primary">
|
||||
@Localizer["edit profile"]
|
||||
@ -104,15 +119,16 @@
|
||||
<MudMenu Size="Size.Small"
|
||||
Dense="true"
|
||||
Color="Color.Primary"
|
||||
Label="@Localizer["view play data"]"
|
||||
Label="@Localizer["View Play Data"]"
|
||||
StartIcon="@Icons.Material.Filled.EmojiEvents"
|
||||
EndIcon="@Icons.Material.Filled.KeyboardArrowDown"
|
||||
FullWidth="true"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
TransformOrigin="Origin.TopCenter">
|
||||
<MudMenuItem Href="@($"Users/{user.Baid}/HighScores")">@Localizer["High Scores"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{user.Baid}/Songs")">@Localizer["Song List"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{user.Baid}/DaniDojo")">@Localizer["dani dojo"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/HighScores")">@Localizer["High Scores"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/PlayHistory")">@Localizer["Play History"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/Songs")">@Localizer["Song List"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/DaniDojo")">@Localizer["Dani Dojo"]</MudMenuItem>
|
||||
</MudMenu>
|
||||
</MudStack>
|
||||
</MudCardActions>
|
||||
@ -120,14 +136,14 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public User? user { get; set; }
|
||||
private DashboardResponse? response;
|
||||
[Parameter] public User? User { get; set; }
|
||||
private UserSetting? userSetting;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{ if (user is not null)
|
||||
{
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{user.Baid}");
|
||||
if (User is not null)
|
||||
{
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{User.Baid}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,21 +154,21 @@
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
DialogService.Show<UserQrCodeDialog>("QR Code", parameters, options);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
DialogService.Show<UserQrCodeDialog>(Localizer["QR Code"], parameters, options);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ResetPassword(User user)
|
||||
{
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
if (LoginService.LoginRequired && !LoginService.IsAdmin)
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
if (AuthService.LoginRequired && !AuthService.IsAdmin)
|
||||
{
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
"Only admin can reset password.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
return;
|
||||
}
|
||||
var parameters = new DialogParameters
|
||||
@ -160,23 +176,19 @@
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var dialog = DialogService.Show<ResetPasswordConfirmDialog>("Reset Password", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result.Canceled) return;
|
||||
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
var dialog = await DialogService.ShowAsync<ResetPasswordConfirmDialog>(Localizer["Reset Password"], parameters, options);
|
||||
await dialog.Result;
|
||||
}
|
||||
|
||||
private async Task DeleteUser(User user)
|
||||
{
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
if (!LoginService.AllowUserDelete)
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
if (!AuthService.AllowUserDelete)
|
||||
{
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
"User deletion is disabled by admin.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
return;
|
||||
}
|
||||
var parameters = new DialogParameters
|
||||
@ -184,13 +196,56 @@
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var dialog = DialogService.Show<UserDeleteConfirmDialog>("Delete User", parameters, options);
|
||||
var dialog = await DialogService.ShowAsync<UserDeleteConfirmDialog>(Localizer["Delete User"], parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result.Canceled) return;
|
||||
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
LoginService.Logout();
|
||||
if (user.Baid == AuthService.GetLoggedInBaid())
|
||||
{
|
||||
await AuthService.Logout();
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
}
|
||||
|
||||
private async Task GenerateInviteCode(uint baid)
|
||||
{
|
||||
var request = new GenerateOtpRequest
|
||||
{
|
||||
Baid = baid
|
||||
};
|
||||
|
||||
var responseMessage = await Client.PostAsJsonAsync("api/Auth/GenerateOtp", request);
|
||||
|
||||
if (!responseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, new DialogOptions { DisableBackdropClick = true });
|
||||
return;
|
||||
}
|
||||
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
if (responseJson == null)
|
||||
{
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, new DialogOptions { DisableBackdropClick = true });
|
||||
return;
|
||||
}
|
||||
|
||||
var otp = responseJson["otp"];
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["otp"] = otp
|
||||
};
|
||||
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
await DialogService.ShowAsync<OTPDialog>(Localizer["Invite Code"], parameters, options);
|
||||
}
|
||||
}
|
802
TaikoWebUI/Localization/LocalizationResource.Designer.cs
generated
802
TaikoWebUI/Localization/LocalizationResource.Designer.cs
generated
@ -1,7 +1,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
@ -60,6 +59,186 @@ namespace TaikoWebUI.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string _Invite_Code__Optional__ {
|
||||
get {
|
||||
return ResourceManager.GetString("\"Invite Code (Optional)\"", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Access_Code {
|
||||
get {
|
||||
return ResourceManager.GetString("Access Code", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Access_Code_is_Required {
|
||||
get {
|
||||
return ResourceManager.GetString("Access Code is Required", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Access_Codes {
|
||||
get {
|
||||
return ResourceManager.GetString("Access Codes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Achievement_Panel {
|
||||
get {
|
||||
return ResourceManager.GetString("Achievement Panel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Achievement_Panel_Difficulty {
|
||||
get {
|
||||
return ResourceManager.GetString("Achievement Panel Difficulty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Add_Access_Code {
|
||||
get {
|
||||
return ResourceManager.GetString("Add Access Code", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string AI_Battle_Data {
|
||||
get {
|
||||
return ResourceManager.GetString("AI Battle Data", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string and {
|
||||
get {
|
||||
return ResourceManager.GetString("and", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Bad {
|
||||
get {
|
||||
return ResourceManager.GetString("Bad", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Best_Crown {
|
||||
get {
|
||||
return ResourceManager.GetString("Best Crown", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Best_Rank {
|
||||
get {
|
||||
return ResourceManager.GetString("Best Rank", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Chojin {
|
||||
get {
|
||||
return ResourceManager.GetString("Chojin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Confirm_New_Password {
|
||||
get {
|
||||
return ResourceManager.GetString("Confirm New Password", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Confirm_Password {
|
||||
get {
|
||||
return ResourceManager.GetString("Confirm Password", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Confirm_Password_is_Required {
|
||||
get {
|
||||
return ResourceManager.GetString("Confirm Password is Required", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Copy_to_Clipboard {
|
||||
get {
|
||||
return ResourceManager.GetString("Copy to Clipboard", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Course_Songs {
|
||||
get {
|
||||
return ResourceManager.GetString("Course Songs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Dani_Dojo {
|
||||
get {
|
||||
return ResourceManager.GetString("Dani Dojo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Dashboard {
|
||||
get {
|
||||
return ResourceManager.GetString("Dashboard", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to MM/dd/yyyy h:mm:ss tt.
|
||||
/// </summary>
|
||||
@ -68,5 +247,626 @@ namespace TaikoWebUI.Localization {
|
||||
return ResourceManager.GetString("DateFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Delete {
|
||||
get {
|
||||
return ResourceManager.GetString("Delete", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Dialog_OK {
|
||||
get {
|
||||
return ResourceManager.GetString("Dialog OK", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Difficulty {
|
||||
get {
|
||||
return ResourceManager.GetString("Difficulty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Easy {
|
||||
get {
|
||||
return ResourceManager.GetString("Easy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Eighth_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Eighth Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Error {
|
||||
get {
|
||||
return ResourceManager.GetString("Error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Fifth_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Fifth Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Fifth_Kyuu {
|
||||
get {
|
||||
return ResourceManager.GetString("Fifth Kyuu", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Filter_by_Genre {
|
||||
get {
|
||||
return ResourceManager.GetString("Filter by Genre", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string First_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("First Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string First_Kyuu {
|
||||
get {
|
||||
return ResourceManager.GetString("First Kyuu", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Fourth_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Fourth Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Fourth_Kyuu {
|
||||
get {
|
||||
return ResourceManager.GetString("Fourth Kyuu", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Gaiden {
|
||||
get {
|
||||
return ResourceManager.GetString("Gaiden", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Generate_Invite_Code {
|
||||
get {
|
||||
return ResourceManager.GetString("Generate Invite Code", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Gold_Donderful_Combo {
|
||||
get {
|
||||
return ResourceManager.GetString("Gold Donderful Combo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Gold_Full_Combo {
|
||||
get {
|
||||
return ResourceManager.GetString("Gold Full Combo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Hard {
|
||||
get {
|
||||
return ResourceManager.GetString("Hard", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string High_Scores {
|
||||
get {
|
||||
return ResourceManager.GetString("High Scores", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Invite_Code {
|
||||
get {
|
||||
return ResourceManager.GetString("Invite Code", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Kuroto {
|
||||
get {
|
||||
return ResourceManager.GetString("Kuroto", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Last_Play_Date {
|
||||
get {
|
||||
return ResourceManager.GetString("Last Play Date", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Last_Play_Time_5_Min_Around_Credit_End_ {
|
||||
get {
|
||||
return ResourceManager.GetString("Last Play Time(5 Min Around Credit End)", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Log_In {
|
||||
get {
|
||||
return ResourceManager.GetString("Log In", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Log_In_First {
|
||||
get {
|
||||
return ResourceManager.GetString("Log In First", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Log_Out {
|
||||
get {
|
||||
return ResourceManager.GetString("Log Out", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Meijin {
|
||||
get {
|
||||
return ResourceManager.GetString("Meijin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string New_Access_Code {
|
||||
get {
|
||||
return ResourceManager.GetString("New Access Code", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string New_Password {
|
||||
get {
|
||||
return ResourceManager.GetString("New Password", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Ninth_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Ninth Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string No_Play_History_Found {
|
||||
get {
|
||||
return ResourceManager.GetString("No Play History Found", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Normal {
|
||||
get {
|
||||
return ResourceManager.GetString("Normal", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Not_Cleared {
|
||||
get {
|
||||
return ResourceManager.GetString("Not Cleared", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Old_Password {
|
||||
get {
|
||||
return ResourceManager.GetString("Old Password", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Oni {
|
||||
get {
|
||||
return ResourceManager.GetString("Oni", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string other_access_code_s_ {
|
||||
get {
|
||||
return ResourceManager.GetString("other access code(s)", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Password {
|
||||
get {
|
||||
return ResourceManager.GetString("Password", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Password_is_Required {
|
||||
get {
|
||||
return ResourceManager.GetString("Password is Required", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Play_Data {
|
||||
get {
|
||||
return ResourceManager.GetString("Play Data", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Play_History {
|
||||
get {
|
||||
return ResourceManager.GetString("Play History", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Play_Time {
|
||||
get {
|
||||
return ResourceManager.GetString("Play Time", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string QR_Code {
|
||||
get {
|
||||
return ResourceManager.GetString("QR Code", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Rank {
|
||||
get {
|
||||
return ResourceManager.GetString("Rank", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Red_Donderful_Combo {
|
||||
get {
|
||||
return ResourceManager.GetString("Red Donderful Combo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Red_Full_Combo {
|
||||
get {
|
||||
return ResourceManager.GetString("Red Full Combo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Register {
|
||||
get {
|
||||
return ResourceManager.GetString("Register", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Rows_Per_Page_ {
|
||||
get {
|
||||
return ResourceManager.GetString("Rows Per Page:", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Search_by_Title__Artist_or_Date {
|
||||
get {
|
||||
return ResourceManager.GetString("Search by Title, Artist or Date", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Search_by_Title_or_Artist {
|
||||
get {
|
||||
return ResourceManager.GetString("Search by Title or Artist", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Second_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Second Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Second_Kyuu {
|
||||
get {
|
||||
return ResourceManager.GetString("Second Kyuu", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Settings {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Seventh_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Seventh Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Sixth_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Sixth Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Song_List {
|
||||
get {
|
||||
return ResourceManager.GetString("Song List", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Song_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Song Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Song_Number {
|
||||
get {
|
||||
return ResourceManager.GetString("Song Number", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Song_Title___Artist {
|
||||
get {
|
||||
return ResourceManager.GetString("Song Title / Artist", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Success {
|
||||
get {
|
||||
return ResourceManager.GetString("Success", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Tatsujin {
|
||||
get {
|
||||
return ResourceManager.GetString("Tatsujin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Tenth_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Tenth Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Third_Dan {
|
||||
get {
|
||||
return ResourceManager.GetString("Third Dan", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Third_Kyuu {
|
||||
get {
|
||||
return ResourceManager.GetString("Third Kyuu", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Unknown_Error {
|
||||
get {
|
||||
return ResourceManager.GetString("Unknown Error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Unregister {
|
||||
get {
|
||||
return ResourceManager.GetString("Unregister", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Ura_Oni {
|
||||
get {
|
||||
return ResourceManager.GetString("Ura Oni", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string User {
|
||||
get {
|
||||
return ResourceManager.GetString("User", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string Users {
|
||||
get {
|
||||
return ResourceManager.GetString("Users", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string View_Play_Data {
|
||||
get {
|
||||
return ResourceManager.GetString("View Play Data", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,31 +117,28 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="dashboard" xml:space="preserve">
|
||||
<data name="Dashboard" xml:space="preserve">
|
||||
<value>Dashboard</value>
|
||||
</data>
|
||||
<data name="users" xml:space="preserve">
|
||||
<data name="Users" xml:space="preserve">
|
||||
<value>Users</value>
|
||||
</data>
|
||||
<data name="edit profile" xml:space="preserve">
|
||||
<value>Edit Profile</value>
|
||||
</data>
|
||||
<data name="user" xml:space="preserve">
|
||||
<data name="User" xml:space="preserve">
|
||||
<value>User</value>
|
||||
</data>
|
||||
<data name="view play data" xml:space="preserve">
|
||||
<data name="View Play Data" xml:space="preserve">
|
||||
<value>View Play Data</value>
|
||||
</data>
|
||||
<data name="high scores" xml:space="preserve">
|
||||
<value>Songs</value>
|
||||
</data>
|
||||
<data name="dani dojo" xml:space="preserve">
|
||||
<value>Dani Dojo</value>
|
||||
<data name="High Scores" xml:space="preserve">
|
||||
<value>High Scores</value>
|
||||
</data>
|
||||
<data name="Show QR Code" xml:space="preserve">
|
||||
<value>Show QR Code</value>
|
||||
</data>
|
||||
<data name="Manage Access Codes" xml:space="preserve">
|
||||
<data name="Access Codes" xml:space="preserve">
|
||||
<value>Manage Access Codes</value>
|
||||
</data>
|
||||
<data name="Change Password" xml:space="preserve">
|
||||
@ -156,7 +153,7 @@
|
||||
<data name="Welcome to TaikoWebUI!" xml:space="preserve">
|
||||
<value>Welcome to TaikoWebUI!</value>
|
||||
</data>
|
||||
<data name="Song" xml:space="preserve">
|
||||
<data name="Song Name" xml:space="preserve">
|
||||
<value>Song</value>
|
||||
</data>
|
||||
<data name="Level" xml:space="preserve">
|
||||
@ -207,7 +204,7 @@
|
||||
<data name="Total Donderful Combos" xml:space="preserve">
|
||||
<value>Total Donderful Combos</value>
|
||||
</data>
|
||||
<data name="Key_01" xml:space="preserve">
|
||||
<data name="Song List" xml:space="preserve">
|
||||
<value>Songs</value>
|
||||
</data>
|
||||
<data name="Hide" xml:space="preserve">
|
||||
@ -229,9 +226,9 @@
|
||||
<value>Crown</value>
|
||||
</data>
|
||||
<data name="No data." xml:space="preserve">
|
||||
<value>No data.</value>
|
||||
<value>No data</value>
|
||||
</data>
|
||||
<data name="Key_02" xml:space="preserve">
|
||||
<data name="Log In First" xml:space="preserve">
|
||||
<value>"Please log in by clicking on "Users" tab first.</value>
|
||||
</data>
|
||||
<data name="Total Hits" xml:space="preserve">
|
||||
@ -240,20 +237,20 @@
|
||||
<data name="Soul Gauge" xml:space="preserve">
|
||||
<value>Soul Gauge</value>
|
||||
</data>
|
||||
<data name="Songs" xml:space="preserve">
|
||||
<data name="Course Songs" xml:space="preserve">
|
||||
<value>Songs</value>
|
||||
</data>
|
||||
<data name="Conditions" xml:space="preserve">
|
||||
<value>Conditions</value>
|
||||
</data>
|
||||
<data name="Red" xml:space="preserve">
|
||||
<value>Red</value>
|
||||
<value>Red Clear</value>
|
||||
</data>
|
||||
<data name="Gold" xml:space="preserve">
|
||||
<value>Gold</value>
|
||||
</data>
|
||||
<data name="Failed" xml:space="preserve">
|
||||
<value>Failed</value>
|
||||
<data name="Not Cleared" xml:space="preserve">
|
||||
<value>Not Cleared</value>
|
||||
</data>
|
||||
<data name="Pass" xml:space="preserve">
|
||||
<value>Pass</value>
|
||||
@ -267,7 +264,7 @@
|
||||
<data name="Stage" xml:space="preserve">
|
||||
<value>Stage</value>
|
||||
</data>
|
||||
<data name="Key_03" xml:space="preserve">
|
||||
<data name="Dani Dojo" xml:space="preserve">
|
||||
<value>Dani Dojo</value>
|
||||
</data>
|
||||
<data name="Profile" xml:space="preserve">
|
||||
@ -384,4 +381,217 @@
|
||||
<data name="DateFormat" xml:space="preserve">
|
||||
<value>MM/dd/yyyy h:mm:ss tt</value>
|
||||
</data>
|
||||
<data name="Generate Invite Code" xml:space="preserve">
|
||||
<value>Generate Invite Code</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>Register</value>
|
||||
</data>
|
||||
<data name="Log In" xml:space="preserve">
|
||||
<value>Log In</value>
|
||||
</data>
|
||||
<data name="Log Out" xml:space="preserve">
|
||||
<value>Log Out</value>
|
||||
</data>
|
||||
<data name="Play Data" xml:space="preserve">
|
||||
<value>Play Data</value>
|
||||
</data>
|
||||
<data name="Add Access Code" xml:space="preserve">
|
||||
<value>Add Access Code</value>
|
||||
</data>
|
||||
<data name="New Access Code" xml:space="preserve">
|
||||
<value>New Access Code</value>
|
||||
</data>
|
||||
<data name="Delete" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="Access Code" xml:space="preserve">
|
||||
<value>Access Code</value>
|
||||
</data>
|
||||
<data name="Old Password" xml:space="preserve">
|
||||
<value>Old Password</value>
|
||||
</data>
|
||||
<data name="New Password" xml:space="preserve">
|
||||
<value>New Password</value>
|
||||
</data>
|
||||
<data name="Confirm New Password" xml:space="preserve">
|
||||
<value>Confirm New Password</value>
|
||||
</data>
|
||||
<data name="Dialog OK" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="QR Code" xml:space="preserve">
|
||||
<value>QR Code</value>
|
||||
</data>
|
||||
<data name="Chojin" xml:space="preserve">
|
||||
<value>Chojin</value>
|
||||
</data>
|
||||
<data name="Eighth Dan" xml:space="preserve">
|
||||
<value>Eighth Dan</value>
|
||||
</data>
|
||||
<data name="Fifth Dan" xml:space="preserve">
|
||||
<value>5th Dan</value>
|
||||
</data>
|
||||
<data name="Fifth Kyuu" xml:space="preserve">
|
||||
<value>5th Kyuu</value>
|
||||
</data>
|
||||
<data name="First Dan" xml:space="preserve">
|
||||
<value>1st Dan</value>
|
||||
</data>
|
||||
<data name="First Kyuu" xml:space="preserve">
|
||||
<value>1st Kyuu</value>
|
||||
</data>
|
||||
<data name="Fourth Dan" xml:space="preserve">
|
||||
<value>4th Dan</value>
|
||||
</data>
|
||||
<data name="Fourth Kyuu" xml:space="preserve">
|
||||
<value>4th Kyuu</value>
|
||||
</data>
|
||||
<data name="Gaiden" xml:space="preserve">
|
||||
<value>Gaiden</value>
|
||||
</data>
|
||||
<data name="Kuroto" xml:space="preserve">
|
||||
<value>Kuroto</value>
|
||||
</data>
|
||||
<data name="Meijin" xml:space="preserve">
|
||||
<value>Meijin</value>
|
||||
</data>
|
||||
<data name="Ninth Dan" xml:space="preserve">
|
||||
<value>9th Dan</value>
|
||||
</data>
|
||||
<data name="Second Dan" xml:space="preserve">
|
||||
<value>2nd Dan</value>
|
||||
</data>
|
||||
<data name="Second Kyuu" xml:space="preserve">
|
||||
<value>2nd Kyuu</value>
|
||||
</data>
|
||||
<data name="Seventh Dan" xml:space="preserve">
|
||||
<value>7th Dan</value>
|
||||
</data>
|
||||
<data name="Sixth Dan" xml:space="preserve">
|
||||
<value>6th Dan</value>
|
||||
</data>
|
||||
<data name="Tatsujin" xml:space="preserve">
|
||||
<value>Tatsujin</value>
|
||||
</data>
|
||||
<data name="Tenth Dan" xml:space="preserve">
|
||||
<value>10th Dan</value>
|
||||
</data>
|
||||
<data name="Third Dan" xml:space="preserve">
|
||||
<value>3rd Dan</value>
|
||||
</data>
|
||||
<data name="Third Kyuu" xml:space="preserve">
|
||||
<value>3rd Kyuu</value>
|
||||
</data>
|
||||
<data name="Gold Full Combo" xml:space="preserve">
|
||||
<value>Gold Full Combo</value>
|
||||
</data>
|
||||
<data name="Red Donderful Combo" xml:space="preserve">
|
||||
<value>Red Donderful Combo</value>
|
||||
</data>
|
||||
<data name="Red Full Combo" xml:space="preserve">
|
||||
<value>Red Full Combo</value>
|
||||
</data>
|
||||
<data name="Gold Donderful Combo" xml:space="preserve">
|
||||
<value>Gold Donderful Combo</value>
|
||||
</data>
|
||||
<data name="Song Title / Artist" xml:space="preserve">
|
||||
<value>Song Title / Artist</value>
|
||||
</data>
|
||||
<data name="Search by Title or Artist" xml:space="preserve">
|
||||
<value>Search by Title or Artist</value>
|
||||
</data>
|
||||
<data name="Filter by Genre" xml:space="preserve">
|
||||
<value>Filter by Genre</value>
|
||||
</data>
|
||||
<data name="Play History" xml:space="preserve">
|
||||
<value>Play History</value>
|
||||
</data>
|
||||
<data name="No Play History Found" xml:space="preserve">
|
||||
<value>No play history found</value>
|
||||
</data>
|
||||
<data name="Password" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Play Time" xml:space="preserve">
|
||||
<value>Play Time</value>
|
||||
</data>
|
||||
<data name="Rank" xml:space="preserve">
|
||||
<value>Rank</value>
|
||||
</data>
|
||||
<data name="Difficulty" xml:space="preserve">
|
||||
<value>Difficulty</value>
|
||||
</data>
|
||||
<data name="Song Number" xml:space="preserve">
|
||||
<value>Song Number</value>
|
||||
</data>
|
||||
<data name="Search by Title, Artist or Date" xml:space="preserve">
|
||||
<value>Search by Title, Artist or Date</value>
|
||||
</data>
|
||||
<data name="Unregister" xml:space="preserve">
|
||||
<value>Unregister</value>
|
||||
</data>
|
||||
<data name="and" xml:space="preserve">
|
||||
<value>and</value>
|
||||
</data>
|
||||
<data name="other access code(s)" xml:space="preserve">
|
||||
<value>other Access Code(s)</value>
|
||||
</data>
|
||||
<data name="Copy to Clipboard" xml:space="preserve">
|
||||
<value>Copy to Clipboard</value>
|
||||
</data>
|
||||
<data name="Invite Code" xml:space="preserve">
|
||||
<value>Invite Code</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
<data name="Access Code is Required" xml:space="preserve">
|
||||
<value>Access Code is required</value>
|
||||
</data>
|
||||
<data name=""Invite Code (Optional)"" xml:space="preserve">
|
||||
<value>"Invite Code (Optional)"</value>
|
||||
</data>
|
||||
<data name="Last Play Date" xml:space="preserve">
|
||||
<value>Last Play Date</value>
|
||||
</data>
|
||||
<data name="Last Play Time(5 Min Around Credit End)" xml:space="preserve">
|
||||
<value>Last Play Time(5 Min Around Credit End)</value>
|
||||
</data>
|
||||
<data name="Password is Required" xml:space="preserve">
|
||||
<value>Password is required</value>
|
||||
</data>
|
||||
<data name="Confirm Password" xml:space="preserve">
|
||||
<value>Confirm Password</value>
|
||||
</data>
|
||||
<data name="Confirm Password is Required" xml:space="preserve">
|
||||
<value>Confirm password is required</value>
|
||||
</data>
|
||||
<data name="Unknown Error" xml:space="preserve">
|
||||
<value>Unknown Error</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>Success</value>
|
||||
</data>
|
||||
<data name="Ura Oni" xml:space="preserve">
|
||||
<value>Ura</value>
|
||||
</data>
|
||||
<data name="Oni" xml:space="preserve">
|
||||
<value>Extreme</value>
|
||||
</data>
|
||||
<data name="Hard" xml:space="preserve">
|
||||
<value>Hard</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>Normal</value>
|
||||
</data>
|
||||
<data name="Easy" xml:space="preserve">
|
||||
<value>Easy</value>
|
||||
</data>
|
||||
<data name="Rows Per Page:" xml:space="preserve">
|
||||
<value>Rows Per Page:</value>
|
||||
</data>
|
||||
</root>
|
@ -117,31 +117,28 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="dashboard" xml:space="preserve">
|
||||
<data name="Dashboard" xml:space="preserve">
|
||||
<value>メニュー</value>
|
||||
</data>
|
||||
<data name="users" xml:space="preserve">
|
||||
<data name="Users" xml:space="preserve">
|
||||
<value>ユーザー管理</value>
|
||||
</data>
|
||||
<data name="edit profile" xml:space="preserve">
|
||||
<value>プロフィール編集</value>
|
||||
</data>
|
||||
<data name="user" xml:space="preserve">
|
||||
<data name="User" xml:space="preserve">
|
||||
<value>ユーザー</value>
|
||||
</data>
|
||||
<data name="view play data" xml:space="preserve">
|
||||
<data name="View Play Data" xml:space="preserve">
|
||||
<value>プレイデータ</value>
|
||||
</data>
|
||||
<data name="high scores" xml:space="preserve">
|
||||
<value>演奏ゲーム</value>
|
||||
</data>
|
||||
<data name="dani dojo" xml:space="preserve">
|
||||
<value>段位道場</value>
|
||||
<data name="High Scores" xml:space="preserve">
|
||||
<value>自己ベスト</value>
|
||||
</data>
|
||||
<data name="Show QR Code" xml:space="preserve">
|
||||
<value>QRコード</value>
|
||||
</data>
|
||||
<data name="Manage Access Codes" xml:space="preserve">
|
||||
<data name="Access Codes" xml:space="preserve">
|
||||
<value>アクセスコード管理</value>
|
||||
</data>
|
||||
<data name="Change Password" xml:space="preserve">
|
||||
@ -156,7 +153,7 @@
|
||||
<data name="Welcome to TaikoWebUI!" xml:space="preserve">
|
||||
<value>TaikoWebUIへようこそ!</value>
|
||||
</data>
|
||||
<data name="Song" xml:space="preserve">
|
||||
<data name="Song Name" xml:space="preserve">
|
||||
<value>曲名</value>
|
||||
</data>
|
||||
<data name="Level" xml:space="preserve">
|
||||
@ -207,8 +204,8 @@
|
||||
<data name="Total Donderful Combos" xml:space="preserve">
|
||||
<value>ドンダフルコンボ回数</value>
|
||||
</data>
|
||||
<data name="Key_01" xml:space="preserve">
|
||||
<value>自己ベスト</value>
|
||||
<data name="Song List" xml:space="preserve">
|
||||
<value>曲目リスト</value>
|
||||
</data>
|
||||
<data name="Hide" xml:space="preserve">
|
||||
<value>隠す</value>
|
||||
@ -229,9 +226,9 @@
|
||||
<value>王冠</value>
|
||||
</data>
|
||||
<data name="No data." xml:space="preserve">
|
||||
<value>データがありません。</value>
|
||||
<value>データがありません</value>
|
||||
</data>
|
||||
<data name="Key_02" xml:space="preserve">
|
||||
<data name="Log In First" xml:space="preserve">
|
||||
<value>"ユーザー管理"タブでログインしてください。</value>
|
||||
</data>
|
||||
<data name="Total Hits" xml:space="preserve">
|
||||
@ -240,7 +237,7 @@
|
||||
<data name="Soul Gauge" xml:space="preserve">
|
||||
<value>魂ゲージ</value>
|
||||
</data>
|
||||
<data name="Songs" xml:space="preserve">
|
||||
<data name="Course Songs" xml:space="preserve">
|
||||
<value>課題曲</value>
|
||||
</data>
|
||||
<data name="Conditions" xml:space="preserve">
|
||||
@ -252,8 +249,8 @@
|
||||
<data name="Gold" xml:space="preserve">
|
||||
<value>金合格</value>
|
||||
</data>
|
||||
<data name="Failed" xml:space="preserve">
|
||||
<value>不合格</value>
|
||||
<data name="Not Cleared" xml:space="preserve">
|
||||
<value>未合格</value>
|
||||
</data>
|
||||
<data name="Pass" xml:space="preserve">
|
||||
<value>合格</value>
|
||||
@ -267,7 +264,7 @@
|
||||
<data name="Stage" xml:space="preserve">
|
||||
<value>曲目</value>
|
||||
</data>
|
||||
<data name="Key_03" xml:space="preserve">
|
||||
<data name="Dani Dojo" xml:space="preserve">
|
||||
<value>段位道場</value>
|
||||
</data>
|
||||
<data name="Profile" xml:space="preserve">
|
||||
@ -378,4 +375,223 @@
|
||||
<data name="DateFormat" xml:space="preserve">
|
||||
<value>yyyy/MM/dd HH:mm:ss</value>
|
||||
</data>
|
||||
<data name="Generate Invite Code" xml:space="preserve">
|
||||
<value>招待コード生成</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>登録</value>
|
||||
</data>
|
||||
<data name="reset_password_confirm_dialog_1" xml:space="preserve">
|
||||
<value>本当にこのユーザーのパスワードをリセットしますか?</value>
|
||||
</data>
|
||||
<data name="reset_password_confirm_dialog_2" xml:space="preserve">
|
||||
<value>これにより、ユーザーの現在のパスワードは削除され、ユーザーは再度登録する必要があります。</value>
|
||||
</data>
|
||||
<data name="Log In" xml:space="preserve">
|
||||
<value>ログイン</value>
|
||||
</data>
|
||||
<data name="Log Out" xml:space="preserve">
|
||||
<value>ログアウト</value>
|
||||
</data>
|
||||
<data name="Play Data" xml:space="preserve">
|
||||
<value>プレイデータ</value>
|
||||
</data>
|
||||
<data name="Add Access Code" xml:space="preserve">
|
||||
<value>アクセスコード追加</value>
|
||||
</data>
|
||||
<data name="New Access Code" xml:space="preserve">
|
||||
<value>新しいアクセスコード</value>
|
||||
</data>
|
||||
<data name="Delete" xml:space="preserve">
|
||||
<value>削除</value>
|
||||
</data>
|
||||
<data name="Access Code" xml:space="preserve">
|
||||
<value>アクセスコード</value>
|
||||
</data>
|
||||
<data name="Old Password" xml:space="preserve">
|
||||
<value>旧パスワード</value>
|
||||
</data>
|
||||
<data name="New Password" xml:space="preserve">
|
||||
<value>新しいパスワード</value>
|
||||
</data>
|
||||
<data name="Confirm New Password" xml:space="preserve">
|
||||
<value>パスワードの再入力</value>
|
||||
</data>
|
||||
<data name="Dialog OK" xml:space="preserve">
|
||||
<value>確認</value>
|
||||
</data>
|
||||
<data name="QR Code" xml:space="preserve">
|
||||
<value>QRコード</value>
|
||||
</data>
|
||||
<data name="Chojin" xml:space="preserve">
|
||||
<value>超人</value>
|
||||
</data>
|
||||
<data name="Eighth Dan" xml:space="preserve">
|
||||
<value>八段</value>
|
||||
</data>
|
||||
<data name="Fifth Dan" xml:space="preserve">
|
||||
<value>五段</value>
|
||||
</data>
|
||||
<data name="Fifth Kyuu" xml:space="preserve">
|
||||
<value>五級</value>
|
||||
</data>
|
||||
<data name="First Dan" xml:space="preserve">
|
||||
<value>一段</value>
|
||||
</data>
|
||||
<data name="First Kyuu" xml:space="preserve">
|
||||
<value>一級</value>
|
||||
</data>
|
||||
<data name="Fourth Dan" xml:space="preserve">
|
||||
<value>四段</value>
|
||||
</data>
|
||||
<data name="Fourth Kyuu" xml:space="preserve">
|
||||
<value>四級</value>
|
||||
</data>
|
||||
<data name="Gaiden" xml:space="preserve">
|
||||
<value>外伝</value>
|
||||
</data>
|
||||
<data name="Kuroto" xml:space="preserve">
|
||||
<value>玄人</value>
|
||||
</data>
|
||||
<data name="Meijin" xml:space="preserve">
|
||||
<value>名人</value>
|
||||
</data>
|
||||
<data name="Ninth Dan" xml:space="preserve">
|
||||
<value>九段</value>
|
||||
</data>
|
||||
<data name="Second Dan" xml:space="preserve">
|
||||
<value>二段</value>
|
||||
</data>
|
||||
<data name="Second Kyuu" xml:space="preserve">
|
||||
<value>二級</value>
|
||||
</data>
|
||||
<data name="Seventh Dan" xml:space="preserve">
|
||||
<value>七段</value>
|
||||
</data>
|
||||
<data name="Sixth Dan" xml:space="preserve">
|
||||
<value>六段</value>
|
||||
</data>
|
||||
<data name="Tatsujin" xml:space="preserve">
|
||||
<value>達人</value>
|
||||
</data>
|
||||
<data name="Tenth Dan" xml:space="preserve">
|
||||
<value>十段</value>
|
||||
</data>
|
||||
<data name="Third Dan" xml:space="preserve">
|
||||
<value>三段</value>
|
||||
</data>
|
||||
<data name="Third Kyuu" xml:space="preserve">
|
||||
<value>三級</value>
|
||||
</data>
|
||||
<data name="Gold Full Combo" xml:space="preserve">
|
||||
<value>金金合格</value>
|
||||
</data>
|
||||
<data name="Red Donderful Combo" xml:space="preserve">
|
||||
<value>虹赤合格</value>
|
||||
</data>
|
||||
<data name="Red Full Combo" xml:space="preserve">
|
||||
<value>金赤合格</value>
|
||||
</data>
|
||||
<data name="Gold Donderful Combo" xml:space="preserve">
|
||||
<value>虹金合格</value>
|
||||
</data>
|
||||
<data name="Song Title / Artist" xml:space="preserve">
|
||||
<value>曲名 / Artist</value>
|
||||
</data>
|
||||
<data name="Search by Title or Artist" xml:space="preserve">
|
||||
<value>曲名・アーティスト名で検索</value>
|
||||
</data>
|
||||
<data name="Filter by Genre" xml:space="preserve">
|
||||
<value>ジャンル検索</value>
|
||||
</data>
|
||||
<data name="Play History" xml:space="preserve">
|
||||
<value>プレー履歴</value>
|
||||
</data>
|
||||
<data name="No Play History Found" xml:space="preserve">
|
||||
<value>プレー履歴なし</value>
|
||||
</data>
|
||||
<data name="Password" xml:space="preserve">
|
||||
<value>パスワード</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="Play Time" xml:space="preserve">
|
||||
<value>プレー時間</value>
|
||||
</data>
|
||||
<data name="Rank" xml:space="preserve">
|
||||
<value>ランク</value>
|
||||
</data>
|
||||
<data name="Difficulty" xml:space="preserve">
|
||||
<value>難易度</value>
|
||||
</data>
|
||||
<data name="Song Number" xml:space="preserve">
|
||||
<value>曲数</value>
|
||||
</data>
|
||||
<data name="Search by Title, Artist or Date" xml:space="preserve">
|
||||
<value>曲名・アーティスト名・日付で検索</value>
|
||||
</data>
|
||||
<data name="Unregister" xml:space="preserve">
|
||||
<value>登録解除</value>
|
||||
</data>
|
||||
<data name="and" xml:space="preserve">
|
||||
<value>と</value>
|
||||
</data>
|
||||
<data name="other access code(s)" xml:space="preserve">
|
||||
<value>他のアクセスコード</value>
|
||||
</data>
|
||||
<data name="Copy to Clipboard" xml:space="preserve">
|
||||
<value>クリップボードにコピー</value>
|
||||
</data>
|
||||
<data name="Invite Code" xml:space="preserve">
|
||||
<value>招待コード</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>エラー</value>
|
||||
</data>
|
||||
<data name="Access Code is Required" xml:space="preserve">
|
||||
<value>アクセスコードが必要です</value>
|
||||
</data>
|
||||
<data name=""Invite Code (Optional)"" xml:space="preserve">
|
||||
<value>招待コード(任意)</value>
|
||||
</data>
|
||||
<data name="Last Play Date" xml:space="preserve">
|
||||
<value>前回プレイデート</value>
|
||||
</data>
|
||||
<data name="Last Play Time(5 Min Around Credit End)" xml:space="preserve">
|
||||
<value>前回プレー時間(クレジット終了5分以内)</value>
|
||||
</data>
|
||||
<data name="Password is Required" xml:space="preserve">
|
||||
<value>パスワードが必要です</value>
|
||||
</data>
|
||||
<data name="Confirm Password" xml:space="preserve">
|
||||
<value>パスワードの再入力</value>
|
||||
</data>
|
||||
<data name="Confirm Password is Required" xml:space="preserve">
|
||||
<value>アクセスコードの再入力が必要です</value>
|
||||
</data>
|
||||
<data name="Unknown Error" xml:space="preserve">
|
||||
<value>未知のエラー</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>成功</value>
|
||||
</data>
|
||||
<data name="Ura Oni" xml:space="preserve">
|
||||
<value>おに裏</value>
|
||||
</data>
|
||||
<data name="Oni" xml:space="preserve">
|
||||
<value>おに</value>
|
||||
</data>
|
||||
<data name="Hard" xml:space="preserve">
|
||||
<value>むずかしい</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>ふつう</value>
|
||||
</data>
|
||||
<data name="Easy" xml:space="preserve">
|
||||
<value>かんたん</value>
|
||||
</data>
|
||||
<data name="Rows Per Page:" xml:space="preserve">
|
||||
<value>1ページ当たりの行数</value>
|
||||
</data>
|
||||
</root>
|
@ -120,4 +120,271 @@
|
||||
<data name="DateFormat" xml:space="preserve">
|
||||
<value>MM/dd/yyyy h:mm:ss tt</value>
|
||||
</data>
|
||||
<data name="Generate Invite Code" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Log In" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Achievement Panel" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Achievement Panel Difficulty" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="AI Battle Data" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Bad" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Best Crown" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Best Rank" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Log Out" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Play Data" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="High Scores" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Access Codes" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Add Access Code" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="New Access Code" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Delete" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Access Code" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Old Password" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="New Password" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Confirm New Password" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Dialog OK" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="QR Code" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Fifth Kyuu" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Fourth Kyuu" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Third Kyuu" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Second Kyuu" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="First Kyuu" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="First Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Second Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Third Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Fourth Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Fifth Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Sixth Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Seventh Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Eighth Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Ninth Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Tenth Dan" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Kuroto" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Meijin" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Chojin" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Tatsujin" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Gaiden" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Gold Full Combo" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Red Full Combo" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Red Donderful Combo" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Gold Donderful Combo" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Song List" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Log In First" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Dani Dojo" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Course Songs" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Song Name" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Song Title / Artist" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Search by Title or Artist" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Filter by Genre" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Play History" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="No Play History Found" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Password" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Play Time" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Rank" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Difficulty" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Song Number" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Search by Title, Artist or Date" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Users" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Dashboard" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Unregister" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="and" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="other access code(s)" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Copy to Clipboard" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Invite Code" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="View Play Data" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Not Cleared" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="User" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Access Code is Required" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name=""Invite Code (Optional)"" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Last Play Date" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Last Play Time(5 Min Around Credit End)" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Password is Required" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Confirm Password" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Confirm Password is Required" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Unknown Error" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Ura Oni" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Oni" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Hard" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Easy" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="Rows Per Page:" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
</root>
|
@ -117,31 +117,28 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="dashboard" xml:space="preserve">
|
||||
<data name="Dashboard" xml:space="preserve">
|
||||
<value>主页</value>
|
||||
</data>
|
||||
<data name="users" xml:space="preserve">
|
||||
<data name="Users" xml:space="preserve">
|
||||
<value>用户管理</value>
|
||||
</data>
|
||||
<data name="edit profile" xml:space="preserve">
|
||||
<value>编辑档案</value>
|
||||
</data>
|
||||
<data name="user" xml:space="preserve">
|
||||
<data name="User" xml:space="preserve">
|
||||
<value>用户</value>
|
||||
</data>
|
||||
<data name="view play data" xml:space="preserve">
|
||||
<data name="View Play Data" xml:space="preserve">
|
||||
<value>查看记录</value>
|
||||
</data>
|
||||
<data name="high scores" xml:space="preserve">
|
||||
<value>演奏模式</value>
|
||||
</data>
|
||||
<data name="dani dojo" xml:space="preserve">
|
||||
<value>段位道场</value>
|
||||
<data name="High Scores" xml:space="preserve">
|
||||
<value>最高分</value>
|
||||
</data>
|
||||
<data name="Show QR Code" xml:space="preserve">
|
||||
<value>查看二维码</value>
|
||||
</data>
|
||||
<data name="Manage Access Codes" xml:space="preserve">
|
||||
<data name="Access Codes" xml:space="preserve">
|
||||
<value>管理访问码</value>
|
||||
</data>
|
||||
<data name="Change Password" xml:space="preserve">
|
||||
@ -156,7 +153,7 @@
|
||||
<data name="Welcome to TaikoWebUI!" xml:space="preserve">
|
||||
<value>欢迎来到TaikoWebUI!</value>
|
||||
</data>
|
||||
<data name="Song" xml:space="preserve">
|
||||
<data name="Song Name" xml:space="preserve">
|
||||
<value>曲名</value>
|
||||
</data>
|
||||
<data name="Level" xml:space="preserve">
|
||||
@ -207,8 +204,8 @@
|
||||
<data name="Total Donderful Combos" xml:space="preserve">
|
||||
<value>总完美连段次数</value>
|
||||
</data>
|
||||
<data name="Key_01" xml:space="preserve">
|
||||
<value>最佳得分</value>
|
||||
<data name="Song List" xml:space="preserve">
|
||||
<value>曲目列表</value>
|
||||
</data>
|
||||
<data name="Hide" xml:space="preserve">
|
||||
<value>隐藏</value>
|
||||
@ -229,18 +226,18 @@
|
||||
<value>王冠</value>
|
||||
</data>
|
||||
<data name="No data." xml:space="preserve">
|
||||
<value>没有数据.</value>
|
||||
<value>没有数据</value>
|
||||
</data>
|
||||
<data name="Key_02" xml:space="preserve">
|
||||
<data name="Log In First" xml:space="preserve">
|
||||
<value>"请先在用户管理那边登录</value>
|
||||
</data>
|
||||
<data name="Total Hits" xml:space="preserve">
|
||||
<value>"总打击次数</value>
|
||||
<value>总打击次数</value>
|
||||
</data>
|
||||
<data name="Soul Gauge" xml:space="preserve">
|
||||
<value>魂条</value>
|
||||
</data>
|
||||
<data name="Songs" xml:space="preserve">
|
||||
<data name="Course Songs" xml:space="preserve">
|
||||
<value>课题曲</value>
|
||||
</data>
|
||||
<data name="Conditions" xml:space="preserve">
|
||||
@ -252,8 +249,8 @@
|
||||
<data name="Gold" xml:space="preserve">
|
||||
<value>金合格</value>
|
||||
</data>
|
||||
<data name="Failed" xml:space="preserve">
|
||||
<value>不合格</value>
|
||||
<data name="Not Cleared" xml:space="preserve">
|
||||
<value>未合格</value>
|
||||
</data>
|
||||
<data name="Pass" xml:space="preserve">
|
||||
<value>合格</value>
|
||||
@ -267,7 +264,7 @@
|
||||
<data name="Stage" xml:space="preserve">
|
||||
<value>曲目</value>
|
||||
</data>
|
||||
<data name="Key_03" xml:space="preserve">
|
||||
<data name="Dani Dojo" xml:space="preserve">
|
||||
<value>段位道场</value>
|
||||
</data>
|
||||
<data name="Profile" xml:space="preserve">
|
||||
@ -378,4 +375,220 @@
|
||||
<data name="DateFormat" xml:space="preserve">
|
||||
<value>yyyy/M/d HH:mm:ss</value>
|
||||
</data>
|
||||
<data name="Generate Invite Code" xml:space="preserve">
|
||||
<value>生成邀请码</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>注册</value>
|
||||
</data>
|
||||
<data name="reset_password_confirm_dialog_1" xml:space="preserve">
|
||||
<value>确定要为这位用户重置密码吗?</value>
|
||||
</data>
|
||||
<data name="reset_password_confirm_dialog_2" xml:space="preserve">
|
||||
<value>重置密码后,用户需要再次注册</value>
|
||||
</data>
|
||||
<data name="Log In" xml:space="preserve">
|
||||
<value>登录</value>
|
||||
</data>
|
||||
<data name="Log Out" xml:space="preserve">
|
||||
<value>登出</value>
|
||||
</data>
|
||||
<data name="Play Data" xml:space="preserve">
|
||||
<value>游玩数据</value>
|
||||
</data>
|
||||
<data name="Add Access Code" xml:space="preserve">
|
||||
<value>新增访问码</value>
|
||||
</data>
|
||||
<data name="New Access Code" xml:space="preserve">
|
||||
<value>新访问码</value>
|
||||
</data>
|
||||
<data name="Delete" xml:space="preserve">
|
||||
<value>删除</value>
|
||||
</data>
|
||||
<data name="Access Code" xml:space="preserve">
|
||||
<value>访问码</value>
|
||||
</data>
|
||||
<data name="Old Password" xml:space="preserve">
|
||||
<value>现有密码</value>
|
||||
</data>
|
||||
<data name="New Password" xml:space="preserve">
|
||||
<value>新密码</value>
|
||||
</data>
|
||||
<data name="Confirm New Password" xml:space="preserve">
|
||||
<value>再次输入新密码</value>
|
||||
</data>
|
||||
<data name="Dialog OK" xml:space="preserve">
|
||||
<value>确定</value>
|
||||
</data>
|
||||
<data name="QR Code" xml:space="preserve">
|
||||
<value>二维码</value>
|
||||
</data>
|
||||
<data name="Chojin" xml:space="preserve">
|
||||
<value>超人</value>
|
||||
</data>
|
||||
<data name="Eighth Dan" xml:space="preserve">
|
||||
<value>八段</value>
|
||||
</data>
|
||||
<data name="Fifth Dan" xml:space="preserve">
|
||||
<value>五段</value>
|
||||
</data>
|
||||
<data name="Fifth Kyuu" xml:space="preserve">
|
||||
<value>五级</value>
|
||||
</data>
|
||||
<data name="First Dan" xml:space="preserve">
|
||||
<value>一段</value>
|
||||
</data>
|
||||
<data name="First Kyuu" xml:space="preserve">
|
||||
<value>一级</value>
|
||||
</data>
|
||||
<data name="Fourth Dan" xml:space="preserve">
|
||||
<value>四段</value>
|
||||
</data>
|
||||
<data name="Fourth Kyuu" xml:space="preserve">
|
||||
<value>四级</value>
|
||||
</data>
|
||||
<data name="Gaiden" xml:space="preserve">
|
||||
<value>外传</value>
|
||||
</data>
|
||||
<data name="Kuroto" xml:space="preserve">
|
||||
<value>玄人</value>
|
||||
</data>
|
||||
<data name="Meijin" xml:space="preserve">
|
||||
<value>名人</value>
|
||||
</data>
|
||||
<data name="Ninth Dan" xml:space="preserve">
|
||||
<value>九段</value>
|
||||
</data>
|
||||
<data name="Second Dan" xml:space="preserve">
|
||||
<value>二段</value>
|
||||
</data>
|
||||
<data name="Second Kyuu" xml:space="preserve">
|
||||
<value>二级</value>
|
||||
</data>
|
||||
<data name="Seventh Dan" xml:space="preserve">
|
||||
<value>七段</value>
|
||||
</data>
|
||||
<data name="Sixth Dan" xml:space="preserve">
|
||||
<value>六段</value>
|
||||
</data>
|
||||
<data name="Tatsujin" xml:space="preserve">
|
||||
<value>达人</value>
|
||||
</data>
|
||||
<data name="Tenth Dan" xml:space="preserve">
|
||||
<value>十段</value>
|
||||
</data>
|
||||
<data name="Third Dan" xml:space="preserve">
|
||||
<value>三段</value>
|
||||
</data>
|
||||
<data name="Third Kyuu" xml:space="preserve">
|
||||
<value>三级</value>
|
||||
</data>
|
||||
<data name="Gold Full Combo" xml:space="preserve">
|
||||
<value>金金合格</value>
|
||||
</data>
|
||||
<data name="Red Donderful Combo" xml:space="preserve">
|
||||
<value>虹赤合格</value>
|
||||
</data>
|
||||
<data name="Red Full Combo" xml:space="preserve">
|
||||
<value>金赤合格</value>
|
||||
</data>
|
||||
<data name="Gold Donderful Combo" xml:space="preserve">
|
||||
<value>虹金合格</value>
|
||||
</data>
|
||||
<data name="Song Title / Artist" xml:space="preserve">
|
||||
<value>曲名 / Artist</value>
|
||||
</data>
|
||||
<data name="Search by Title or Artist" xml:space="preserve">
|
||||
<value>曲名/Artist名搜索</value>
|
||||
</data>
|
||||
<data name="Filter by Genre" xml:space="preserve">
|
||||
<value>分类过滤</value>
|
||||
</data>
|
||||
<data name="Play History" xml:space="preserve">
|
||||
<value>游玩历史</value>
|
||||
</data>
|
||||
<data name="No Play History Found" xml:space="preserve">
|
||||
<value>沒有游玩历史</value>
|
||||
</data>
|
||||
<data name="Password" xml:space="preserve">
|
||||
<value>密码</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>设定</value>
|
||||
</data>
|
||||
<data name="Play Time" xml:space="preserve">
|
||||
<value>游玩时间</value>
|
||||
</data>
|
||||
<data name="Rank" xml:space="preserve">
|
||||
<value>评价</value>
|
||||
</data>
|
||||
<data name="Difficulty" xml:space="preserve">
|
||||
<value>难度</value>
|
||||
</data>
|
||||
<data name="Song Number" xml:space="preserve">
|
||||
<value>曲数</value>
|
||||
</data>
|
||||
<data name="Search by Title, Artist or Date" xml:space="preserve">
|
||||
<value>曲名/Artist名/日期搜索</value>
|
||||
</data>
|
||||
<data name="Unregister" xml:space="preserve">
|
||||
<value>注销账号</value>
|
||||
</data>
|
||||
<data name="and" xml:space="preserve">
|
||||
<value>和</value>
|
||||
</data>
|
||||
<data name="other access code(s)" xml:space="preserve">
|
||||
<value>个其他访问码</value>
|
||||
</data>
|
||||
<data name="Copy to Clipboard" xml:space="preserve">
|
||||
<value>复制到粘贴板</value>
|
||||
</data>
|
||||
<data name="Invite Code" xml:space="preserve">
|
||||
<value>邀请码</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>错误</value>
|
||||
</data>
|
||||
<data name="Access Code is Required" xml:space="preserve">
|
||||
<value>访问码为必填项</value>
|
||||
</data>
|
||||
<data name=""Invite Code (Optional)"" xml:space="preserve">
|
||||
<value>邀请码(可选)</value>
|
||||
</data>
|
||||
<data name="Last Play Time(5 Min Around Credit End)" xml:space="preserve">
|
||||
<value>最后游玩时间(游戏结束5分钟内)</value>
|
||||
</data>
|
||||
<data name="Password is Required" xml:space="preserve">
|
||||
<value>密码为必填项</value>
|
||||
</data>
|
||||
<data name="Confirm Password" xml:space="preserve">
|
||||
<value>再次输入密码</value>
|
||||
</data>
|
||||
<data name="Confirm Password is Required" xml:space="preserve">
|
||||
<value>再次输入密码为必填项</value>
|
||||
</data>
|
||||
<data name="Unknown Error" xml:space="preserve">
|
||||
<value>不明错误</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>成功</value>
|
||||
</data>
|
||||
<data name="Ura Oni" xml:space="preserve">
|
||||
<value>里譜面</value>
|
||||
</data>
|
||||
<data name="Oni" xml:space="preserve">
|
||||
<value>魔王</value>
|
||||
</data>
|
||||
<data name="Hard" xml:space="preserve">
|
||||
<value>困难</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>普通</value>
|
||||
</data>
|
||||
<data name="Easy" xml:space="preserve">
|
||||
<value>简单</value>
|
||||
</data>
|
||||
<data name="Rows Per Page:" xml:space="preserve">
|
||||
<value>每页行数</value>
|
||||
</data>
|
||||
</root>
|
@ -117,32 +117,29 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="dashboard" xml:space="preserve">
|
||||
<data name="Dashboard" xml:space="preserve">
|
||||
<value>首頁</value>
|
||||
</data>
|
||||
<data name="users" xml:space="preserve">
|
||||
<data name="Users" xml:space="preserve">
|
||||
<value>管理用戶</value>
|
||||
</data>
|
||||
<data name="edit profile" xml:space="preserve">
|
||||
<value>編輯檔案</value>
|
||||
</data>
|
||||
<data name="user" xml:space="preserve">
|
||||
<data name="User" xml:space="preserve">
|
||||
<value>用戶</value>
|
||||
</data>
|
||||
<data name="view play data" xml:space="preserve">
|
||||
<data name="View Play Data" xml:space="preserve">
|
||||
<value>查看記錄</value>
|
||||
</data>
|
||||
<data name="high scores" xml:space="preserve">
|
||||
<value>演奏模式</value>
|
||||
</data>
|
||||
<data name="dani dojo" xml:space="preserve">
|
||||
<value>段位道場</value>
|
||||
<data name="High Scores" xml:space="preserve">
|
||||
<value>最高分</value>
|
||||
</data>
|
||||
<data name="Show QR Code" xml:space="preserve">
|
||||
<value>查看QR Code</value>
|
||||
</data>
|
||||
<data name="Manage Access Codes" xml:space="preserve">
|
||||
<value>管理Access Codes</value>
|
||||
<data name="Access Codes" xml:space="preserve">
|
||||
<value>管理訪問碼</value>
|
||||
</data>
|
||||
<data name="Change Password" xml:space="preserve">
|
||||
<value>更改密碼</value>
|
||||
@ -156,7 +153,7 @@
|
||||
<data name="Welcome to TaikoWebUI!" xml:space="preserve">
|
||||
<value>歡迎來到TaikoWebUI!</value>
|
||||
</data>
|
||||
<data name="Song" xml:space="preserve">
|
||||
<data name="Song Name" xml:space="preserve">
|
||||
<value>曲名</value>
|
||||
</data>
|
||||
<data name="Level" xml:space="preserve">
|
||||
@ -207,8 +204,8 @@
|
||||
<data name="Total Donderful Combos" xml:space="preserve">
|
||||
<value>總全良次數</value>
|
||||
</data>
|
||||
<data name="Key_01" xml:space="preserve">
|
||||
<value>最佳得分</value>
|
||||
<data name="Song List" xml:space="preserve">
|
||||
<value>曲目列表</value>
|
||||
</data>
|
||||
<data name="Hide" xml:space="preserve">
|
||||
<value>隱藏</value>
|
||||
@ -229,18 +226,18 @@
|
||||
<value>皇冠</value>
|
||||
</data>
|
||||
<data name="No data." xml:space="preserve">
|
||||
<value>沒有數據.</value>
|
||||
<value>沒有數據</value>
|
||||
</data>
|
||||
<data name="Key_02" xml:space="preserve">
|
||||
<data name="Log In First" xml:space="preserve">
|
||||
<value>"請先在管理用戶那邊登入</value>
|
||||
</data>
|
||||
<data name="Total Hits" xml:space="preserve">
|
||||
<value>"總打擊次數</value>
|
||||
<value>總打擊次數</value>
|
||||
</data>
|
||||
<data name="Soul Gauge" xml:space="preserve">
|
||||
<value>魂條</value>
|
||||
</data>
|
||||
<data name="Songs" xml:space="preserve">
|
||||
<data name="Course Songs" xml:space="preserve">
|
||||
<value>課題曲</value>
|
||||
</data>
|
||||
<data name="Conditions" xml:space="preserve">
|
||||
@ -252,8 +249,8 @@
|
||||
<data name="Gold" xml:space="preserve">
|
||||
<value>金合格</value>
|
||||
</data>
|
||||
<data name="Failed" xml:space="preserve">
|
||||
<value>不合格</value>
|
||||
<data name="Not Cleared" xml:space="preserve">
|
||||
<value>未合格</value>
|
||||
</data>
|
||||
<data name="Pass" xml:space="preserve">
|
||||
<value>合格</value>
|
||||
@ -267,7 +264,7 @@
|
||||
<data name="Stage" xml:space="preserve">
|
||||
<value>曲目</value>
|
||||
</data>
|
||||
<data name="Key_03" xml:space="preserve">
|
||||
<data name="Dani Dojo" xml:space="preserve">
|
||||
<value>段位道埸</value>
|
||||
</data>
|
||||
<data name="Profile" xml:space="preserve">
|
||||
@ -378,4 +375,220 @@
|
||||
<data name="DateFormat" xml:space="preserve">
|
||||
<value>yyyy/M/d HH:mm:ss</value>
|
||||
</data>
|
||||
<data name="Generate Invite Code" xml:space="preserve">
|
||||
<value>生成邀請碼</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>注冊</value>
|
||||
</data>
|
||||
<data name="reset_password_confirm_dialog_1" xml:space="preserve">
|
||||
<value>確定要為這位用戶重置密碼嗎?</value>
|
||||
</data>
|
||||
<data name="reset_password_confirm_dialog_2" xml:space="preserve">
|
||||
<value>重置密碼后,用戶需要再次注冊</value>
|
||||
</data>
|
||||
<data name="Log In" xml:space="preserve">
|
||||
<value>登錄</value>
|
||||
</data>
|
||||
<data name="Log Out" xml:space="preserve">
|
||||
<value>登出</value>
|
||||
</data>
|
||||
<data name="Play Data" xml:space="preserve">
|
||||
<value>游玩數據</value>
|
||||
</data>
|
||||
<data name="Add Access Code" xml:space="preserve">
|
||||
<value>新增訪問碼</value>
|
||||
</data>
|
||||
<data name="New Access Code" xml:space="preserve">
|
||||
<value>新訪問碼</value>
|
||||
</data>
|
||||
<data name="Delete" xml:space="preserve">
|
||||
<value>删除</value>
|
||||
</data>
|
||||
<data name="Access Code" xml:space="preserve">
|
||||
<value>訪問碼</value>
|
||||
</data>
|
||||
<data name="Old Password" xml:space="preserve">
|
||||
<value>現有密碼</value>
|
||||
</data>
|
||||
<data name="New Password" xml:space="preserve">
|
||||
<value>新密碼</value>
|
||||
</data>
|
||||
<data name="Confirm New Password" xml:space="preserve">
|
||||
<value>再次輸入新密碼</value>
|
||||
</data>
|
||||
<data name="Dialog OK" xml:space="preserve">
|
||||
<value>確認</value>
|
||||
</data>
|
||||
<data name="QR Code" xml:space="preserve">
|
||||
<value>查看QR Code</value>
|
||||
</data>
|
||||
<data name="Chojin" xml:space="preserve">
|
||||
<value>超人</value>
|
||||
</data>
|
||||
<data name="Eighth Dan" xml:space="preserve">
|
||||
<value>八段</value>
|
||||
</data>
|
||||
<data name="Gaiden" xml:space="preserve">
|
||||
<value>外傳</value>
|
||||
</data>
|
||||
<data name="Fourth Kyuu" xml:space="preserve">
|
||||
<value>四級</value>
|
||||
</data>
|
||||
<data name="Fourth Dan" xml:space="preserve">
|
||||
<value>四段</value>
|
||||
</data>
|
||||
<data name="First Kyuu" xml:space="preserve">
|
||||
<value>一級</value>
|
||||
</data>
|
||||
<data name="First Dan" xml:space="preserve">
|
||||
<value>一段</value>
|
||||
</data>
|
||||
<data name="Fifth Kyuu" xml:space="preserve">
|
||||
<value>五級</value>
|
||||
</data>
|
||||
<data name="Fifth Dan" xml:space="preserve">
|
||||
<value>五段</value>
|
||||
</data>
|
||||
<data name="Kuroto" xml:space="preserve">
|
||||
<value>玄人</value>
|
||||
</data>
|
||||
<data name="Meijin" xml:space="preserve">
|
||||
<value>名人</value>
|
||||
</data>
|
||||
<data name="Ninth Dan" xml:space="preserve">
|
||||
<value>九段</value>
|
||||
</data>
|
||||
<data name="Second Dan" xml:space="preserve">
|
||||
<value>二段</value>
|
||||
</data>
|
||||
<data name="Second Kyuu" xml:space="preserve">
|
||||
<value>二級</value>
|
||||
</data>
|
||||
<data name="Seventh Dan" xml:space="preserve">
|
||||
<value>七段</value>
|
||||
</data>
|
||||
<data name="Sixth Dan" xml:space="preserve">
|
||||
<value>六段</value>
|
||||
</data>
|
||||
<data name="Tatsujin" xml:space="preserve">
|
||||
<value>達人</value>
|
||||
</data>
|
||||
<data name="Tenth Dan" xml:space="preserve">
|
||||
<value>十段</value>
|
||||
</data>
|
||||
<data name="Third Dan" xml:space="preserve">
|
||||
<value>三段</value>
|
||||
</data>
|
||||
<data name="Third Kyuu" xml:space="preserve">
|
||||
<value>三級</value>
|
||||
</data>
|
||||
<data name="Gold Full Combo" xml:space="preserve">
|
||||
<value>金金合格</value>
|
||||
</data>
|
||||
<data name="Red Donderful Combo" xml:space="preserve">
|
||||
<value>虹赤合格</value>
|
||||
</data>
|
||||
<data name="Red Full Combo" xml:space="preserve">
|
||||
<value>金赤合格</value>
|
||||
</data>
|
||||
<data name="Gold Donderful Combo" xml:space="preserve">
|
||||
<value>虹金合格</value>
|
||||
</data>
|
||||
<data name="Song Title / Artist" xml:space="preserve">
|
||||
<value>曲名 / Artist</value>
|
||||
</data>
|
||||
<data name="Search by Title or Artist" xml:space="preserve">
|
||||
<value>曲名/Artist名搜索</value>
|
||||
</data>
|
||||
<data name="Filter by Genre" xml:space="preserve">
|
||||
<value>分類過濾</value>
|
||||
</data>
|
||||
<data name="Play History" xml:space="preserve">
|
||||
<value>游玩歷史</value>
|
||||
</data>
|
||||
<data name="No Play History Found" xml:space="preserve">
|
||||
<value>没有游玩歷史</value>
|
||||
</data>
|
||||
<data name="Password" xml:space="preserve">
|
||||
<value>密碼</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="Play Time" xml:space="preserve">
|
||||
<value>游玩時間</value>
|
||||
</data>
|
||||
<data name="Rank" xml:space="preserve">
|
||||
<value>評價</value>
|
||||
</data>
|
||||
<data name="Difficulty" xml:space="preserve">
|
||||
<value>難度</value>
|
||||
</data>
|
||||
<data name="Song Number" xml:space="preserve">
|
||||
<value>曲數</value>
|
||||
</data>
|
||||
<data name="Search by Title, Artist or Date" xml:space="preserve">
|
||||
<value>曲名/Artist名/日期搜索</value>
|
||||
</data>
|
||||
<data name="Unregister" xml:space="preserve">
|
||||
<value>注銷賬號</value>
|
||||
</data>
|
||||
<data name="and" xml:space="preserve">
|
||||
<value>和</value>
|
||||
</data>
|
||||
<data name="other access code(s)" xml:space="preserve">
|
||||
<value>個其他訪問碼</value>
|
||||
</data>
|
||||
<data name="Copy to Clipboard" xml:space="preserve">
|
||||
<value>複製到粘貼板</value>
|
||||
</data>
|
||||
<data name="Invite Code" xml:space="preserve">
|
||||
<value>邀請碼</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value> 錯誤</value>
|
||||
</data>
|
||||
<data name="Access Code is Required" xml:space="preserve">
|
||||
<value>訪問碼為必填項</value>
|
||||
</data>
|
||||
<data name=""Invite Code (Optional)"" xml:space="preserve">
|
||||
<value>邀請碼(可選)</value>
|
||||
</data>
|
||||
<data name="Last Play Time(5 Min Around Credit End)" xml:space="preserve">
|
||||
<value>最後游玩時間(游戲結束5分鐘内)</value>
|
||||
</data>
|
||||
<data name="Password is Required" xml:space="preserve">
|
||||
<value>密碼為必填項</value>
|
||||
</data>
|
||||
<data name="Confirm Password" xml:space="preserve">
|
||||
<value>再次輸入密碼</value>
|
||||
</data>
|
||||
<data name="Confirm Password is Required" xml:space="preserve">
|
||||
<value>再次輸入密碼為必填項</value>
|
||||
</data>
|
||||
<data name="Unknown Error" xml:space="preserve">
|
||||
<value>不明錯誤</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>成功</value>
|
||||
</data>
|
||||
<data name="Ura Oni" xml:space="preserve">
|
||||
<value>裏譜面</value>
|
||||
</data>
|
||||
<data name="Oni" xml:space="preserve">
|
||||
<value>魔王</value>
|
||||
</data>
|
||||
<data name="Hard" xml:space="preserve">
|
||||
<value>困難</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>普通</value>
|
||||
</data>
|
||||
<data name="Easy" xml:space="preserve">
|
||||
<value>簡單</value>
|
||||
</data>
|
||||
<data name="Rows Per Page:" xml:space="preserve">
|
||||
<value>每頁行數</value>
|
||||
</data>
|
||||
</root>
|
@ -1,43 +1,36 @@
|
||||
@page "/Users/{baid:int}/AccessCode"
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject TaikoWebUI.Utilities.StringUtil StringUtil;
|
||||
@inject Utilities.StringUtil StringUtil;
|
||||
|
||||
@if (response is not null)
|
||||
{
|
||||
@if ((LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin))) || User is null)
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin))) {
|
||||
NavigationManager.NavigateTo(!AuthService.IsLoggedIn ? "/Login" : "/");
|
||||
} else if (User is null) {
|
||||
// Loading ...
|
||||
<MudContainer Style="display:flex;margin:50px 0;align-items:center;justify-content:center;">
|
||||
<MudProgressCircular Indeterminate="true" Size="Size.Large" Color="Color.Primary" />
|
||||
</MudContainer>
|
||||
} else {
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h4">Access Codes</MudText>
|
||||
<MudText Typo="Typo.h4">@Localizer["Access Codes"]</MudText>
|
||||
<MudGrid Class="my-4 pb-10">
|
||||
<MudItem xs="12">
|
||||
<MudCard Outlined="true" Class="mb-6">
|
||||
<MudCardContent>
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.h6">Add Access Code</MudText>
|
||||
<MudText Typo="Typo.h6">@Localizer["Add Access Code"]</MudText>
|
||||
<MudForm @ref="bindAccessCodeForm">
|
||||
<MudGrid Spacing="2" Class="mt-4">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudTextField @bind-value="inputAccessCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Required="@true" RequiredError="Access Code is required" Variant="Variant.Outlined" Margin="Margin.Dense"
|
||||
Label="New Access Code" />
|
||||
Label=@Localizer["New Access Code"] />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton OnClick="OnBind" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled" Class="mt-1">Add</MudButton>
|
||||
<MudButton OnClick="OnBind" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled" Class="mt-1">@Localizer["Add"]</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudForm>
|
||||
@ -50,7 +43,7 @@
|
||||
<MudCardContent>
|
||||
<MudGrid Spacing="3" Class="pb-2">
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.h6">Access Codes</MudText>
|
||||
<MudText Typo="Typo.h6">@Localizer["Access Code"]</MudText>
|
||||
</MudItem>
|
||||
@for (var idx = 0; idx < User.AccessCodes.Count; idx++)
|
||||
{
|
||||
@ -83,12 +76,4 @@
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer Style="display:flex;margin:50px 0;align-items:center;justify-content:center;">
|
||||
<MudProgressCircular Indeterminate="true" Size="Size.Large" Color="Color.Primary" />
|
||||
</MudContainer>
|
||||
}
|
@ -10,9 +10,8 @@ public partial class AccessCode
|
||||
private string inputAccessCode = "";
|
||||
private MudForm bindAccessCodeForm = default!;
|
||||
|
||||
private User? User { get; set; } = new();
|
||||
private User? User { get; set; }
|
||||
|
||||
private DashboardResponse? response;
|
||||
private UserSetting? userSetting;
|
||||
|
||||
private readonly List<BreadcrumbItem> breadcrumbs = new();
|
||||
@ -24,32 +23,28 @@ public partial class AccessCode
|
||||
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Dashboard", href: "/"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Users", href: "/Users"));
|
||||
};
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
|
||||
}
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem("Access Codes", href: $"/Users/{Baid}/AccessCode", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Access Codes"], href: $"/Users/{Baid}/AccessCode", disabled: false));
|
||||
}
|
||||
|
||||
private async Task InitializeUser()
|
||||
{
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
LoginService.ResetLoggedInUser(response);
|
||||
if (LoginService.IsAdmin || !LoginService.LoginRequired)
|
||||
if (!AuthService.LoginRequired)
|
||||
{
|
||||
if (response is not null)
|
||||
{
|
||||
User = response.Users.FirstOrDefault(u => u.Baid == Baid);
|
||||
var users = await Client.GetFromJsonAsync<List<User>>("api/Users");
|
||||
if (users != null) User = users.FirstOrDefault(u => u.Baid == Baid);
|
||||
}
|
||||
}
|
||||
else if (LoginService.IsLoggedIn)
|
||||
else if (AuthService.IsLoggedIn)
|
||||
{
|
||||
User = LoginService.GetLoggedInUser();
|
||||
User = await Client.GetFromJsonAsync<User>($"api/Users/{Baid}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +56,7 @@ public partial class AccessCode
|
||||
{ x => x.AccessCode, accessCode }
|
||||
};
|
||||
|
||||
var dialog = DialogService.Show<AccessCodeDeleteConfirmDialog>("Delete Access Code", parameters);
|
||||
var dialog = await DialogService.ShowAsync<AccessCodeDeleteConfirmDialog>("Delete Access Code", parameters);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result.Canceled) return;
|
||||
@ -72,9 +67,8 @@ public partial class AccessCode
|
||||
|
||||
private async Task OnBind()
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
var result = await LoginService.BindAccessCode(inputAccessCode.ToUpper().Trim(), response.Users.First(u => u.Baid == Baid), Client);
|
||||
if (User == null) return;
|
||||
var result = await AuthService.BindAccessCode(inputAccessCode.ToUpper().Trim(), User);
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
@ -122,5 +116,4 @@ public partial class AccessCode
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/ChangePassword"
|
||||
|
||||
@if (LoginService.OnlyAdmin || !LoginService.LoginRequired)
|
||||
@if (AuthService.OnlyAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LoginService.IsLoggedIn)
|
||||
if (AuthService.IsLoggedIn)
|
||||
{
|
||||
<MudContainer>
|
||||
<MudGrid Justify="Justify.Center">
|
||||
@ -19,24 +19,24 @@ else
|
||||
<MudCard Elevation="0" Outlined="true">
|
||||
<MudCardContent>
|
||||
<MudForm @ref="changePasswordForm">
|
||||
<MudText Typo="Typo.h5" Class="mb-4">Change Password</MudText>
|
||||
<MudText Typo="Typo.h5" Class="mb-4">@Localizer["Change Password"]</MudText>
|
||||
<div style="display:flex;flex-direction:column;gap:15px;">
|
||||
<MudTextField @bind-value="cardNum" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Required="@true" RequiredError="Access code is required"
|
||||
Label="Access Code" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
Label=@Localizer["Access Code"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudTextField @bind-Value="oldPassword" InputType="InputType.Password"
|
||||
T="string" FullWidth="true" Required="@true"
|
||||
RequiredError="Old Password is required"
|
||||
Label="Old Password" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
Label=@Localizer["Old Password"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudTextField @bind-Value="newPassword" InputType="InputType.Password"
|
||||
T="string" FullWidth="true" Required="@true"
|
||||
RequiredError="Password is required"
|
||||
Label="New Password" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
Label=@Localizer["New Password"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudTextField @bind-Value="confirmNewPassword" InputType="InputType.Password"
|
||||
T="string" FullWidth="true" Required="@true"
|
||||
RequiredError="Confirm password is required"
|
||||
Label="Confirm New Password" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudButton OnClick="OnChangePassword" FullWidth="true" Class="mt-3" StartIcon="@Icons.Material.Filled.Edit" Color="Color.Primary" Variant="Variant.Filled">Change password</MudButton>
|
||||
Label=@Localizer["Confirm New Password"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudButton OnClick="OnChangePassword" FullWidth="true" Class="mt-3" StartIcon="@Icons.Material.Filled.Edit" Color="Color.Primary" Variant="Variant.Filled">@Localizer["Change Password"]</MudButton>
|
||||
</div>
|
||||
</MudForm>
|
||||
</MudCardContent>
|
||||
|
@ -8,57 +8,63 @@ public partial class ChangePassword
|
||||
private string newPassword = "";
|
||||
private string oldPassword = "";
|
||||
|
||||
private DashboardResponse? response;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
}
|
||||
|
||||
private async Task OnChangePassword()
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
var result = await LoginService.ChangePassword(cardNum, oldPassword, newPassword, confirmNewPassword,
|
||||
response, Client);
|
||||
var result = await AuthService.ChangePassword(cardNum, oldPassword, newPassword, confirmNewPassword);
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
"Only admin can log in.",
|
||||
"Ok");
|
||||
Localizer["Dialog OK"]);
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Success",
|
||||
Localizer["Success"],
|
||||
"Password changed successfully.",
|
||||
"Ok");
|
||||
Localizer["Dialog OK"]);
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
"Confirm new password is not the same as new password.",
|
||||
"Ok");
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Card number not found.<br />Please play one game with this card number to register it.",
|
||||
"Ok");
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Old password is wrong!",
|
||||
"Ok");
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Card number not registered.<br />Please use register button to create a password first.",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 6:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/Users/{baid:int}/DaniDojo"
|
||||
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
@ -20,7 +20,7 @@
|
||||
else
|
||||
{
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h4">@Localizer["Key_03"]</MudText>
|
||||
<MudText Typo="Typo.h4">@Localizer["Dani Dojo"]</MudText>
|
||||
<MudGrid Class="my-4 pb-10">
|
||||
<MudItem xs="12">
|
||||
<MudPaper Elevation="0" Outlined="true">
|
||||
@ -58,7 +58,7 @@ else
|
||||
<MudCard Outlined="true">
|
||||
<MudCardHeader Class="pb-0">
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">@Localizer["Key_01"]</MudText>
|
||||
<MudText Typo="Typo.h6">@Localizer["Score"]</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="d-flex py-10" Style="justify-content:center">
|
||||
@ -119,7 +119,7 @@ else
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mt-10 mb-4">@Localizer["Songs"]</MudText>
|
||||
<MudText Typo="Typo.h5" Class="mt-10 mb-4">@Localizer["Course Songs"]</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudGrid Class="d-block">
|
||||
@ -214,7 +214,7 @@ else
|
||||
var redRequirement = GetSoulGauge(danData, false);
|
||||
var goldRequirement = GetSoulGauge(danData, true);
|
||||
var barClass = "bar-default";
|
||||
var resultText = @Localizer["Failed"];
|
||||
var resultText = @Localizer["Not Cleared"];
|
||||
}
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
|
||||
@ -284,7 +284,7 @@ else
|
||||
var redRequirement = border.RedBorderTotal;
|
||||
var goldRequirement = border.GoldBorderTotal;
|
||||
var barClass = "bar-default";
|
||||
var resultText = @Localizer["Failed"];
|
||||
var resultText = @Localizer["Not Cleared"];
|
||||
}
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
|
||||
@ -370,7 +370,7 @@ else
|
||||
var redRequirement = GetSongBorderCondition(border, songNumber, false);
|
||||
var goldRequirement = GetSongBorderCondition(border, songNumber, true);
|
||||
var barClass = "bar-default";
|
||||
var resultText = @Localizer["Failed"];
|
||||
var resultText = Localizer["Not Cleared"];
|
||||
|
||||
<MudItem xs="12" md="4">
|
||||
<MudCard Outlined="true">
|
||||
@ -381,7 +381,7 @@ else
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
|
||||
@if (bestDataMap.TryGetValue(danId, out var danBestData))
|
||||
@if (bestDataMap.TryGetValue(danId, out var danBestData) && (danBestData.DanBestStageDataList.Count > songNumber))
|
||||
{
|
||||
var bestData = GetSongBestFromData((DanConditionType)border.OdaiType, danBestData, songNumber);
|
||||
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)
|
||||
@ -389,14 +389,15 @@ else
|
||||
if (bestData <= redRequirement)
|
||||
{
|
||||
barClass = "bar-pass-red";
|
||||
resultText = @Localizer["Pass"];
|
||||
resultText = Localizer["Pass"];
|
||||
}
|
||||
|
||||
if (bestData <= goldRequirement)
|
||||
{
|
||||
barClass = "bar-pass-gold";
|
||||
resultText = @Localizer["Gold"];
|
||||
resultText = Localizer["Gold"];
|
||||
}
|
||||
|
||||
var resultValue = redRequirement - bestData;
|
||||
if (bestData >= redRequirement) resultValue = 0;
|
||||
|
||||
@ -410,13 +411,13 @@ else
|
||||
if (bestData >= redRequirement)
|
||||
{
|
||||
barClass = "bar-pass-red";
|
||||
resultText = @Localizer["Pass"];
|
||||
resultText = Localizer["Pass"];
|
||||
}
|
||||
|
||||
if (bestData >= goldRequirement)
|
||||
{
|
||||
barClass = "bar-pass-gold";
|
||||
resultText = @Localizer["Gold"];
|
||||
resultText = Localizer["Gold"];
|
||||
}
|
||||
|
||||
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@(goldRequirement > 0 ? goldRequirement : 1)" Value="@(goldRequirement > 0 ? bestData : 1)">
|
||||
@ -426,7 +427,6 @@ else
|
||||
<MudText Typo="Typo.caption">@resultText</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -25,54 +25,54 @@ public partial class DaniDojo
|
||||
.Sort((stageData, otherStageData) => stageData.SongNumber.CompareTo(otherStageData.SongNumber)));
|
||||
bestDataMap = response.DanBestDataList.ToDictionary(data => data.DanId);
|
||||
|
||||
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Dashboard", href: "/"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Users", href: "/Users"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
|
||||
};
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem("Dani Dojo", href: $"/Users/{Baid}/DaniDojo", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dani Dojo"], href: $"/Users/{Baid}/DaniDojo", disabled: false));
|
||||
}
|
||||
|
||||
private static string GetDanClearStateString(DanClearState danClearState)
|
||||
private string GetDanClearStateString(DanClearState danClearState)
|
||||
{
|
||||
return danClearState switch
|
||||
{
|
||||
DanClearState.NotClear => "Not Cleared",
|
||||
DanClearState.RedNormalClear => "Red Clear",
|
||||
DanClearState.RedFullComboClear => "Red Full Combo",
|
||||
DanClearState.RedPerfectClear => "Red Donderful Combo",
|
||||
DanClearState.GoldNormalClear => "Gold Clear",
|
||||
DanClearState.GoldFullComboClear => "Gold Full Combo",
|
||||
DanClearState.GoldPerfectClear => "Gold Donderful Combo",
|
||||
DanClearState.NotClear => Localizer["Not Cleared"],
|
||||
DanClearState.RedNormalClear => Localizer["Red"],
|
||||
DanClearState.RedFullComboClear => Localizer["Red Full Combo"],
|
||||
DanClearState.RedPerfectClear => Localizer["Red Donderful Combo"],
|
||||
DanClearState.GoldNormalClear => Localizer["Gold"],
|
||||
DanClearState.GoldFullComboClear => Localizer["Gold Full Combo"],
|
||||
DanClearState.GoldPerfectClear => Localizer["Gold Donderful Combo"],
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDanRequirementString(DanConditionType danConditionType)
|
||||
private string GetDanRequirementString(DanConditionType danConditionType)
|
||||
{
|
||||
return danConditionType switch
|
||||
{
|
||||
DanConditionType.TotalHitCount => "Total Hits",
|
||||
DanConditionType.GoodCount => "Good Hits",
|
||||
DanConditionType.OkCount => "OK Hits",
|
||||
DanConditionType.BadCount => "Bad Hits",
|
||||
DanConditionType.SoulGauge => "Soul Gauge",
|
||||
DanConditionType.DrumrollCount => "Drumroll Hits",
|
||||
DanConditionType.Score => "Score",
|
||||
DanConditionType.ComboCount => "MAX Combo",
|
||||
DanConditionType.TotalHitCount => Localizer["Total Hits"],
|
||||
DanConditionType.GoodCount => Localizer["Good"],
|
||||
DanConditionType.OkCount => Localizer["OK"],
|
||||
DanConditionType.BadCount => Localizer["Bad"],
|
||||
DanConditionType.SoulGauge => Localizer["Soul Gauge"],
|
||||
DanConditionType.DrumrollCount => Localizer["Drumroll"],
|
||||
DanConditionType.Score => Localizer["Score"],
|
||||
DanConditionType.ComboCount => Localizer["MAX Combo"],
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDanRequirementTitle(DanData.OdaiBorder data)
|
||||
private string GetDanRequirementTitle(DanData.OdaiBorder data)
|
||||
{
|
||||
var danConditionType = (DanConditionType)data.OdaiType;
|
||||
|
||||
@ -98,6 +98,7 @@ public partial class DaniDojo
|
||||
private static uint GetSongBestFromData(DanConditionType type, DanBestData data, int songNumber)
|
||||
{
|
||||
songNumber.Throw().IfOutOfRange(0, 2);
|
||||
|
||||
return type switch
|
||||
{
|
||||
DanConditionType.SoulGauge => throw new ArgumentException("Soul gauge should not be here"),
|
||||
@ -134,30 +135,30 @@ public partial class DaniDojo
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDanTitle(string title)
|
||||
private string GetDanTitle(string title)
|
||||
{
|
||||
return title switch
|
||||
{
|
||||
"5kyuu" => "Fifth Kyuu",
|
||||
"4kyuu" => "Fourth Kyuu",
|
||||
"3kyuu" => "Third Kyuu",
|
||||
"2kyuu" => "Second Kyuu",
|
||||
"1kyuu" => "First Kyuu",
|
||||
"1dan" => "First Dan",
|
||||
"2dan" => "Second Dan",
|
||||
"3dan" => "Third Dan",
|
||||
"4dan" => "Fourth Dan",
|
||||
"5dan" => "Fifth Dan",
|
||||
"6dan" => "Sixth Dan",
|
||||
"7dan" => "Seventh Dan",
|
||||
"8dan" => "Eighth Dan",
|
||||
"9dan" => "Ninth Dan",
|
||||
"10dan" => "Tenth Dan",
|
||||
"11dan" => "Kuroto",
|
||||
"12dan" => "Meijin",
|
||||
"13dan" => "Chojin",
|
||||
"14dan" => "Tatsujin",
|
||||
"15dan" => "Gaiden",
|
||||
"5kyuu" => Localizer["Fifth Kyuu"],
|
||||
"4kyuu" => Localizer["Fourth Kyuu"],
|
||||
"3kyuu" => Localizer["Third Kyuu"],
|
||||
"2kyuu" => Localizer["Second Kyuu"],
|
||||
"1kyuu" => Localizer["First Kyuu"],
|
||||
"1dan" => Localizer["First Dan"],
|
||||
"2dan" => Localizer["Second Dan"],
|
||||
"3dan" => Localizer["Third Dan"],
|
||||
"4dan" => Localizer["Fourth Dan"],
|
||||
"5dan" => Localizer["Fifth Dan"],
|
||||
"6dan" => Localizer["Sixth Dan"],
|
||||
"7dan" => Localizer["Seventh Dan"],
|
||||
"8dan" => Localizer["Eighth Dan"],
|
||||
"9dan" => Localizer["Ninth Dan"],
|
||||
"10dan" => Localizer["Tenth Dan"],
|
||||
"11dan" => Localizer["Kuroto"],
|
||||
"12dan" => Localizer["Meijin"],
|
||||
"13dan" => Localizer["Chojin"],
|
||||
"14dan" => Localizer["Tatsujin"],
|
||||
"15dan" => Localizer["Gaiden"],
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
@page "/"
|
||||
|
||||
<MudText Typo="Typo.h4">@Localizer["dashboard"]</MudText>
|
||||
<MudText Typo="Typo.h4">@Localizer["Dashboard"]</MudText>
|
||||
|
||||
<MudText Class="mt-8">
|
||||
@Localizer["Welcome to TaikoWebUI!"]
|
||||
|
@ -16,7 +16,7 @@
|
||||
<MudText>
|
||||
<code>
|
||||
<pre>
|
||||
@String.Format("{0:0000 0000 0000 0000 0000}", (Int64.Parse(AccessCode)))
|
||||
@AccessCode
|
||||
</pre>
|
||||
</code>
|
||||
</MudText>
|
||||
|
@ -1,10 +1,12 @@
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
using System.Net.Http.Headers;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
public partial class AccessCodeDeleteConfirmDialog
|
||||
{
|
||||
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public User User { get; set; } = new();
|
||||
@ -12,6 +14,13 @@ public partial class AccessCodeDeleteConfirmDialog
|
||||
[Parameter]
|
||||
public string AccessCode { get; set; } = "";
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public AuthService AuthService { get; set; } = null!;
|
||||
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task DeleteAccessCode()
|
||||
|
@ -1,6 +1,7 @@
|
||||
@using TaikoWebUI.Shared.Models
|
||||
@using System.Collections.Immutable
|
||||
@inject IGameDataService GameDataService
|
||||
@inject IJSRuntime Js
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@ -38,7 +39,7 @@
|
||||
}
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page"] />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
<MudText Class="mt-4 d-block" Typo="Typo.caption"> <b>@Localizer["Selected Title:"]</b> @selectedTitle?.TitleName</MudText>
|
||||
@ -64,6 +65,9 @@
|
||||
[Parameter]
|
||||
public bool AllowFreeProfileEditing { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<uint> TitleUniqueIdList { get; set; } = new();
|
||||
|
||||
private IEnumerable<Title> titles = new List<Title>();
|
||||
|
||||
private Title? selectedTitle;
|
||||
@ -77,7 +81,12 @@
|
||||
if (!AllowFreeProfileEditing)
|
||||
{
|
||||
var unlockedTitle = UserSetting.UnlockedTitle;
|
||||
titleSet = titleSet.Where(title => unlockedTitle.Contains((uint)title.TitleId)).ToImmutableHashSet();
|
||||
titleSet = titleSet.Where(title => unlockedTitle.Contains(title.TitleId)).ToImmutableHashSet();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only allow titles in titleUniqueIdList
|
||||
titleSet = titleSet.Where(title => TitleUniqueIdList.Contains(title.TitleId)).ToImmutableHashSet();
|
||||
}
|
||||
titles = titleSet.ToImmutableList().Sort((title, title1) => title.TitleId.CompareTo(title1.TitleId));
|
||||
var currentTitle = new Title
|
||||
@ -103,13 +112,15 @@
|
||||
title.TitleName.Contains(searchString, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private void Submit()
|
||||
private async Task Submit()
|
||||
{
|
||||
if (selectedTitle is not null)
|
||||
{
|
||||
UserSetting.Title = selectedTitle.TitleName;
|
||||
UserSetting.TitlePlateId = selectedTitle.TitleRarity;
|
||||
}
|
||||
|
||||
await Js.InvokeVoidAsync("updateTitleText", UserSetting.Title);
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
|
||||
|
36
TaikoWebUI/Pages/Dialogs/OTPDialog.razor
Normal file
36
TaikoWebUI/Pages/Dialogs/OTPDialog.razor
Normal file
@ -0,0 +1,36 @@
|
||||
@inject HttpClient Client
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IJSRuntime Js
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudText Typo="Typo.h6">@Otp</MudText>
|
||||
<div style="height: 1rem"></div>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
OnClick="@(_ => CopyToClipboard(Otp))">
|
||||
@Localizer["Copy to Clipboard"]
|
||||
</MudButton>
|
||||
<div style="height: 1rem"></div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Color="Color.Primary" OnClick="@Submit">@Localizer["Dialog OK"]</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public string Otp { get; set; } = "";
|
||||
|
||||
private async Task CopyToClipboard(string text)
|
||||
{
|
||||
await Js.InvokeVoidAsync("clipboardCopy.copyText", text);
|
||||
}
|
||||
|
||||
private void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
}
|
@ -1,26 +1,31 @@
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
using System.Net.Http.Headers;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
public partial class ResetPasswordConfirmDialog
|
||||
{
|
||||
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public User User { get; set; } = new();
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public AuthService AuthService { get; set; } = null!;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task ResetPassword()
|
||||
{
|
||||
var request = new SetPasswordRequest
|
||||
var request = new ResetPasswordRequest
|
||||
{
|
||||
Baid = User.Baid,
|
||||
Password = "",
|
||||
Salt = ""
|
||||
Baid = User.Baid
|
||||
};
|
||||
var responseMessage = await Client.PostAsJsonAsync("api/Credentials", request);
|
||||
|
||||
var responseMessage = await Client.PostAsJsonAsync("api/Auth/ResetPassword", request);
|
||||
if (!responseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
Snackbar.Add("Something went wrong when resetting password!", Severity.Error);
|
||||
|
@ -1,14 +1,21 @@
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
using System.Net.Http.Headers;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
public partial class UserDeleteConfirmDialog
|
||||
{
|
||||
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public User User { get; set; } = new();
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public AuthService AuthService { get; set; } = null!;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task DeleteUser()
|
||||
|
@ -1,11 +1,9 @@
|
||||
@inject IGameDataService GameDataService
|
||||
|
||||
<MudDialog Class="dialog-user-qr-code">
|
||||
<MudDialog Class="dialog-user-qr-code">
|
||||
<DialogContent>
|
||||
<MudExtensions.MudBarcode Value="@qrCode" BarcodeFormat="ZXing.BarcodeFormat.QR_CODE" Height="300" Width="300" />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Ok</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">@Localizer["Dialog OK"]</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject Blazored.LocalStorage.ILocalStorageService localStorage
|
||||
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
|
||||
@using TaikoWebUI.Utilities;
|
||||
|
||||
@page "/Users/{baid:int}/HighScores"
|
||||
@ -20,9 +20,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
@ -39,25 +39,104 @@
|
||||
{
|
||||
@if (difficulty is not Difficulty.None)
|
||||
{
|
||||
<MudTabPanel Text="@ScoreUtils.GetDifficultyTitle(difficulty)"
|
||||
<MudTabPanel Text="@Localizer[ScoreUtils.GetDifficultyTitle(difficulty)]"
|
||||
Icon="@ScoreUtils.GetDifficultyIconSvg(difficulty)">
|
||||
@if (songBestDataMap.TryGetValue(difficulty, out var value))
|
||||
{
|
||||
<MudDataGrid Items="@value"
|
||||
ColumnResizeMode="ResizeMode.None" RowsPerPage="25" Elevation="0">
|
||||
<Columns>
|
||||
<TemplateColumn T="SongBestData" Title=@Localizer["Song"] StickyLeft="true">
|
||||
<CellTemplate>
|
||||
// Rows per page 25
|
||||
<MudTable Items="@value" Elevation="0" Striped="true" RowsPerPage="25">
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudText Typo="Typo.body2" Style="font-weight:bold">@Localizer["Song Name"]</MudText>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => GameDataService.GetMusicStarLevel(x.SongId, difficulty)">
|
||||
<MudText>@Localizer["Level"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.Genre">
|
||||
<MudText>@Localizer["Genre"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.BestScore">
|
||||
<MudText>@Localizer["Best Score"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.BestCrown">
|
||||
<MudText>@Localizer["Best Crown"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.BestScoreRank">
|
||||
<MudText>@Localizer["Best Rank"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.GoodCount">
|
||||
<MudText>@Localizer["Good"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.OkCount">
|
||||
<MudText>@Localizer["OK"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.MissCount">
|
||||
<MudText>@Localizer["Bad"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.DrumrollCount">
|
||||
<MudText>@Localizer["Drumroll"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.ComboCount">
|
||||
<MudText>@Localizer["MAX Combo"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.LastPlayTime">
|
||||
<MudText>@Localizer["Last Played"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.PlayCount">
|
||||
<MudText>@Localizer["Total Plays"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.ClearCount">
|
||||
<MudText>@Localizer["Total Clears"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.FullComboCount">
|
||||
<MudText>@Localizer["Total Full Combos"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongBestData" SortBy="x => x.PerfectCount">
|
||||
<MudText>@Localizer["Total Donderful Combos"]</MudText>
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<div style="width:300px">
|
||||
<a href="@($"/Users/{Baid}/Songs/{context.Item.SongId}")">
|
||||
<MudText Typo="Typo.body2" Style="font-weight:bold">@context.Item.MusicName</MudText>
|
||||
<MudText Typo="Typo.caption">@context.Item.MusicArtist</MudText>
|
||||
<a href="@($"/Users/{Baid}/Songs/{context.SongId}")">
|
||||
<MudText Typo="Typo.body2" Style="font-weight:bold">@context.MusicName</MudText>
|
||||
<MudText Typo="Typo.caption">@context.MusicArtist</MudText>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<MudToggleIconButton Toggled="@context.Item.IsFavorite"
|
||||
ToggledChanged="@(async () => await OnFavoriteToggled(context.Item))"
|
||||
<MudToggleIconButton Toggled="@context.IsFavorite"
|
||||
ToggledChanged="@(async () => await OnFavoriteToggled(context))"
|
||||
Icon="@Icons.Material.Filled.FavoriteBorder" Color="@Color.Secondary"
|
||||
ToggledIcon="@Icons.Material.Filled.Favorite" ToggledColor="@Color.Secondary"
|
||||
Size="Size.Small"
|
||||
@ -65,54 +144,66 @@
|
||||
Title="Add to favorites" ToggledTitle="Remove from favorites" />
|
||||
</div>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn T="SongBestData" Title=@Localizer["Level"] Sortable="false">
|
||||
<CellTemplate>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" />
|
||||
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(@context.Item.SongId, difficulty)</MudText>
|
||||
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(context.SongId, difficulty)</MudText>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn T="SongBestData" Title=@Localizer["Genre"]
|
||||
Sortable="false" Filterable="true">
|
||||
<CellTemplate>
|
||||
<MudChip Style="@ScoreUtils.GetGenreStyle(context.Item.Genre)"
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudChip Style="@ScoreUtils.GetGenreStyle(context.Genre)"
|
||||
Size="Size.Small">
|
||||
@ScoreUtils.GetGenreTitle(context.Item.Genre)
|
||||
@ScoreUtils.GetGenreTitle(context.Genre)
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="data => data.BestScore" Title=@Localizer["Best Score"] />
|
||||
<TemplateColumn T="SongBestData" Title=@Localizer["Best Crown"]>
|
||||
<CellTemplate>
|
||||
<img src="@($"/images/crown_{context.Item.BestCrown}.png")" alt="@(ScoreUtils.GetCrownText(context.Item.BestCrown))" title="@(ScoreUtils.GetCrownText(context.Item.BestCrown))" style="@Constants.ICON_STYLE" />
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn T="SongBestData" Title=@Localizer["Best Rank"] Sortable="false">
|
||||
<CellTemplate>
|
||||
@if (context.Item.BestScoreRank is not ScoreRank.None)
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.BestScore</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<img src="@($"/images/crown_{context.BestCrown}.png")" alt="@(ScoreUtils.GetCrownText(context.BestCrown))" title="@(ScoreUtils.GetCrownText(context.BestCrown))" style="@Constants.ICON_STYLE" />
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
@if (context.BestScoreRank is not ScoreRank.None)
|
||||
{
|
||||
<img src="@($"/images/rank_{context.Item.BestScoreRank}.png")" alt="@(ScoreUtils.GetRankText(context.Item.BestScoreRank))" title="@(ScoreUtils.GetRankText(context.Item.BestScoreRank))" style="@Constants.ICON_STYLE" />
|
||||
<img src="@($"/images/rank_{context.BestScoreRank}.png")" alt="@(ScoreUtils.GetRankText(context.BestScoreRank))" title="@(ScoreUtils.GetRankText(context.BestScoreRank))" style="@Constants.ICON_STYLE" />
|
||||
}
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="data => data.GoodCount" Title=@Localizer["Good"] Sortable="false" />
|
||||
<PropertyColumn Property="data => data.OkCount" Title=@Localizer["OK"] Sortable="false" />
|
||||
<PropertyColumn Property="data => data.MissCount" Title=@Localizer["Bad"] Sortable="false" />
|
||||
<PropertyColumn Property="data => data.DrumrollCount" Title=@Localizer["Drumroll"] Sortable="false" />
|
||||
<PropertyColumn Property="data => data.ComboCount" Title=@Localizer["MAX Combo"] Sortable="false" />
|
||||
<PropertyColumn Property="data => data.LastPlayTime" Title=@Localizer["Last Played"] Hideable="true" />
|
||||
<PropertyColumn Property="data => data.PlayCount" Title=@Localizer["Total Plays"] Hideable="true" />
|
||||
<PropertyColumn Property="data => data.ClearCount" Title=@Localizer["Total Clears"] Hideable="true" />
|
||||
<PropertyColumn Property="data => data.FullComboCount" Title=@Localizer["Total Full Combos"] Hideable="true" />
|
||||
<PropertyColumn Property="data => data.PerfectCount" Title=@Localizer["Total Donderful Combos"] Hideable="true" />
|
||||
</Columns>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.GoodCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.OkCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.MissCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.DrumrollCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.ComboCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.LastPlayTime</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.PlayCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.ClearCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.FullComboCount</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudText>@context.PerfectCount</MudText>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="SongBestData" />
|
||||
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page:"] />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
</MudTable>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
|
||||
namespace TaikoWebUI.Pages;
|
||||
|
||||
public partial class HighScores
|
||||
@ -24,7 +25,7 @@ public partial class HighScores
|
||||
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
var language = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
response.SongBestData.ForEach(data =>
|
||||
{
|
||||
@ -43,21 +44,20 @@ public partial class HighScores
|
||||
.CompareTo(GameDataService.GetMusicIndexBySongId(data2.SongId)));
|
||||
}
|
||||
|
||||
|
||||
// Set last selected tab from local storage
|
||||
selectedDifficultyTab = await localStorage.GetItemAsync<int>($"highScoresTab");
|
||||
selectedDifficultyTab = await LocalStorage.GetItemAsync<int>($"highScoresTab");
|
||||
|
||||
// Breadcrumbs
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Dashboard", href: "/"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Users", href: "/Users"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
|
||||
};
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem("High Scores", href: $"/Users/{Baid}/HighScores", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["High Scores"], href: $"/Users/{Baid}/HighScores", disabled: false));
|
||||
}
|
||||
|
||||
private async Task OnFavoriteToggled(SongBestData data)
|
||||
@ -78,6 +78,6 @@ public partial class HighScores
|
||||
private async Task OnTabChanged(int index)
|
||||
{
|
||||
selectedDifficultyTab = index;
|
||||
await localStorage.SetItemAsync($"highScoresTab", selectedDifficultyTab);
|
||||
await LocalStorage.SetItemAsync($"highScoresTab", selectedDifficultyTab);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/Login"
|
||||
|
||||
|
||||
@if (!LoginService.IsLoggedIn)
|
||||
@if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
// Not logged in, show login form
|
||||
<MudContainer>
|
||||
@ -21,11 +21,11 @@
|
||||
<div style="display:flex;flex-direction:column;gap:15px;">
|
||||
<MudTextField @bind-value="inputAccessCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Required="@true" RequiredError="Access code is required"
|
||||
Label="Access Code" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
Label=@Localizer["Access Code"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudTextField @bind-Value="inputPassword" InputType="InputType.Password"
|
||||
T="string" FullWidth="true" Required="@true"
|
||||
RequiredError="Password is required"
|
||||
Label="Password" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
Label=@Localizer["Password"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudButton OnClick="OnLogin" FullWidth="true" StartIcon="@Icons.Material.Filled.Login" Color="Color.Primary" Variant="Variant.Filled">@Localizer["Log In"]</MudButton>
|
||||
</div>
|
||||
</MudForm>
|
||||
|
@ -5,27 +5,23 @@ public partial class Login
|
||||
private string inputAccessCode = "";
|
||||
private MudForm loginForm = default!;
|
||||
private string inputPassword = "";
|
||||
private DashboardResponse? response;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
}
|
||||
|
||||
private async Task OnLogin()
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
var result = LoginService.Login(inputAccessCode, inputPassword, response);
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
var result = await AuthService.Login(inputAccessCode, inputPassword);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
"Only admin can log in.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
await loginForm.ResetAsync();
|
||||
break;
|
||||
case 1:
|
||||
@ -33,25 +29,30 @@ public partial class Login
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
"Wrong password!",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not found.<br />Please play one game with this access code to register it.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not registered.<br />Please use register button to create a password first.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
148
TaikoWebUI/Pages/PlayHistory.razor
Normal file
148
TaikoWebUI/Pages/PlayHistory.razor
Normal file
@ -0,0 +1,148 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
@using TaikoWebUI.Utilities;
|
||||
@using TaikoWebUI.Shared.Models;
|
||||
@using SharedProject.Enums;
|
||||
|
||||
@page "/Users/{baid:int}/PlayHistory"
|
||||
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h4">@Localizer["Play History"]</MudText>
|
||||
|
||||
<MudGrid Class="my-8">
|
||||
@if (response is null)
|
||||
{
|
||||
<MudContainer Style="display:flex;margin:50px 0;align-items:center;justify-content:center;">
|
||||
<MudProgressCircular Indeterminate="true" Size="Size.Large" Color="Color.Primary" />
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudTable Items="@songHistoryDataMap.Values.ToList()" Elevation="0" Filter=@FilterSongs Virtualize="true" RowsPerPage="25" Bordered="false" Dense="true">
|
||||
<ToolBarContent>
|
||||
<MudGrid Spacing="2" Justify="Justify.SpaceBetween">
|
||||
<MudItem xs="12" md="4">
|
||||
<MudText Typo="Typo.caption">@Localizer["Total Credits Played"]: @songHistoryDataMap.Values.Count</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="8">
|
||||
<MudTextField @bind-Value="Search"
|
||||
Placeholder=@Localizer["Search by Title, Artist or Date"]
|
||||
Variant="Variant.Outlined"
|
||||
Clearable="true"
|
||||
Immediate="true"
|
||||
Margin="Margin.Dense"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium"/>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" T="List<SongHistoryData>" SortBy="x => x[0].PlayTime">
|
||||
@Localizer["Play Time"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<div class="mt-4 mb-1">
|
||||
<MudText Typo="Typo.subtitle2">
|
||||
@CultureInfo.CurrentCulture.TextInfo.ToTitleCase(context[0].PlayTime.ToString(Localizer["DateFormat"]))
|
||||
</MudText>
|
||||
</div>
|
||||
</RowTemplate>
|
||||
<ChildRowContent>
|
||||
<MudTable Items="@context" T="SongHistoryData" Context="songHistoryData" Elevation="0" Striped="false" Hover="false" ReadOnly="true" Outlined="true" Class="mb-8">
|
||||
<HeaderContent>
|
||||
<MudTh>@Localizer["Difficulty"]</MudTh>
|
||||
<MudTh>@Localizer["Level"]</MudTh>
|
||||
<MudTh Style="width:500px">@Localizer["Song Name"]</MudTh>
|
||||
<MudTh>@Localizer["Genre"]</MudTh>
|
||||
<MudTh>@Localizer["Score"]</MudTh>
|
||||
<MudTh>@Localizer["Crown"]</MudTh>
|
||||
<MudTh>@Localizer["Rank"]</MudTh>
|
||||
<MudTh>@Localizer["Good"]</MudTh>
|
||||
<MudTh>@Localizer["OK"]</MudTh>
|
||||
<MudTh>@Localizer["Bad"]</MudTh>
|
||||
<MudTh>@Localizer["Drumroll"]</MudTh>
|
||||
<MudTh>@Localizer["MAX Combo"]</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@* Difficulty rating *@
|
||||
<MudTd DataLabel="Difficulty" Style="text-align:center"><MudIcon Style=@IconStyle Icon="@GetDifficultyIcon(songHistoryData.Difficulty)" /></MudTd>
|
||||
@* Star rating *@
|
||||
<MudTd DataLabel="Stars" Style="text-align:center">
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" />
|
||||
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@songHistoryData.Stars</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
|
||||
@* Song title *@
|
||||
<MudTd DataLabel="Song">
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||||
<div>
|
||||
<a href="@($"/Users/{Baid}/Songs/{songHistoryData.SongId}")">
|
||||
<MudText Typo="Typo.body2" Style="font-weight:bold">@songHistoryData.MusicName</MudText>
|
||||
<MudText Typo="Typo.caption">@songHistoryData.MusicArtist</MudText>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<MudToggleIconButton Toggled="@songHistoryData.IsFavorite"
|
||||
ToggledChanged="@(async () => await OnFavoriteToggled(songHistoryData))"
|
||||
Icon="@Icons.Material.Filled.FavoriteBorder" Color="@Color.Secondary"
|
||||
ToggledIcon="@Icons.Material.Filled.Favorite" ToggledColor="@Color.Secondary"
|
||||
Size="Size.Small"
|
||||
ToggledSize="Size.Small"
|
||||
Title="Add to favorites" ToggledTitle="Remove from favorites" />
|
||||
</div>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
|
||||
@* Genre display *@
|
||||
<MudTd DataLabel="Genre" Style="text-align:left">
|
||||
<MudChip Style=@GetGenreStyle(songHistoryData.Genre) Size="Size.Small">@GetGenreTitle(songHistoryData.Genre)</MudChip>
|
||||
</MudTd>
|
||||
|
||||
<MudTd>@(songHistoryData.Score)</MudTd>
|
||||
<MudTd Style="text-align:center"><img src="@($"/images/crown_{songHistoryData.Crown}.png")" alt="@(GetCrownText(songHistoryData.Crown))" title="@(GetCrownText(songHistoryData.Crown))" style=@IconStyle /></MudTd>
|
||||
<MudTd Style="text-align:center">
|
||||
@if (songHistoryData.ScoreRank is not ScoreRank.None)
|
||||
{
|
||||
<img src="@($"/images/rank_{songHistoryData.ScoreRank}.png")" alt="@(GetRankText(songHistoryData.ScoreRank))" title="@(GetRankText(songHistoryData.ScoreRank))" style=@IconStyle />
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@(songHistoryData.GoodCount)</MudTd>
|
||||
<MudTd>@(songHistoryData.OkCount)</MudTd>
|
||||
<MudTd>@(songHistoryData.MissCount)</MudTd>
|
||||
<MudTd>@(songHistoryData.DrumrollCount)</MudTd>
|
||||
<MudTd>@(songHistoryData.ComboCount)</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</ChildRowContent>
|
||||
<PagerContent>
|
||||
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page:"] />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
}
|
||||
}
|
||||
</MudGrid>
|
||||
|
177
TaikoWebUI/Pages/PlayHistory.razor.cs
Normal file
177
TaikoWebUI/Pages/PlayHistory.razor.cs
Normal file
@ -0,0 +1,177 @@
|
||||
using static MudBlazor.Colors;
|
||||
using System;
|
||||
using static MudBlazor.CategoryTypes;
|
||||
using System.Globalization;
|
||||
using Microsoft.JSInterop;
|
||||
using TaikoWebUI.Shared.Models;
|
||||
|
||||
namespace TaikoWebUI.Pages;
|
||||
|
||||
public partial class PlayHistory
|
||||
{
|
||||
[Parameter]
|
||||
public int Baid { get; set; }
|
||||
|
||||
private const string IconStyle = "width:25px; height:25px;";
|
||||
|
||||
private string Search { get; set; } = string.Empty;
|
||||
|
||||
private string? currentLanguage;
|
||||
|
||||
private SongHistoryResponse? response;
|
||||
|
||||
private Dictionary<DateTime, List<SongHistoryData>> songHistoryDataMap = new();
|
||||
|
||||
private readonly List<BreadcrumbItem> breadcrumbs = new();
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
|
||||
response.ThrowIfNull();
|
||||
|
||||
currentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
response.SongHistoryData.ForEach(data =>
|
||||
{
|
||||
var songId = data.SongId;
|
||||
data.Genre = GameDataService.GetMusicGenreBySongId(songId);
|
||||
data.MusicName = GameDataService.GetMusicNameBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
|
||||
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
|
||||
data.Stars = GameDataService.GetMusicStarLevel(songId, data.Difficulty);
|
||||
data.ShowDetails = false;
|
||||
});
|
||||
|
||||
songHistoryDataMap = response.SongHistoryData.GroupBy(data => data.PlayTime).ToDictionary(data => data.Key,data => data.ToList());
|
||||
foreach (var songHistoryDataList in songHistoryDataMap.Values)
|
||||
{
|
||||
songHistoryDataList.Sort((data1, data2) => data1.SongNumber.CompareTo(data2.SongNumber));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCrownText(CrownType crown)
|
||||
{
|
||||
return crown switch
|
||||
{
|
||||
CrownType.None => "Fail",
|
||||
CrownType.Clear => "Clear",
|
||||
CrownType.Gold => "Full Combo",
|
||||
CrownType.Dondaful => "Donderful Combo",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetRankText(ScoreRank rank)
|
||||
{
|
||||
return rank switch
|
||||
{
|
||||
ScoreRank.White => "Stylish",
|
||||
ScoreRank.Bronze => "Stylish",
|
||||
ScoreRank.Silver => "Stylish",
|
||||
ScoreRank.Gold => "Graceful",
|
||||
ScoreRank.Sakura => "Graceful",
|
||||
ScoreRank.Purple => "Graceful",
|
||||
ScoreRank.Dondaful => "Top Class",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDifficultyTitle(Difficulty difficulty)
|
||||
{
|
||||
return difficulty switch
|
||||
{
|
||||
Difficulty.Easy => "Easy",
|
||||
Difficulty.Normal => "Normal",
|
||||
Difficulty.Hard => "Hard",
|
||||
Difficulty.Oni => "Oni",
|
||||
Difficulty.UraOni => "Ura Oni",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetDifficultyIcon(Difficulty difficulty)
|
||||
{
|
||||
return $"<image href='/images/difficulty_{difficulty}.png' alt='{difficulty}' width='24' height='24'/>";
|
||||
}
|
||||
|
||||
private static string GetGenreTitle(SongGenre genre)
|
||||
{
|
||||
return genre switch
|
||||
{
|
||||
SongGenre.Pop => "Pop",
|
||||
SongGenre.Anime => "Anime",
|
||||
SongGenre.Kids => "Kids",
|
||||
SongGenre.Vocaloid => "Vocaloid",
|
||||
SongGenre.GameMusic => "Game Music",
|
||||
SongGenre.NamcoOriginal => "NAMCO Original",
|
||||
SongGenre.Variety => "Variety",
|
||||
SongGenre.Classical => "Classical",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetGenreStyle(SongGenre genre)
|
||||
{
|
||||
return genre switch
|
||||
{
|
||||
SongGenre.Pop => "background: #42c0d2; color: #fff",
|
||||
SongGenre.Anime => "background: #ff90d3; color: #fff",
|
||||
SongGenre.Kids => "background: #fec000; color: #fff",
|
||||
SongGenre.Vocaloid => "background: #ddd; color: #000",
|
||||
SongGenre.GameMusic => "background: #cc8aea; color: #fff",
|
||||
SongGenre.NamcoOriginal => "background: #ff7027; color: #fff",
|
||||
SongGenre.Variety => "background: #1dc83b; color: #fff",
|
||||
SongGenre.Classical => "background: #bfa356; color: #000",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private bool FilterSongs(List<SongHistoryData> songHistoryDataList)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Search))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var language = currentLanguage ?? "ja";
|
||||
|
||||
if (songHistoryDataList[0].PlayTime
|
||||
.ToString("dddd d MMMM yyyy - HH:mm", CultureInfo.CreateSpecificCulture(language))
|
||||
.Contains(Search, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var songHistoryData in songHistoryDataList)
|
||||
{
|
||||
if (songHistoryData.MusicName.Contains(Search, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (songHistoryData.MusicArtist.Contains(Search, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task OnFavoriteToggled(SongHistoryData data)
|
||||
{
|
||||
var request = new SetFavoriteRequest
|
||||
{
|
||||
Baid = (uint)Baid,
|
||||
IsFavorite = !data.IsFavorite,
|
||||
SongId = data.SongId
|
||||
};
|
||||
var result = await Client.PostAsJsonAsync("api/FavoriteSongs", request);
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
data.IsFavorite = !data.IsFavorite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,15 @@
|
||||
@inject HttpClient Client
|
||||
@inject IGameDataService GameDataService
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime Js
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (response is not null)
|
||||
{
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
@ -47,7 +47,7 @@
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="8">
|
||||
@if (LoginService.AllowFreeProfileEditing)
|
||||
@if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
<MudTextField TextChanged="UpdateTitle" @bind-Value="@response.Title" Label=@Localizer["Title"]/>
|
||||
}
|
||||
@ -55,18 +55,17 @@
|
||||
{
|
||||
<MudTextField ReadOnly="true" @bind-Value="@response.Title" Label=@Localizer["Title"]/>
|
||||
}
|
||||
<MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@((_) => OpenChooseTitleDialog())">
|
||||
<MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@(_ => OpenChooseTitleDialog())">
|
||||
@Localizer["Select a Title"]
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
@if (LoginService.AllowFreeProfileEditing)
|
||||
@if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
<MudItem xs="12" md="4">
|
||||
<MudSelect @bind-Value="@response.TitlePlateId" Label=@Localizer["Title Plate"]>
|
||||
@for (uint i = 0; i < TitlePlateStrings.Length; i++)
|
||||
@foreach (var index in titlePlateIdList)
|
||||
{
|
||||
var index = i;
|
||||
<MudSelectItem Value="@i">@TitlePlateStrings[index]</MudSelectItem>
|
||||
<MudSelectItem Value="@index">@TitlePlateStrings[index]</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@ -124,100 +123,41 @@
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudStack Spacing="4" Class="mb-8">
|
||||
@if (LoginService.AllowFreeProfileEditing)
|
||||
{
|
||||
<MudSelect @bind-Value="@response.Head" Label=@Localizer["Head"]>
|
||||
@for (var i = 0; i < costumeFlagArraySizes[1]; i++)
|
||||
@foreach (var index in headUniqueIdList)
|
||||
{
|
||||
var index = (uint)i;
|
||||
var costumeTitle = GameDataService.GetHeadTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Body" Label=@Localizer["Body"]>
|
||||
@for (var i = 0; i < costumeFlagArraySizes[2]; i++)
|
||||
@foreach (var index in bodyUniqueIdList)
|
||||
{
|
||||
var index = (uint)i;
|
||||
var costumeTitle = GameDataService.GetBodyTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Face" Label=@Localizer["Face"]>
|
||||
@for (var i = 0; i < costumeFlagArraySizes[3]; i++)
|
||||
@foreach (var index in faceUniqueIdList)
|
||||
{
|
||||
var index = (uint)i;
|
||||
var costumeTitle = GameDataService.GetFaceTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Kigurumi" Label=@Localizer["Kigurumi"]>
|
||||
@for (var i = 0; i < costumeFlagArraySizes[0]; i++)
|
||||
@foreach (var index in kigurumiUniqueIdList)
|
||||
{
|
||||
var index = (uint)i;
|
||||
var costumeTitle = GameDataService.GetKigurumiTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Puchi" Label=@Localizer["Puchi"]>
|
||||
@for (var i = 0; i < costumeFlagArraySizes[4]; i++)
|
||||
@foreach (var index in puchiUniqueIdList)
|
||||
{
|
||||
var index = (uint)i;
|
||||
var costumeTitle = GameDataService.GetPuchiTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect @bind-Value="@response.Head" Label=@Localizer["Head"]>
|
||||
@foreach (var i in unlockedHeadCostumes)
|
||||
{
|
||||
var index = i;
|
||||
var costumeTitle = GameDataService.GetHeadTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Body" Label=@Localizer["Body"]>
|
||||
@foreach (var i in unlockedBodyCostumes)
|
||||
{
|
||||
var index = i;
|
||||
var costumeTitle = GameDataService.GetBodyTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Face" Label=@Localizer["Face"]>
|
||||
@foreach (var i in unlockedFaceCostumes)
|
||||
{
|
||||
var index = i;
|
||||
var costumeTitle = GameDataService.GetFaceTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Kigurumi" Label=@Localizer["Kigurumi"]>
|
||||
@foreach (var i in unlockedKigurumiCostumes)
|
||||
{
|
||||
var index = i;
|
||||
var costumeTitle = GameDataService.GetKigurumiTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="@response.Puchi" Label=@Localizer["Puchi"]>
|
||||
@foreach (var i in unlockedPuchiCostumes)
|
||||
{
|
||||
var index = i;
|
||||
var costumeTitle = GameDataService.GetPuchiTitle(index);
|
||||
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="true">
|
||||
@ -311,7 +251,7 @@
|
||||
<MudStack Spacing="4" Class="sticky" Style="top:100px">
|
||||
<MudPaper Elevation="0" Outlined="true">
|
||||
<MudTabs Rounded="true" Border="true" PanelClass="pa-8">
|
||||
<MudTabPanel Text="@Localizer["Player"]">
|
||||
<MudTabPanel Text=@Localizer["Player"]>
|
||||
<MudItem style="height: auto">
|
||||
@* Player Visualizer *@
|
||||
<MudItem style="text-align: center;">
|
||||
@ -349,12 +289,13 @@
|
||||
<MudItem Style="position:absolute; top: 47%; left:0; right:1.1%; height:41%; width:min(96%, 320px); margin: 0 auto; z-index:2">
|
||||
@* Name textoffset to the right for Dan Rank *@
|
||||
<MudItem Style=@($"position:absolute; height:100%; right: 10%; {(response.IsDisplayDanOnNamePlate ? "width:46%;" : "width:80%;")}")>
|
||||
<MudText Id="nameplate-name-outline" Style="position:absolute; height:100%; top: 0; left: 0; right: 0; margin: auto auto; font-family: 'Nijiiro', sans-serif; -webkit-text-stroke: 5px; -webkit-text-stroke-color: black">@response.MyDonName</MudText>
|
||||
<MudText Id="nameplate-name-outline" Style="position:absolute; height:100%; top: 0; left: 0; right: 0; margin: auto auto; font-family: 'Nijiiro', sans-serif; -webkit-text-stroke-width: 5px; -webkit-text-stroke-color: black">@response.MyDonName</MudText>
|
||||
<MudText Id="nameplate-name" Style="position:absolute; height:100%; top: 0; left: 0; right: 0; margin: auto auto; font-family: 'Nijiiro', sans-serif; color:white">@response.MyDonName</MudText>
|
||||
</MudItem>
|
||||
</MudItem>
|
||||
<MudImage onload="nameplateLoaded()" Id="nameplate" Fluid="true" Style="position: relative; top: 0; left: 0;" Src="images/Nameplates/nameplate.png" />
|
||||
<MudImage Fluid="true" Style="position:absolute; top: 0; left: 0; right: 0; margin: 0 auto;" Src=@($"images/Nameplates/nameplate_{TitlePlateStrings[response.TitlePlateId].Replace(' ', '_')}.png") />
|
||||
<MudImage onload="nameplateLoaded()" Id="nameplate" Fluid="true" Style="position: relative; bottom: 0; left: 0;" Src="images/Nameplates/nameplate.png" />
|
||||
@* Check if image does not exist, use nameplate_Wood.png *@
|
||||
<MudImage Fluid="true" Style="position:absolute; bottom: 0%; left: 0; right: 0; margin: 0 auto;" onerror="this.src='images/Nameplates/nameplate_Wood.png'" Src=@($"images/Nameplates/nameplate_{TitlePlateStrings[response.TitlePlateId].Replace(' ', '_')}.png") />
|
||||
@if (response.IsDisplayDanOnNamePlate)
|
||||
{
|
||||
<MudImage Fluid="true" Style="position:absolute; top: 0; left: 0; right: 0; margin: 0 auto;" Src="images/Nameplates/nameplate_dan.png" />
|
||||
@ -460,17 +401,17 @@ else
|
||||
@code {
|
||||
private async Task UpdateMyDonName()
|
||||
{
|
||||
@if (response is not null) await Js.InvokeVoidAsync("updateMyDonNameText", response.MyDonName);
|
||||
@if (response is not null) await JsRuntime.InvokeVoidAsync("updateMyDonNameText", response.MyDonName);
|
||||
}
|
||||
|
||||
private async Task UpdateTitle()
|
||||
{
|
||||
@if (response is not null) await Js.InvokeVoidAsync("updateTitleText", response.Title);
|
||||
@if (response is not null) await JsRuntime.InvokeVoidAsync("updateTitleText", response.Title);
|
||||
}
|
||||
|
||||
private async Task UpdateScoreboard(Difficulty difficulty)
|
||||
{
|
||||
UpdateScores(difficulty);
|
||||
await Js.InvokeVoidAsync("updateScoreboardText", scoresArray);
|
||||
await JsRuntime.InvokeVoidAsync("updateScoreboardText", scoresArray);
|
||||
}
|
||||
}
|
@ -135,17 +135,17 @@ public partial class Profile
|
||||
"Synth Drum", "Shuriken", "Bubble Pop", "Electric Guitar"
|
||||
};
|
||||
|
||||
private static readonly string[] LanguageStrings =
|
||||
{
|
||||
"Japanese", "English", "Chinese (Traditional)", "Korean", "Chinese (Simplified)"
|
||||
};
|
||||
|
||||
private static readonly string[] TitlePlateStrings =
|
||||
{
|
||||
"Wood", "Rainbow", "Gold", "Purple",
|
||||
"AI 1", "AI 2", "AI 3", "AI 4"
|
||||
};
|
||||
|
||||
private static readonly string[] LanguageStrings =
|
||||
{
|
||||
"Japanese", "English", "Chinese (Traditional)", "Korean", "Chinese (Simplified)"
|
||||
};
|
||||
|
||||
private static readonly string[] DifficultySettingCourseStrings =
|
||||
{
|
||||
"None", "Set up each time",
|
||||
@ -170,13 +170,13 @@ public partial class Profile
|
||||
|
||||
private Difficulty highestDifficulty = Difficulty.Easy;
|
||||
|
||||
private List<int> costumeFlagArraySizes = new();
|
||||
|
||||
private List<uint> unlockedHeadCostumes = new();
|
||||
private List<uint> unlockedBodyCostumes = new();
|
||||
private List<uint> unlockedFaceCostumes = new();
|
||||
private List<uint> unlockedKigurumiCostumes = new();
|
||||
private List<uint> unlockedPuchiCostumes = new();
|
||||
private List<uint> kigurumiUniqueIdList = new();
|
||||
private List<uint> headUniqueIdList = new();
|
||||
private List<uint> bodyUniqueIdList = new();
|
||||
private List<uint> faceUniqueIdList = new();
|
||||
private List<uint> puchiUniqueIdList = new();
|
||||
private List<uint> titleUniqueIdList = new();
|
||||
private List<uint> titlePlateIdList = new();
|
||||
|
||||
private int[] scoresArray = new int[10];
|
||||
|
||||
@ -187,27 +187,19 @@ public partial class Profile
|
||||
response = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
response.ThrowIfNull();
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Dashboard", href: "/"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Users", href: "/Users"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
|
||||
};
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{response.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem("Profile", href: $"/Users/{Baid}/Profile", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Profile"], href: $"/Users/{Baid}/Profile", disabled: false));
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
unlockedHeadCostumes = response.UnlockedHead.Distinct().OrderBy(x => x).ToList();
|
||||
unlockedBodyCostumes = response.UnlockedBody.Distinct().OrderBy(x => x).ToList();
|
||||
unlockedFaceCostumes = response.UnlockedFace.Distinct().OrderBy(x => x).ToList();
|
||||
unlockedKigurumiCostumes = response.UnlockedKigurumi.Distinct().OrderBy(x => x).ToList();
|
||||
unlockedPuchiCostumes = response.UnlockedPuchi.Distinct().OrderBy(x => x).ToList();
|
||||
}
|
||||
|
||||
costumeFlagArraySizes = GameDataService.GetCostumeFlagArraySizes();
|
||||
InitializeAvailableCostumes();
|
||||
InitializeAvailableTitles();
|
||||
|
||||
songresponse = await Client.GetFromJsonAsync<SongBestResponse>($"api/PlayData/{Baid}");
|
||||
songresponse.ThrowIfNull();
|
||||
@ -229,14 +221,87 @@ public partial class Profile
|
||||
.CompareTo(GameDataService.GetMusicIndexBySongId(data2.SongId)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)Difficulty.UraOni; i++)
|
||||
for (var i = 0; i < (int)Difficulty.UraOni; i++)
|
||||
if (songBestDataMap.TryGetValue((Difficulty)i, out var values))
|
||||
{
|
||||
highestDifficulty = (Difficulty)i;
|
||||
}
|
||||
|
||||
if (response != null) UpdateScores(response.AchievementDisplayDifficulty);
|
||||
}
|
||||
|
||||
UpdateScores(response.AchievementDisplayDifficulty);
|
||||
private void InitializeAvailableCostumes()
|
||||
{
|
||||
var unlockedKigurumi = response != null ? response.UnlockedKigurumi : new List<uint>();
|
||||
var unlockedHead = response != null ? response.UnlockedHead : new List<uint>();
|
||||
var unlockedBody = response != null ? response.UnlockedBody : new List<uint>();
|
||||
var unlockedFace = response != null ? response.UnlockedFace : new List<uint>();
|
||||
var unlockedPuchi = response != null ? response.UnlockedPuchi : new List<uint>();
|
||||
|
||||
if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
kigurumiUniqueIdList = GameDataService.GetKigurumiUniqueIdList();
|
||||
headUniqueIdList = GameDataService.GetHeadUniqueIdList();
|
||||
bodyUniqueIdList = GameDataService.GetBodyUniqueIdList();
|
||||
faceUniqueIdList = GameDataService.GetFaceUniqueIdList();
|
||||
puchiUniqueIdList = GameDataService.GetPuchiUniqueIdList();
|
||||
|
||||
// Lock costumes in LockedCostumesList but not in UnlockedCostumesList
|
||||
var lockedKigurumiUniqueIdList = GameDataService.GetLockedKigurumiUniqueIdList().Except(unlockedKigurumi).ToList();
|
||||
var lockedHeadUniqueIdList = GameDataService.GetLockedHeadUniqueIdList().Except(unlockedHead).ToList();
|
||||
var lockedBodyUniqueIdList = GameDataService.GetLockedBodyUniqueIdList().Except(unlockedBody).ToList();
|
||||
var lockedFaceUniqueIdList = GameDataService.GetLockedFaceUniqueIdList().Except(unlockedFace).ToList();
|
||||
var lockedPuchiUniqueIdList = GameDataService.GetLockedPuchiUniqueIdList().Except(unlockedPuchi).ToList();
|
||||
|
||||
lockedKigurumiUniqueIdList.ForEach(id => kigurumiUniqueIdList.Remove(id));
|
||||
lockedHeadUniqueIdList.ForEach(id => headUniqueIdList.Remove(id));
|
||||
lockedBodyUniqueIdList.ForEach(id => bodyUniqueIdList.Remove(id));
|
||||
lockedFaceUniqueIdList.ForEach(id => faceUniqueIdList.Remove(id));
|
||||
lockedPuchiUniqueIdList.ForEach(id => puchiUniqueIdList.Remove(id));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only unlock costumes that are in both UnlockedCostumesList and CostumeList
|
||||
kigurumiUniqueIdList = GameDataService.GetKigurumiUniqueIdList().Intersect(unlockedKigurumi).ToList();
|
||||
headUniqueIdList = GameDataService.GetHeadUniqueIdList().Intersect(unlockedHead).ToList();
|
||||
bodyUniqueIdList = GameDataService.GetBodyUniqueIdList().Intersect(unlockedBody).ToList();
|
||||
faceUniqueIdList = GameDataService.GetFaceUniqueIdList().Intersect(unlockedFace).ToList();
|
||||
puchiUniqueIdList = GameDataService.GetPuchiUniqueIdList().Intersect(unlockedPuchi).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeAvailableTitlePlates()
|
||||
{
|
||||
titlePlateIdList = GameDataService.GetTitlePlateIdList().ToList();
|
||||
// Cut off ids longer than TitlePlateStrings
|
||||
titlePlateIdList = titlePlateIdList.Where(id => id < TitlePlateStrings.Length).Except(GameDataService.GetLockedTitlePlateIdList()).ToList();
|
||||
}
|
||||
|
||||
private void InitializeAvailableTitles()
|
||||
{
|
||||
InitializeAvailableTitlePlates();
|
||||
|
||||
var unlockedTitle = response != null ? response.UnlockedTitle : new List<uint>();
|
||||
|
||||
if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
titleUniqueIdList = GameDataService.GetTitleUniqueIdList();
|
||||
|
||||
var titles = GameDataService.GetTitles();
|
||||
// Lock titles in LockedTitlesList but not in UnlockedTitle
|
||||
var lockedTitleUniqueIdList = GameDataService.GetLockedTitleUniqueIdList().ToList();
|
||||
var lockedTitlePlateIdList = GameDataService.GetLockedTitlePlateIdList().ToList();
|
||||
// Unlock titles in UnlockedTitlesList
|
||||
lockedTitleUniqueIdList = lockedTitleUniqueIdList.Except(unlockedTitle).ToList();
|
||||
// Find uniqueIds of titles with rarity in lockedTitlePlateIdList
|
||||
lockedTitleUniqueIdList.AddRange(titles.Where(title => lockedTitlePlateIdList.Contains(title.TitleRarity)).Select(title => title.TitleId));
|
||||
titleUniqueIdList = titleUniqueIdList.Except(lockedTitleUniqueIdList).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only unlock titles that are in both UnlockedTitlesList and TitleList
|
||||
titleUniqueIdList = GameDataService.GetTitleUniqueIdList().Intersect(unlockedTitle).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveOptions()
|
||||
@ -246,7 +311,7 @@ public partial class Profile
|
||||
isSavingOptions = false;
|
||||
|
||||
// Adjust breadcrumb if name is changed
|
||||
if (response != null && response.MyDonName != null)
|
||||
if (response != null)
|
||||
{
|
||||
breadcrumbs[^2] = new BreadcrumbItem($"{response.MyDonName}", href: null, disabled: true);
|
||||
}
|
||||
@ -260,8 +325,8 @@ public partial class Profile
|
||||
|
||||
if (difficulty is Difficulty.None) difficulty = highestDifficulty;
|
||||
|
||||
if (songBestDataMap.TryGetValue(difficulty, out var values))
|
||||
{
|
||||
if (!songBestDataMap.TryGetValue(difficulty, out var values)) return;
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
switch (value.BestScoreRank)
|
||||
@ -302,8 +367,6 @@ public partial class Profile
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public static string CostumeOrDefault(string file, uint id, string defaultfile)
|
||||
{
|
||||
@ -327,7 +390,8 @@ public partial class Profile
|
||||
var parameters = new DialogParameters<ChooseTitleDialog>
|
||||
{
|
||||
{x => x.UserSetting, response},
|
||||
{x => x.AllowFreeProfileEditing, LoginService.AllowFreeProfileEditing}
|
||||
{x => x.AllowFreeProfileEditing, AuthService.AllowFreeProfileEditing},
|
||||
{x => x.TitleUniqueIdList, titleUniqueIdList}
|
||||
};
|
||||
var dialog = DialogService.Show<ChooseTitleDialog>("Player Titles", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
@ -1,16 +1,16 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/Register"
|
||||
|
||||
@if (LoginService.OnlyAdmin || !LoginService.LoginRequired)
|
||||
@if (AuthService.OnlyAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
Console.WriteLine("Registration is disabled. Redirecting to Dashboard...");
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
else if (LoginService.IsLoggedIn)
|
||||
else if (AuthService.IsLoggedIn)
|
||||
{
|
||||
// User is already logged in. Redirect to dashboard.
|
||||
NavigationManager.NavigateTo("/");
|
||||
@ -22,30 +22,32 @@ else
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<MudCard Elevation="0" Outlined="true">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h5">Register</MudText>
|
||||
<MudText Typo="Typo.h5">@Localizer["Register"]</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudForm @ref="registerForm">
|
||||
<div style="display:flex;flex-direction:column;gap:15px;">
|
||||
<MudTextField @bind-value="accessCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Required="@true" RequiredError="Access Code is required"
|
||||
Label="Access Code" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
@if (LoginService.RegisterWithLastPlayTime)
|
||||
FullWidth="true" Required="@true" RequiredError=@Localizer["Access Code is required"]
|
||||
Label=@Localizer["Access Code"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
@if (AuthService.RegisterWithLastPlayTime)
|
||||
{
|
||||
<MudDatePicker @ref="datePicker" Label="Last Play Date" @bind-Date="date" AutoClose="true" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudTimePicker @ref="timePicker" AmPm="true" Label="Last Play Time(5 min around credit end)" @bind-Time="time" AutoClose="true" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudTextField @bind-value="inviteCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Label=@Localizer["Invite Code (Optional)"]/>
|
||||
<MudDatePicker @ref="datePicker" Label=@Localizer["Last Play Date"] @bind-Date="date" AutoClose="true" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
<MudTimePicker @ref="timePicker" AmPm="true" Label=@Localizer["Last Play Time(5 Min Around Credit End)"] @bind-Time="time" AutoClose="true" Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
}
|
||||
<MudTextField @bind-Value="password" InputType="InputType.Password"
|
||||
T="string" FullWidth="true" Required="@true"
|
||||
RequiredError="Password is required"
|
||||
Label="Password" Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
RequiredError=@Localizer["Password is Required"]
|
||||
Label=@Localizer["Password"] Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
</MudTextField>
|
||||
<MudTextField @bind-Value="confirmPassword" InputType="InputType.Password"
|
||||
T="string" FullWidth="true" Required="@true"
|
||||
RequiredError="Confirm password is required"
|
||||
Label="Confirm Password" Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
RequiredError=@Localizer["Confirm Password is Required"]
|
||||
Label=@Localizer["Confirm Password"] Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
</MudTextField>
|
||||
<MudButton OnClick="OnRegister" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled">Register</MudButton>
|
||||
<MudButton OnClick="OnRegister" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled">@Localizer["Register"]</MudButton>
|
||||
</div>
|
||||
</MudForm>
|
||||
</MudCardContent>
|
||||
|
@ -11,67 +11,68 @@ public partial class Register
|
||||
private MudTimePicker timePicker = new();
|
||||
private DateTime? date = DateTime.Today;
|
||||
private TimeSpan? time = new TimeSpan(00, 45, 00);
|
||||
|
||||
private DashboardResponse? response;
|
||||
private string inviteCode = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
}
|
||||
|
||||
private async Task OnRegister()
|
||||
{
|
||||
var inputDateTime = date!.Value.Date + time!.Value;
|
||||
if (response != null)
|
||||
{
|
||||
var result = await LoginService.Register(accessCode, inputDateTime, password, confirmPassword, response, Client);
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
var result = await AuthService.Register(accessCode, inputDateTime, password, confirmPassword, inviteCode);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
"Only admin can log in.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Error"],
|
||||
"Only admin can register.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Success",
|
||||
Localizer["Success"],
|
||||
"Access code registered successfully.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
"Confirm password is not the same as password.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not found.<br />Please play one game with this access code to register it.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code is already registered, please use set password to login.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Wrong last play time.<br />If you have forgotten when you last played, please play another game with this access code.",
|
||||
"Ok", null, null, options);
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 6:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,34 +2,26 @@
|
||||
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
@using TaikoWebUI.Utilities;
|
||||
@using TaikoWebUI.Components.Song;
|
||||
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
NavigationManager.NavigateTo(AuthService.IsLoggedIn ? "/" : "/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (response is not null)
|
||||
{
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-3"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h5">@SongTitle</MudText>
|
||||
<MudText Typo="Typo.body2">@SongArtist</MudText>
|
||||
<MudText Typo="Typo.h5">@songTitle</MudText>
|
||||
<MudText Typo="Typo.body2">@songArtist</MudText>
|
||||
<MudGrid Class="my-4 pb-10">
|
||||
<MudItem xs="12">
|
||||
<PlayHistoryCard Items="@SongBestData?.RecentPlayData" />
|
||||
<PlayHistoryCard Items="@songHistoryData" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
|
@ -11,47 +11,48 @@ namespace TaikoWebUI.Pages
|
||||
public int Baid { get; set; }
|
||||
|
||||
private UserSetting? userSetting;
|
||||
private SongBestResponse? response;
|
||||
private SongBestData? SongBestData;
|
||||
private List<BreadcrumbItem> breadcrumbs = new List<BreadcrumbItem>();
|
||||
private SongHistoryResponse? response;
|
||||
private List<SongHistoryData>? songHistoryData;
|
||||
private readonly List<BreadcrumbItem> breadcrumbs = new();
|
||||
|
||||
private string SongTitle = string.Empty;
|
||||
private string SongArtist = string.Empty;
|
||||
private string songTitle = string.Empty;
|
||||
private string songArtist = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
response = await Client.GetFromJsonAsync<SongBestResponse>($"api/PlayData/{Baid}");
|
||||
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
|
||||
response.ThrowIfNull();
|
||||
SongBestData = response.SongBestData.FirstOrDefault(x => x.SongId == SongId);
|
||||
// Get all song best data with SongId
|
||||
songHistoryData = response.SongHistoryData.Where(data => data.SongId == (uint)SongId).ToList();
|
||||
|
||||
// Get user settings
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
// Get song title and artist
|
||||
var language = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
SongTitle = GameDataService.GetMusicNameBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
SongArtist = GameDataService.GetMusicArtistBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
songTitle = GameDataService.GetMusicNameBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
songArtist = GameDataService.GetMusicArtistBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
|
||||
// Breadcrumbs
|
||||
var _songTitle = SongTitle;
|
||||
if (_songTitle.Length > 20)
|
||||
var formattedSongTitle = songTitle;
|
||||
if (formattedSongTitle.Length > 20)
|
||||
{
|
||||
_songTitle = _songTitle.Substring(0, 20) + "...";
|
||||
formattedSongTitle = string.Concat(formattedSongTitle.AsSpan(0, 20), "...");
|
||||
}
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Dashboard", href: "/"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Users", href: "/Users"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
|
||||
};
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem("Songs", href: $"/Users/{Baid}/Songs", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(_songTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(formattedSongTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
@using TaikoWebUI.Utilities;
|
||||
@using TaikoWebUI.Shared.Models;
|
||||
@using SharedProject.Enums;
|
||||
|
||||
@page "/Users/{baid:int}/Songs"
|
||||
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h4">@Localizer["Key_01"]</MudText>
|
||||
<MudText Typo="Typo.h4">@Localizer["Song List"]</MudText>
|
||||
|
||||
<MudGrid Class="my-8">
|
||||
@if (response is null)
|
||||
@ -21,16 +20,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
NavigationManager.NavigateTo(AuthService.IsLoggedIn ? "/" : "/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -65,12 +57,12 @@
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="MusicDetail" SortBy="context => GameDataService.GetMusicNameBySongId(context.SongId, CurrentLanguage)">
|
||||
Song Title / Artist
|
||||
@Localizer["Song Title / Artist"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="MusicDetail" SortBy="context => context.Genre">
|
||||
Genre
|
||||
@Localizer["Genre"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
@foreach (var difficulty in Enum.GetValues<Difficulty>())
|
||||
@ -138,7 +130,7 @@
|
||||
}
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page"] />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.JSInterop;
|
||||
using System.Reflection.Emit;
|
||||
using Microsoft.JSInterop;
|
||||
using TaikoWebUI.Shared.Models;
|
||||
|
||||
|
||||
@ -9,8 +10,6 @@ public partial class SongList
|
||||
[Parameter]
|
||||
public int Baid { get; set; }
|
||||
|
||||
private const string IconStyle = "width:25px; height:25px;";
|
||||
|
||||
private string Search { get; set; } = string.Empty;
|
||||
private string GenreFilter { get; set; } = string.Empty;
|
||||
private string CurrentLanguage { get; set; } = "ja";
|
||||
@ -18,8 +17,6 @@ public partial class SongList
|
||||
private SongBestResponse? response;
|
||||
private UserSetting? userSetting;
|
||||
|
||||
private Dictionary<Difficulty, List<SongBestData>> songBestDataMap = new();
|
||||
|
||||
private readonly List<BreadcrumbItem> breadcrumbs = new();
|
||||
|
||||
private List<MusicDetail> musicMap = new();
|
||||
@ -33,33 +30,18 @@ public partial class SongList
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
musicMap = GameDataService.GetMusicList();
|
||||
|
||||
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Dashboard", href: "/"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem("Users", href: "/Users"));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
|
||||
};
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem("Songs", href: $"/Users/{Baid}/Songs", disabled: false));
|
||||
}
|
||||
|
||||
private async Task OnFavoriteToggled(SongBestData data)
|
||||
{
|
||||
var request = new SetFavoriteRequest
|
||||
{
|
||||
Baid = (uint)Baid,
|
||||
IsFavorite = !data.IsFavorite,
|
||||
SongId = data.SongId
|
||||
};
|
||||
var result = await Client.PostAsJsonAsync("api/FavoriteSongs", request);
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
data.IsFavorite = !data.IsFavorite;
|
||||
}
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
|
||||
}
|
||||
|
||||
private bool FilterSongs(MusicDetail musicDetail)
|
||||
|
@ -1,54 +1,17 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@using TaikoWebUI.Components
|
||||
|
||||
@page "/Users"
|
||||
|
||||
<MudText Typo="Typo.h4">@Localizer["users"]</MudText>
|
||||
<MudText Typo="Typo.h4">@Localizer["Users"]</MudText>
|
||||
<MudGrid Class="my-8">
|
||||
@if (response is not null)
|
||||
{
|
||||
// Response received and users are available
|
||||
if (response.Users.Count != 0)
|
||||
{
|
||||
if (LoginService.IsAdmin || !LoginService.LoginRequired) // Admin mode, can see all users
|
||||
{
|
||||
@foreach (var user in response.Users)
|
||||
{
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<UserCard user="user" />
|
||||
</MudItem>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not admin, redirect
|
||||
@if (!LoginService.IsLoggedIn) // Not logged in, show login form
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // No users in the database
|
||||
<MudItem xs="12">
|
||||
<MudText Align="Align.Center" Class="my-8">
|
||||
@Localizer["No data."]
|
||||
</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
} else
|
||||
{
|
||||
@if (!AuthService.LoginRequired || (AuthService.LoginRequired && AuthService.IsAdmin)) {
|
||||
if (users == null) {
|
||||
// Loading...
|
||||
@for (uint i = 0; i < 6; i++)
|
||||
{
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>
|
||||
@ -65,5 +28,24 @@
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
} else if (users.Count > 0) {
|
||||
foreach (var user in users)
|
||||
{
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<UserCard User="user" />
|
||||
</MudItem>
|
||||
}
|
||||
} else { // No users in the database
|
||||
<MudItem xs="12">
|
||||
<MudText Align="Align.Center" Class="my-8">
|
||||
@Localizer["No data."]
|
||||
</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
} else if (AuthService.LoginRequired && !AuthService.IsLoggedIn) {
|
||||
// Not logged in, redirect
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
} else {
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
</MudGrid>
|
@ -2,11 +2,14 @@
|
||||
|
||||
public partial class Users
|
||||
{
|
||||
private DashboardResponse? response;
|
||||
private List<User>? users;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
if (AuthService.IsAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
users = await Client.GetFromJsonAsync<List<User>>("api/Users");
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ builder.Services.AddSingleton<IGameDataService, GameDataService>();
|
||||
|
||||
builder.Services.Configure<WebUiSettings>(builder.Configuration.GetSection(nameof(WebUiSettings)));
|
||||
|
||||
builder.Services.AddScoped<LoginService>();
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
builder.Services.AddLocalization();
|
||||
builder.Services.AddSingleton<MudLocalizer, ResXMudLocalizer>();
|
||||
builder.Services.AddSingleton<ScoreUtils>();
|
||||
|
240
TaikoWebUI/Services/AuthService.cs
Normal file
240
TaikoWebUI/Services/AuthService.cs
Normal file
@ -0,0 +1,240 @@
|
||||
using System.Diagnostics;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TaikoWebUI.Settings;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Services;
|
||||
|
||||
public sealed class AuthService
|
||||
{
|
||||
public event EventHandler? LoginStatusChanged;
|
||||
public bool LoginRequired { get; }
|
||||
public bool OnlyAdmin { get; }
|
||||
private readonly int boundAccessCodeUpperLimit;
|
||||
public bool RegisterWithLastPlayTime { get; }
|
||||
public bool AllowUserDelete { get; }
|
||||
public bool AllowFreeProfileEditing { get; }
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
private uint LoggedInBaid { get; set; }
|
||||
public bool IsAdmin { get; private set; }
|
||||
private readonly ILocalStorageService localStorage;
|
||||
private readonly HttpClient client;
|
||||
|
||||
public AuthService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage, HttpClient client)
|
||||
{
|
||||
this.localStorage = localStorage;
|
||||
IsLoggedIn = false;
|
||||
IsAdmin = false;
|
||||
var webUiSettings = settings.Value;
|
||||
LoginRequired = webUiSettings.LoginRequired;
|
||||
OnlyAdmin = webUiSettings.OnlyAdmin;
|
||||
boundAccessCodeUpperLimit = webUiSettings.BoundAccessCodeUpperLimit;
|
||||
RegisterWithLastPlayTime = webUiSettings.RegisterWithLastPlayTime;
|
||||
AllowUserDelete = webUiSettings.AllowUserDelete;
|
||||
AllowFreeProfileEditing = webUiSettings.AllowFreeProfileEditing;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private void OnLoginStatusChanged()
|
||||
{
|
||||
LoginStatusChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static (uint, bool) GetBaidAndIsAdminFromToken(string authToken)
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwtSecurityToken = handler.ReadJwtToken(authToken);
|
||||
var baid = uint.Parse(jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value);
|
||||
var isAdmin = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Role).Value == "Admin";
|
||||
return (baid, isAdmin);
|
||||
}
|
||||
|
||||
public async Task<int> Login(string inputAccessCode, string inputPassword)
|
||||
{
|
||||
// strip spaces or dashes from card number
|
||||
inputAccessCode = inputAccessCode.Replace(" ", "").Replace("-", "").Replace(":", "");
|
||||
|
||||
var request = new LoginRequest
|
||||
{
|
||||
AccessCode = inputAccessCode,
|
||||
Password = inputPassword
|
||||
};
|
||||
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Auth/Login", request);
|
||||
|
||||
if (!responseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
// Unauthorized, extract specific error message as json
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
// Unknown error message
|
||||
if (responseJson is null) return 5;
|
||||
var errorMessage = responseJson["message"];
|
||||
return errorMessage switch
|
||||
{
|
||||
"Access Code Not Found" => 3,
|
||||
"User Not Registered" => 4,
|
||||
"Invalid Password" => 2,
|
||||
_ => 5
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Authorized, store Jwt token
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
if (responseJson is null) return 5;
|
||||
|
||||
var authToken = responseJson["authToken"];
|
||||
await localStorage.SetItemAsync("authToken", authToken);
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
|
||||
|
||||
var (baid, isAdmin) = GetBaidAndIsAdminFromToken(authToken);
|
||||
IsLoggedIn = true;
|
||||
IsAdmin = isAdmin;
|
||||
LoggedInBaid = baid;
|
||||
OnLoginStatusChanged();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoginWithAuthToken()
|
||||
{
|
||||
var hasAuthToken = await localStorage.ContainKeyAsync("authToken");
|
||||
if (!hasAuthToken) return;
|
||||
|
||||
// Attempt to get JWT token from local storage
|
||||
var authToken = await localStorage.GetItemAsync<string>("authToken");
|
||||
if (authToken == null) return;
|
||||
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
|
||||
var responseMessage = await client.PostAsync("api/Auth/LoginWithToken", null);
|
||||
if (!responseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
// Clear JWT token
|
||||
await localStorage.RemoveItemAsync("authToken");
|
||||
return;
|
||||
}
|
||||
|
||||
var (baid, isAdmin) = GetBaidAndIsAdminFromToken(authToken);
|
||||
|
||||
IsLoggedIn = true;
|
||||
IsAdmin = isAdmin;
|
||||
LoggedInBaid = baid;
|
||||
OnLoginStatusChanged();
|
||||
}
|
||||
|
||||
public async Task<int> Register(string inputCardNum, DateTime inputDateTime, string inputPassword,
|
||||
string inputConfirmPassword, string inviteCode)
|
||||
{
|
||||
if (OnlyAdmin) return 0;
|
||||
|
||||
if (inputPassword != inputConfirmPassword) return 2;
|
||||
|
||||
// strip spaces or dashes from card number
|
||||
inputCardNum = inputCardNum.Replace(" ", "").Replace("-", "").Replace(":", "");
|
||||
|
||||
var request = new RegisterRequest
|
||||
{
|
||||
AccessCode = inputCardNum,
|
||||
Password = inputPassword,
|
||||
RegisterWithLastPlayTime = RegisterWithLastPlayTime,
|
||||
LastPlayDateTime = inputDateTime,
|
||||
InviteCode = inviteCode
|
||||
};
|
||||
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Auth/Register", request);
|
||||
|
||||
if (responseMessage.IsSuccessStatusCode) return 1;
|
||||
|
||||
// Unauthorized, extract specific error message as json
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
// Unknown error message
|
||||
if (responseJson is null) return 6;
|
||||
var errorMessage = responseJson["message"];
|
||||
return errorMessage switch
|
||||
{
|
||||
"Access Code Not Found" => 3,
|
||||
"User Already Registered" => 4,
|
||||
"Wrong Last Play Time" => 5,
|
||||
_ => 6
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<int> ChangePassword(string inputAccessCode, string inputOldPassword, string inputNewPassword,
|
||||
string inputConfirmNewPassword)
|
||||
{
|
||||
if (OnlyAdmin) return 0;
|
||||
|
||||
if (inputNewPassword != inputConfirmNewPassword) return 2;
|
||||
|
||||
var request = new ChangePasswordRequest
|
||||
{
|
||||
AccessCode = inputAccessCode,
|
||||
OldPassword = inputOldPassword,
|
||||
NewPassword = inputNewPassword
|
||||
};
|
||||
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Auth/ChangePassword", request);
|
||||
|
||||
if (responseMessage.IsSuccessStatusCode) return 1;
|
||||
|
||||
// Unauthorized, extract specific error message as json
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
// Unknown error message
|
||||
if (responseJson is null) return 6;
|
||||
var errorMessage = responseJson["message"];
|
||||
return errorMessage switch
|
||||
{
|
||||
"Access Code Not Found" => 3,
|
||||
"User Not Registered" => 5,
|
||||
"Wrong Old Password" => 4,
|
||||
_ => 6
|
||||
};
|
||||
}
|
||||
|
||||
public async Task Logout()
|
||||
{
|
||||
IsLoggedIn = false;
|
||||
LoggedInBaid = 0;
|
||||
IsAdmin = false;
|
||||
|
||||
// Clear JWT token
|
||||
await localStorage.RemoveItemAsync("authToken");
|
||||
OnLoginStatusChanged();
|
||||
}
|
||||
|
||||
public async Task<User?> GetLoggedInUser()
|
||||
{
|
||||
return await client.GetFromJsonAsync<User>($"api/Users/{LoggedInBaid}");
|
||||
}
|
||||
|
||||
public uint GetLoggedInBaid()
|
||||
{
|
||||
return LoggedInBaid;
|
||||
}
|
||||
|
||||
public async Task<int> BindAccessCode(string inputAccessCode, User user)
|
||||
{
|
||||
if (inputAccessCode.Trim() == "") return 4; /*Empty access code*/
|
||||
if (!IsLoggedIn && LoginRequired) return 0; /*User not connected and login is required*/
|
||||
var loggedInUser = await GetLoggedInUser();
|
||||
if (loggedInUser == null) return 0;
|
||||
if (LoginRequired && !IsAdmin && user.Baid != loggedInUser.Baid) return 5; /*User not admin trying to update someone else's Access Codes*/
|
||||
if (user.AccessCodes.Count >= boundAccessCodeUpperLimit) return 2; /*Limit of codes has been reached*/
|
||||
|
||||
var request = new BindAccessCodeRequest
|
||||
{
|
||||
AccessCode = inputAccessCode,
|
||||
Baid = user.Baid
|
||||
};
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Cards/BindAccessCode", request);
|
||||
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
||||
}
|
||||
}
|
@ -8,8 +8,6 @@ public class GameDataService : IGameDataService
|
||||
{
|
||||
private readonly HttpClient client;
|
||||
private readonly Dictionary<uint, MusicDetail> musicMap = new();
|
||||
private List<int> costumeFlagArraySizes = new();
|
||||
private int titleFlagArraySize;
|
||||
private ImmutableDictionary<uint, DanData> danMap = ImmutableDictionary<uint, DanData>.Empty;
|
||||
private ImmutableHashSet<Title> titles = ImmutableHashSet<Title>.Empty;
|
||||
|
||||
@ -19,6 +17,23 @@ public class GameDataService : IGameDataService
|
||||
private string[] kigurumiTitles = { };
|
||||
private string[] puchiTitles = { };
|
||||
|
||||
private List<uint> kigurumiUniqueIdList = new();
|
||||
private List<uint> headUniqueIdList = new();
|
||||
private List<uint> bodyUniqueIdList = new();
|
||||
private List<uint> faceUniqueIdList = new();
|
||||
private List<uint> puchiUniqueIdList = new();
|
||||
|
||||
private List<uint> titleUniqueIdList = new();
|
||||
private List<uint> titlePlateIdList = new();
|
||||
|
||||
private List<uint> lockedKigurumiUniqueIdList = new();
|
||||
private List<uint> lockedHeadUniqueIdList = new();
|
||||
private List<uint> lockedBodyUniqueIdList = new();
|
||||
private List<uint> lockedFaceUniqueIdList = new();
|
||||
private List<uint> lockedPuchiUniqueIdList = new();
|
||||
private List<uint> lockedTitleUniqueIdList = new();
|
||||
private List<uint> lockedTitlePlateIdList = new();
|
||||
|
||||
public GameDataService(HttpClient client)
|
||||
{
|
||||
this.client = client;
|
||||
@ -38,19 +53,30 @@ public class GameDataService : IGameDataService
|
||||
danMap = danData.ToImmutableDictionary(data => data.DanId);
|
||||
|
||||
// To prevent duplicate entries in wordlist
|
||||
var dict = wordList.WordListEntries.GroupBy(entry => entry.Key)
|
||||
var wordlistDict = wordList.WordListEntries.GroupBy(entry => entry.Key)
|
||||
.ToImmutableDictionary(group => group.Key, group => group.First());
|
||||
await Task.Run(() => InitializeMusicMap(musicInfo, dict, musicOrder));
|
||||
await Task.Run(() => InitializeMusicMap(musicInfo, wordlistDict, musicOrder));
|
||||
|
||||
InitializeCostumeFlagArraySizes(donCosRewardData);
|
||||
InitializeTitleFlagArraySize(shougouData);
|
||||
await Task.Run(() => InitializeCostumeIdLists(donCosRewardData));
|
||||
await Task.Run(() => InitializeTitleIdList(shougouData));
|
||||
|
||||
await Task.Run(() => InitializeHeadTitles(dict));
|
||||
await Task.Run(() => InitializeFaceTitles(dict));
|
||||
await Task.Run(() => InitializeBodyTitles(dict));
|
||||
await Task.Run(() => InitializePuchiTitles(dict));
|
||||
await Task.Run(() => InitializeKigurumiTitles(dict));
|
||||
await Task.Run(() => InitializeTitles(dict, shougouData));
|
||||
await Task.Run(() => InitializeHeadTitles(wordlistDict));
|
||||
await Task.Run(() => InitializeFaceTitles(wordlistDict));
|
||||
await Task.Run(() => InitializeBodyTitles(wordlistDict));
|
||||
await Task.Run(() => InitializePuchiTitles(wordlistDict));
|
||||
await Task.Run(() => InitializeKigurumiTitles(wordlistDict));
|
||||
await Task.Run(() => InitializeTitles(wordlistDict, shougouData));
|
||||
|
||||
var lockedCostumeDataDictionary = await client.GetFromJsonAsync<Dictionary<string, List<uint>>>($"{dataBaseUrl}/data/locked_costume_data.json") ?? throw new InvalidOperationException();
|
||||
lockedKigurumiUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Kigurumi") ?? new List<uint>();
|
||||
lockedHeadUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Head") ?? new List<uint>();
|
||||
lockedBodyUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Body") ?? new List<uint>();
|
||||
lockedFaceUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Face") ?? new List<uint>();
|
||||
lockedPuchiUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Puchi") ?? new List<uint>();
|
||||
|
||||
var lockedTitleDataDictionary = await client.GetFromJsonAsync<Dictionary<string, List<uint>>>($"{dataBaseUrl}/data/locked_title_data.json") ?? throw new InvalidOperationException();
|
||||
lockedTitleUniqueIdList = lockedTitleDataDictionary.GetValueOrDefault("TitleNo") ?? new List<uint>();
|
||||
lockedTitlePlateIdList = lockedTitleDataDictionary.GetValueOrDefault("TitlePlateNo") ?? new List<uint>();
|
||||
}
|
||||
|
||||
private async Task<T> GetData<T>(string dataBaseUrl, string fileBaseName) where T : notnull
|
||||
@ -151,15 +177,45 @@ public class GameDataService : IGameDataService
|
||||
return titles;
|
||||
}
|
||||
|
||||
public List<int> GetCostumeFlagArraySizes()
|
||||
public List<uint> GetKigurumiUniqueIdList()
|
||||
{
|
||||
return costumeFlagArraySizes;
|
||||
return kigurumiUniqueIdList;
|
||||
}
|
||||
|
||||
private void InitializeTitleFlagArraySize(Shougous? shougouData)
|
||||
public List<uint> GetHeadUniqueIdList()
|
||||
{
|
||||
return headUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetBodyUniqueIdList()
|
||||
{
|
||||
return bodyUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetFaceUniqueIdList()
|
||||
{
|
||||
return faceUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetPuchiUniqueIdList()
|
||||
{
|
||||
return puchiUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetTitleUniqueIdList()
|
||||
{
|
||||
return titleUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetTitlePlateIdList()
|
||||
{
|
||||
return titlePlateIdList;
|
||||
}
|
||||
|
||||
private void InitializeTitleIdList(Shougous? shougouData)
|
||||
{
|
||||
shougouData.ThrowIfNull("Shouldn't happen!");
|
||||
titleFlagArraySize = (int)shougouData.ShougouEntries.Max(entry => entry.UniqueId) + 1;
|
||||
titleUniqueIdList = shougouData.ShougouEntries.Select(entry => entry.UniqueId).ToList();
|
||||
}
|
||||
|
||||
private void InitializeTitles(ImmutableDictionary<string, WordListEntry> dict, Shougous? shougouData)
|
||||
@ -167,7 +223,7 @@ public class GameDataService : IGameDataService
|
||||
shougouData.ThrowIfNull("Shouldn't happen!");
|
||||
|
||||
var set = ImmutableHashSet.CreateBuilder<Title>();
|
||||
for (var i = 1; i < titleFlagArraySize; i++)
|
||||
foreach (var i in titleUniqueIdList)
|
||||
{
|
||||
var key = $"syougou_{i}";
|
||||
|
||||
@ -178,6 +234,11 @@ public class GameDataService : IGameDataService
|
||||
.Select(entry => entry.Rarity)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!titlePlateIdList.Contains(titleRarity))
|
||||
{
|
||||
titlePlateIdList.Add(titleRarity);
|
||||
}
|
||||
|
||||
set.Add(new Title
|
||||
{
|
||||
TitleName = titleWordlistItem.JapaneseText,
|
||||
@ -189,40 +250,30 @@ public class GameDataService : IGameDataService
|
||||
titles = set.ToImmutable();
|
||||
}
|
||||
|
||||
private void InitializeCostumeFlagArraySizes(DonCosRewards? donCosRewardData)
|
||||
private void InitializeCostumeIdLists(DonCosRewards? donCosRewardData)
|
||||
{
|
||||
donCosRewardData.ThrowIfNull("Shouldn't happen!");
|
||||
var kigurumiUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
kigurumiUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Where(entry => entry.CosType == "kigurumi")
|
||||
.Select(entry => entry.UniqueId);
|
||||
var headUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Select(entry => entry.UniqueId).ToList();
|
||||
headUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Where(entry => entry.CosType == "head")
|
||||
.Select(entry => entry.UniqueId);
|
||||
var bodyUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Select(entry => entry.UniqueId).ToList();
|
||||
bodyUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Where(entry => entry.CosType == "body")
|
||||
.Select(entry => entry.UniqueId);
|
||||
var faceUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Select(entry => entry.UniqueId).ToList();
|
||||
faceUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Where(entry => entry.CosType == "face")
|
||||
.Select(entry => entry.UniqueId);
|
||||
var puchiUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Select(entry => entry.UniqueId).ToList();
|
||||
puchiUniqueIdList = donCosRewardData.DonCosRewardEntries
|
||||
.Where(entry => entry.CosType == "puchi")
|
||||
.Select(entry => entry.UniqueId);
|
||||
|
||||
costumeFlagArraySizes = new List<int>
|
||||
{
|
||||
(int)kigurumiUniqueIdList.Max() + 1,
|
||||
(int)headUniqueIdList.Max() + 1,
|
||||
(int)bodyUniqueIdList.Max() + 1,
|
||||
(int)faceUniqueIdList.Max() + 1,
|
||||
(int)puchiUniqueIdList.Max() + 1
|
||||
};
|
||||
.Select(entry => entry.UniqueId).ToList();
|
||||
}
|
||||
|
||||
private void InitializeKigurumiTitles(ImmutableDictionary<string, WordListEntry> dict)
|
||||
{
|
||||
var costumeKigurumiMax = costumeFlagArraySizes[0];
|
||||
kigurumiTitles = new string[costumeKigurumiMax];
|
||||
for (var i = 0; i < costumeKigurumiMax; i++)
|
||||
kigurumiTitles = new string[kigurumiUniqueIdList.Max() + 1];
|
||||
foreach (var i in kigurumiUniqueIdList)
|
||||
{
|
||||
var key = $"costume_kigurumi_{i}";
|
||||
|
||||
@ -233,9 +284,8 @@ public class GameDataService : IGameDataService
|
||||
|
||||
private void InitializeHeadTitles(ImmutableDictionary<string, WordListEntry> dict)
|
||||
{
|
||||
var costumeHeadMax = costumeFlagArraySizes[1];
|
||||
headTitles = new string[costumeHeadMax];
|
||||
for (var i = 0; i < costumeHeadMax; i++)
|
||||
headTitles = new string[headUniqueIdList.Max() + 1];
|
||||
foreach (var i in headUniqueIdList)
|
||||
{
|
||||
var key = $"costume_head_{i}";
|
||||
|
||||
@ -246,9 +296,8 @@ public class GameDataService : IGameDataService
|
||||
|
||||
private void InitializeBodyTitles(ImmutableDictionary<string, WordListEntry> dict)
|
||||
{
|
||||
var costumeBodyMax = costumeFlagArraySizes[2];
|
||||
bodyTitles = new string[costumeBodyMax];
|
||||
for (var i = 0; i < costumeBodyMax; i++)
|
||||
bodyTitles = new string[bodyUniqueIdList.Max() + 1];
|
||||
foreach (var i in bodyUniqueIdList)
|
||||
{
|
||||
var key = $"costume_body_{i}";
|
||||
|
||||
@ -259,9 +308,8 @@ public class GameDataService : IGameDataService
|
||||
|
||||
private void InitializeFaceTitles(ImmutableDictionary<string, WordListEntry> dict)
|
||||
{
|
||||
var costumeFaceMax = costumeFlagArraySizes[3];
|
||||
faceTitles = new string[costumeFaceMax];
|
||||
for (var i = 0; i < costumeFaceMax; i++)
|
||||
faceTitles = new string[faceUniqueIdList.Max() + 1];
|
||||
foreach (var i in faceUniqueIdList)
|
||||
{
|
||||
var key = $"costume_face_{i}";
|
||||
|
||||
@ -272,9 +320,8 @@ public class GameDataService : IGameDataService
|
||||
|
||||
private void InitializePuchiTitles(ImmutableDictionary<string, WordListEntry> dict)
|
||||
{
|
||||
var costumePuchiMax = costumeFlagArraySizes[4];
|
||||
puchiTitles = new string[costumePuchiMax];
|
||||
for (var i = 0; i < costumePuchiMax; i++)
|
||||
puchiTitles = new string[puchiUniqueIdList.Max() + 1];
|
||||
foreach (var i in puchiUniqueIdList)
|
||||
{
|
||||
var key = $"costume_puchi_{i}";
|
||||
|
||||
@ -322,4 +369,39 @@ public class GameDataService : IGameDataService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<uint> GetLockedKigurumiUniqueIdList()
|
||||
{
|
||||
return lockedKigurumiUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetLockedHeadUniqueIdList()
|
||||
{
|
||||
return lockedHeadUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetLockedBodyUniqueIdList()
|
||||
{
|
||||
return lockedBodyUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetLockedFaceUniqueIdList()
|
||||
{
|
||||
return lockedFaceUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetLockedPuchiUniqueIdList()
|
||||
{
|
||||
return lockedPuchiUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetLockedTitleUniqueIdList()
|
||||
{
|
||||
return lockedTitleUniqueIdList;
|
||||
}
|
||||
|
||||
public List<uint> GetLockedTitlePlateIdList()
|
||||
{
|
||||
return lockedTitlePlateIdList;
|
||||
}
|
||||
}
|
@ -27,7 +27,21 @@ public interface IGameDataService
|
||||
public string GetFaceTitle(uint index);
|
||||
public string GetPuchiTitle(uint index);
|
||||
|
||||
public List<int> GetCostumeFlagArraySizes();
|
||||
public List<uint> GetKigurumiUniqueIdList();
|
||||
public List<uint> GetHeadUniqueIdList();
|
||||
public List<uint> GetBodyUniqueIdList();
|
||||
public List<uint> GetFaceUniqueIdList();
|
||||
public List<uint> GetPuchiUniqueIdList();
|
||||
public List<uint> GetTitleUniqueIdList();
|
||||
public List<uint> GetTitlePlateIdList();
|
||||
|
||||
public List<uint> GetLockedKigurumiUniqueIdList();
|
||||
public List<uint> GetLockedHeadUniqueIdList();
|
||||
public List<uint> GetLockedBodyUniqueIdList();
|
||||
public List<uint> GetLockedFaceUniqueIdList();
|
||||
public List<uint> GetLockedPuchiUniqueIdList();
|
||||
public List<uint> GetLockedTitleUniqueIdList();
|
||||
public List<uint> GetLockedTitlePlateIdList();
|
||||
|
||||
public ImmutableHashSet<Title> GetTitles();
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TaikoWebUI.Settings;
|
||||
|
||||
namespace TaikoWebUI.Services;
|
||||
|
||||
public class LoginService
|
||||
{
|
||||
public event EventHandler? LoginStatusChanged;
|
||||
public delegate void LoginStatusChangedEventHandler(object? sender, EventArgs e);
|
||||
public bool LoginRequired { get; }
|
||||
public bool OnlyAdmin { get; }
|
||||
private readonly int boundAccessCodeUpperLimit;
|
||||
public bool RegisterWithLastPlayTime { get; }
|
||||
public bool AllowUserDelete { get; }
|
||||
public bool AllowFreeProfileEditing { get; }
|
||||
|
||||
public LoginService(IOptions<WebUiSettings> settings)
|
||||
{
|
||||
IsLoggedIn = false;
|
||||
IsAdmin = false;
|
||||
var webUiSettings = settings.Value;
|
||||
LoginRequired = webUiSettings.LoginRequired;
|
||||
OnlyAdmin = webUiSettings.OnlyAdmin;
|
||||
boundAccessCodeUpperLimit = webUiSettings.BoundAccessCodeUpperLimit;
|
||||
RegisterWithLastPlayTime = webUiSettings.RegisterWithLastPlayTime;
|
||||
AllowUserDelete = webUiSettings.AllowUserDelete;
|
||||
AllowFreeProfileEditing = webUiSettings.AllowFreeProfileEditing;
|
||||
}
|
||||
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
private User LoggedInUser { get; set; } = new();
|
||||
public bool IsAdmin { get; private set; }
|
||||
|
||||
protected virtual void OnLoginStatusChanged()
|
||||
{
|
||||
LoginStatusChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public int Login(string inputCardNum, string inputPassword, DashboardResponse response)
|
||||
{
|
||||
// strip spaces or dashes from card number
|
||||
inputCardNum = inputCardNum.Replace(" ", "").Replace("-", "");
|
||||
|
||||
foreach (var user in response.Users.Where(user => user.AccessCodes.Contains(inputCardNum)))
|
||||
{
|
||||
foreach (var userCredential in response.UserCredentials.Where(userCredential => userCredential.Baid == user.Baid))
|
||||
{
|
||||
if (userCredential.Password == "") return 4;
|
||||
if (ComputeHash(inputPassword, userCredential.Salt) != userCredential.Password) return 2;
|
||||
IsAdmin = user.IsAdmin;
|
||||
if (!IsAdmin && OnlyAdmin) return 0;
|
||||
IsLoggedIn = true;
|
||||
LoggedInUser = user;
|
||||
|
||||
OnLoginStatusChanged();
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
public async Task<int> Register(string inputCardNum, DateTime inputDateTime, string inputPassword, string inputConfirmPassword,
|
||||
DashboardResponse response, HttpClient client)
|
||||
{
|
||||
if (OnlyAdmin) return 0;
|
||||
|
||||
// strip spaces or dashes from card number
|
||||
inputCardNum = inputCardNum.Replace(" ", "").Replace("-", "");
|
||||
|
||||
foreach (var user in response.Users.Where(user => user.AccessCodes.Contains(inputCardNum)))
|
||||
{
|
||||
foreach (var userCredential in response.UserCredentials.Where(userCredential => userCredential.Baid == user.Baid))
|
||||
{
|
||||
if (RegisterWithLastPlayTime)
|
||||
{
|
||||
var userSettingResponse = await client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{user.Baid}");
|
||||
if (userSettingResponse is null) return 3;
|
||||
var lastPlayDateTime = userSettingResponse.LastPlayDateTime;
|
||||
var diffMinutes = (inputDateTime - lastPlayDateTime).Duration().TotalMinutes;
|
||||
if (diffMinutes > 5) return 5;
|
||||
}
|
||||
if (userCredential.Password != "") return 4;
|
||||
if (inputPassword != inputConfirmPassword) return 2;
|
||||
var salt = CreateSalt();
|
||||
var request = new SetPasswordRequest
|
||||
{
|
||||
Baid = user.Baid,
|
||||
Password = ComputeHash(inputPassword, salt),
|
||||
Salt = salt
|
||||
};
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Credentials", request);
|
||||
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
||||
}
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
private static string CreateSalt()
|
||||
{
|
||||
//Generate a cryptographic random number.
|
||||
var randomNumber = new byte[32];
|
||||
var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(randomNumber);
|
||||
var salt = Convert.ToBase64String(randomNumber);
|
||||
|
||||
// Return a Base64 string representation of the random number.
|
||||
return salt;
|
||||
}
|
||||
|
||||
private static string ComputeHash(string inputPassword, string salt)
|
||||
{
|
||||
var encDataByte = Encoding.UTF8.GetBytes(inputPassword + salt);
|
||||
var encodedData = Convert.ToBase64String(encDataByte);
|
||||
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
||||
encodedData = Convert.ToBase64String(encDataByte);
|
||||
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
||||
encodedData = Convert.ToBase64String(encDataByte);
|
||||
encDataByte = Encoding.UTF8.GetBytes(encodedData);
|
||||
encodedData = Convert.ToBase64String(encDataByte);
|
||||
return encodedData;
|
||||
}
|
||||
|
||||
public async Task<int> ChangePassword(string inputCardNum, string inputOldPassword, string inputNewPassword,
|
||||
string inputConfirmNewPassword, DashboardResponse response, HttpClient client)
|
||||
{
|
||||
if (OnlyAdmin) return 0;
|
||||
foreach (var user in response.Users.Where(user => user.AccessCodes.Contains(inputCardNum)))
|
||||
{
|
||||
foreach (var userCredential in response.UserCredentials.Where(userCredential => userCredential.Baid == user.Baid))
|
||||
{
|
||||
if (userCredential.Password != ComputeHash(inputOldPassword, userCredential.Salt)) return 4;
|
||||
if (inputNewPassword != inputConfirmNewPassword) return 2;
|
||||
var request = new SetPasswordRequest
|
||||
{
|
||||
Baid = user.Baid,
|
||||
Password = ComputeHash(inputNewPassword, userCredential.Salt),
|
||||
Salt = userCredential.Salt
|
||||
};
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Credentials", request);
|
||||
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
||||
}
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
{
|
||||
IsLoggedIn = false;
|
||||
LoggedInUser = new User();
|
||||
IsAdmin = false;
|
||||
OnLoginStatusChanged();
|
||||
}
|
||||
|
||||
public User GetLoggedInUser()
|
||||
{
|
||||
return LoggedInUser;
|
||||
}
|
||||
|
||||
public void ResetLoggedInUser(DashboardResponse? response)
|
||||
{
|
||||
if (response is null) return;
|
||||
var baid = LoggedInUser.Baid;
|
||||
var newLoggedInUser = response.Users.FirstOrDefault(u => u.Baid == baid);
|
||||
if (newLoggedInUser is null) return;
|
||||
LoggedInUser = newLoggedInUser;
|
||||
}
|
||||
|
||||
public async Task<int> BindAccessCode(string inputAccessCode, User user, HttpClient client)
|
||||
{
|
||||
if (inputAccessCode.Trim() == "") return 4; /*Empty access code*/
|
||||
if (!IsLoggedIn && LoginRequired) return 0; /*User not connected and login is required*/
|
||||
if (LoginRequired && !IsAdmin && !(user.Baid == GetLoggedInUser().Baid)) return 5; /*User not admin trying to update someone elses Access Codes*/
|
||||
if (user.AccessCodes.Count >= boundAccessCodeUpperLimit) return 2; /*Limit of codes has been reached*/
|
||||
var request = new BindAccessCodeRequest
|
||||
{
|
||||
AccessCode = inputAccessCode,
|
||||
Baid = user.Baid
|
||||
};
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Cards", request);
|
||||
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
public class Title
|
||||
{
|
||||
public int TitleId { get; set; }
|
||||
public uint TitleId { get; set; }
|
||||
|
||||
public string TitleName { get; init; } = string.Empty;
|
||||
|
||||
|
@ -14,15 +14,17 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autocomplete.Clients" Version="1.1.0" />
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="6.5.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0-rc.1.23421.29" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0-rc.1.23421.29" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0-rc.1.23419.4" />
|
||||
<PackageReference Include="MudBlazor" Version="6.17.0" />
|
||||
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="6.9.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.4" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||
<PackageReference Include="MudBlazor" Version="6.19.1" />
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -66,6 +68,12 @@
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Profile.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Register.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Users.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Dialogs\OTPDialog.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Dialogs\ResetPasswordConfirmDialog.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Login.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\PlayHistory.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Song.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\SongList.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -103,6 +111,15 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Pages\Pages\AccessCode.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\ChangePassword.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\DaniDojo.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\Dashboard.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\Dialogs\AccessCodeDeleteConfirmDialog.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\Dialogs\ChooseTitleDialog.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
7
TaikoWebUI/wwwroot/css/bootstrap/bootstrap.min.css
vendored
Normal file
7
TaikoWebUI/wwwroot/css/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
TaikoWebUI/wwwroot/css/bootstrap/bootstrap.min.css.map
Normal file
1
TaikoWebUI/wwwroot/css/bootstrap/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
@ -14,30 +14,42 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="app">
|
||||
<div class="loader-container">
|
||||
<div class="linear-progress"></div>
|
||||
<div class="loading-text">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="js/textFit.min.js"></script>
|
||||
<script src="js/updateTextFit.js"></script>
|
||||
<script>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="js/textFit.min.js"></script>
|
||||
<script src="js/updateTextFit.js"></script>
|
||||
<script>
|
||||
window.blazorCulture = {
|
||||
get: () => localStorage['BlazorCulture'],
|
||||
set: (value) => localStorage['BlazorCulture'] = value
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
<script>
|
||||
window.clipboardCopy = {
|
||||
copyText: function(text) {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
alert("Copied to clipboard!");
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error("Failed to copy text: ", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user