mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-09-22 22:38:14 +03:00
feat: display ongeki refresh data on web ui
Co-authored-by: Azalea <noreply@aza.moe>
This commit is contained in:
@@ -16,19 +16,25 @@
|
||||
|
||||
if (top) split = split?.toSorted((a, b) => b.score - a.score).slice(0, top)
|
||||
if (split) console.log("Split", split)
|
||||
|
||||
let mustResize = split && split.length > 12
|
||||
</script>
|
||||
|
||||
{#if split && comp}
|
||||
<div>
|
||||
<h2>{title}</h2>
|
||||
<div class="rating-composition">
|
||||
{#each split as p}
|
||||
<div>
|
||||
<RatingCompSong {p} {game}/>
|
||||
</div>
|
||||
{/each}
|
||||
{#if split && split.length > 0 && comp}
|
||||
<div class="rating-composition-container" class:must-resize={mustResize}>
|
||||
{#if mustResize}
|
||||
<input type="checkbox">
|
||||
{/if}
|
||||
|
||||
<h2>{title}</h2>
|
||||
<div class="rating-composition">
|
||||
{#each split as p}
|
||||
<div>
|
||||
<RatingCompSong {p} {game}/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@@ -39,4 +45,43 @@
|
||||
// 3 columns
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr))
|
||||
gap: vars.$gap
|
||||
|
||||
overflow: hidden
|
||||
transition: max-height 250ms
|
||||
|
||||
.rating-composition-container
|
||||
position: relative
|
||||
|
||||
input
|
||||
width: 100%
|
||||
height: 100%
|
||||
position: absolute
|
||||
z-index: 10
|
||||
|
||||
border: none
|
||||
border-radius: 0
|
||||
opacity: 0
|
||||
|
||||
transition: opacity 250ms
|
||||
|
||||
.rating-composition-container.must-resize
|
||||
input
|
||||
& ~ .rating-composition
|
||||
max-height: 250px
|
||||
|
||||
& ~ h2::after
|
||||
content: "▾"
|
||||
margin-left: 0.5em
|
||||
&:checked ~ h2::after
|
||||
content: "▴"
|
||||
|
||||
&:checked
|
||||
opacity: 0
|
||||
&:checked ~ .rating-composition
|
||||
max-height: 3000px
|
||||
|
||||
opacity: 1
|
||||
background: linear-gradient(#0000 250px, rgb(28.35, 28.35, 28.35) 100%) /* for some reason the color.adjust doesn't work here so whatever */
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -79,7 +79,8 @@ export interface GenericRanking {
|
||||
rating: number
|
||||
fullCombo: number
|
||||
allPerfect: number
|
||||
lastSeen: string
|
||||
lastSeen: string,
|
||||
modernRating: number
|
||||
}
|
||||
|
||||
export interface RankCount {
|
||||
|
||||
@@ -2,6 +2,8 @@ export const EN_REF_USER = {
|
||||
'UserHome.ServerRank': 'Server Rank',
|
||||
'UserHome.DXRating': 'DX Rating',
|
||||
'UserHome.Rating': 'Rating',
|
||||
'UserHome.LegacyRating': 'Legacy Rating',
|
||||
'UserHome.ModernRating': 'Modern Rating',
|
||||
'UserHome.Statistics': 'Statistics',
|
||||
'UserHome.Accuracy': 'Accuracy',
|
||||
'UserHome.MaxCombo': 'Max Combo',
|
||||
@@ -24,6 +26,16 @@ export const EN_REF_USER = {
|
||||
'UserHome.RankDetail.Title': 'Achievement Details',
|
||||
'UserHome.RankDetail.Level': 'Level',
|
||||
'UserHome.B50': 'B50',
|
||||
'UserHome.RatingComposition.Best50': 'Best 50',
|
||||
'UserHome.RatingComposition.Best35': 'Best 35',
|
||||
'UserHome.RatingComposition.Best30': 'Best 30',
|
||||
'UserHome.RatingComposition.Best15': 'Best 15',
|
||||
'UserHome.RatingComposition.New15': 'New 15',
|
||||
'UserHome.RatingComposition.New10': 'New 10',
|
||||
'UserHome.RatingComposition.Best25Candidates': 'Best 25 (Candidates)',
|
||||
'UserHome.RatingComposition.Platinum': 'Platinum',
|
||||
'UserHome.RatingComposition.New20': 'New 20',
|
||||
'UserHome.RatingComposition.Recent10': 'Recent 10',
|
||||
'UserHome.AddRival': "Add to Rival",
|
||||
'UserHome.RemoveRival': "Remove from Rival",
|
||||
'UserHome.InvalidGame': "Game ${game} is not supported on the web UI yet. We only support maimai, chunithm, wacca, and ongeki for now.",
|
||||
@@ -67,6 +79,8 @@ export const EN_REF_LEADERBOARD = {
|
||||
'Leaderboard.Title': 'Server Leaderboard',
|
||||
'Leaderboard.Rank': 'Rank',
|
||||
'Leaderboard.Rating': 'Rating',
|
||||
'Leaderboard.LegacyRating': 'Legacy Rating',
|
||||
'Leaderboard.ModernRating': 'Modern Rating',
|
||||
'Leaderboard.Accuracy': 'Accuracy',
|
||||
'Leaderboard.FC': 'FC',
|
||||
'Leaderboard.AP': 'AP',
|
||||
|
||||
@@ -15,6 +15,8 @@ const zhUser: typeof EN_REF_USER = {
|
||||
'UserHome.ServerRank': '服务器排名',
|
||||
'UserHome.DXRating': 'DX B50',
|
||||
'UserHome.Rating': '评分',
|
||||
'UserHome.LegacyRating': '旧评分',
|
||||
'UserHome.ModernRating': '新评分',
|
||||
'UserHome.Statistics': '统计数据',
|
||||
'UserHome.Accuracy': '准确率',
|
||||
'UserHome.MaxCombo': '最大连击',
|
||||
@@ -37,6 +39,16 @@ const zhUser: typeof EN_REF_USER = {
|
||||
'UserHome.RankDetail.Title': '评分详细',
|
||||
'UserHome.RankDetail.Level': "等级",
|
||||
'UserHome.B50': "B50",
|
||||
'UserHome.RatingComposition.Best50': '最佳 50',
|
||||
'UserHome.RatingComposition.Best35': '最佳 35',
|
||||
'UserHome.RatingComposition.Best30': '最佳 30',
|
||||
'UserHome.RatingComposition.Best15': '最佳 15',
|
||||
'UserHome.RatingComposition.New15': '新曲 15',
|
||||
'UserHome.RatingComposition.New10': '新曲 10',
|
||||
'UserHome.RatingComposition.Best25Candidates': '最佳 25(候选)',
|
||||
'UserHome.RatingComposition.Platinum': '白金分',
|
||||
'UserHome.RatingComposition.New20': '新曲 20',
|
||||
'UserHome.RatingComposition.Recent10': '最近 10',
|
||||
'UserHome.AddRival': "添加劲敌",
|
||||
'UserHome.RemoveRival': "移除劲敌",
|
||||
'UserHome.InvalidGame': "游戏 ${game} 还不支持网页端查看。我们目前只支持舞萌、中二、华卡和音击。",
|
||||
@@ -73,12 +85,15 @@ const zhWelcome: typeof EN_REF_Welcome = {
|
||||
'welcome.verified': '您的邮箱已经验证成功!您现在可以登录了',
|
||||
'welcome.verification-failed': '验证失败:${message}。请重试',
|
||||
'welcome.password-reset-done': '您的密码已更新!请重新登录',
|
||||
'welcome.login_link': '登录或注册以绑定卡片。',
|
||||
}
|
||||
|
||||
const zhLeaderboard: typeof EN_REF_LEADERBOARD = {
|
||||
'Leaderboard.Title': '排行榜',
|
||||
'Leaderboard.Rank': '排名',
|
||||
'Leaderboard.Rating': '评分',
|
||||
'Leaderboard.LegacyRating': '旧评分',
|
||||
'Leaderboard.ModernRating': '新评分',
|
||||
'Leaderboard.Accuracy': '准确率',
|
||||
'Leaderboard.FC': 'FC',
|
||||
'Leaderboard.AP': 'AP',
|
||||
|
||||
@@ -22,3 +22,8 @@ export interface OngekiUserSummaryEntry {
|
||||
recent10: string
|
||||
recent: GenericGamePlaylog[]
|
||||
}
|
||||
|
||||
export interface OngekiRefreshData {
|
||||
playerRating: number,
|
||||
highestRating: number
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
Dict,
|
||||
GameUserOption
|
||||
} from './generalTypes'
|
||||
import type { OngekiRefreshData } from './ongekiTypes'
|
||||
import type { GameName } from './scoring'
|
||||
|
||||
interface ExtReqInit extends RequestInit {
|
||||
@@ -244,6 +245,8 @@ export const GAME = {
|
||||
post(`/api/v2/game/${game}/import-music-detail`, {}, { json: data }),
|
||||
setRival: (game: GameName, rivalUserName: string, isAdd: boolean) =>
|
||||
post(`/api/v2/game/${game}/set-rival`, { rivalUserName, isAdd }),
|
||||
refreshData: (username: string): Promise<OngekiRefreshData> =>
|
||||
post(`/api/v2/game/ongeki/refresh-data`, { username })
|
||||
}
|
||||
|
||||
export const DATA = {
|
||||
|
||||
@@ -52,7 +52,12 @@
|
||||
<div class="lb-user" on:mouseenter={() => hoveringUser = ""} role="heading" aria-level="2">
|
||||
<span class="rank">{t("Leaderboard.Rank")}</span>
|
||||
<span class="name"></span>
|
||||
<span class="rating">{t("Leaderboard.Rating")}</span>
|
||||
{#if game == "ongeki"}
|
||||
<span class="rating modern-rating">{t("Leaderboard.ModernRating")}</span>
|
||||
<span class="rating legacy-rating">{t("Leaderboard.LegacyRating")}</span>
|
||||
{:else}
|
||||
<span class="rating">{t("Leaderboard.Rating")}</span>
|
||||
{/if}
|
||||
<span class="accuracy">{t("Leaderboard.Accuracy")}</span>
|
||||
<span class="fc">{t("Leaderboard.FC")}</span>
|
||||
<span class="ap">{t("Leaderboard.AP")}</span>
|
||||
@@ -70,11 +75,23 @@
|
||||
<span>{user.name}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="rating">{
|
||||
game === 'chu3' || game === 'ongeki' ?
|
||||
(user.rating / 100).toFixed(2) :
|
||||
user.rating.toLocaleString()
|
||||
}</span>
|
||||
{#if game == 'ongeki'}
|
||||
{#if user.modernRating > 0}
|
||||
<span class="rating modern-rating">{
|
||||
user.modernRating.toLocaleString()
|
||||
}</span>
|
||||
{/if}
|
||||
<span class="rating legacy-rating">{
|
||||
(user.rating / 100).toFixed(2)
|
||||
}</span>
|
||||
{:else}
|
||||
<span class="rating">{
|
||||
game === 'chu3' ?
|
||||
(user.rating / 100).toFixed(2) :
|
||||
user.rating.toLocaleString()
|
||||
}</span>
|
||||
{/if}
|
||||
|
||||
<span class="accuracy">{(+user.accuracy).toFixed(2)}%</span>
|
||||
<span class="fc">{user.fullCombo}</span>
|
||||
<span class="ap">{user.allPerfect}</span>
|
||||
@@ -141,6 +158,14 @@
|
||||
|
||||
.accuracy
|
||||
display: none
|
||||
.modern-rating
|
||||
display: none
|
||||
.legacy-rating
|
||||
/* this is officially the worst css i've ever written dear god */
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: clip
|
||||
min-width: 3.5em !important
|
||||
|
||||
&.alternate
|
||||
background-color: vars.$ov-light
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import useLocalStorage from "../libs/hooks/useLocalStorage.svelte";
|
||||
import Line from "../components/chart/Line.svelte";
|
||||
import ChuniUserboxDisplay from "../components/settings/userbox/ChuniUserboxDisplay.svelte";
|
||||
import type { OngekiRefreshData } from "../libs/ongekiTypes";
|
||||
|
||||
const TREND_DAYS = 60
|
||||
|
||||
@@ -51,6 +52,8 @@
|
||||
let isLoading = false
|
||||
let showMoreRecent = false
|
||||
|
||||
let ongekiData: OngekiRefreshData
|
||||
|
||||
function init() {
|
||||
USER.isLoggedIn() && USER.me().then(u => me = u)
|
||||
|
||||
@@ -77,7 +80,7 @@
|
||||
GAME.userSummary(username, game),
|
||||
GAME.trend(username, game),
|
||||
DATA.allMusic(game),
|
||||
]).then(([user, trend, music]) => {
|
||||
]).then(async ([user, trend, music]) => {
|
||||
console.log(user)
|
||||
console.log(trend)
|
||||
console.log(games)
|
||||
@@ -92,6 +95,11 @@
|
||||
})
|
||||
}
|
||||
|
||||
if (game == 'ongeki') {
|
||||
let ogkData = await GAME.refreshData(username).catch(err => {});
|
||||
if(ogkData) ongekiData = ogkData
|
||||
}
|
||||
|
||||
// Set beforeRating in recent to the last play's afterRating
|
||||
user.recent.forEach((it, i) => {
|
||||
if (i < user.recent.length - 1) {
|
||||
@@ -187,14 +195,28 @@
|
||||
<div class="scoring-info">
|
||||
<div class="chart">
|
||||
<div class="info-top">
|
||||
<div class="rating">
|
||||
<span>{game === 'mai2' ? t("UserHome.DXRating"): t("UserHome.Rating")}</span>
|
||||
<span>{
|
||||
game === 'chu3' || game === 'ongeki' ?
|
||||
(d.user.rating / 100).toFixed(2) :
|
||||
d.user.rating.toLocaleString()
|
||||
}</span>
|
||||
</div>
|
||||
|
||||
|
||||
{#if game == "ongeki" && ongekiData}
|
||||
<!-- display modern (refresh) and legacy rating -->
|
||||
<div class="rating">
|
||||
<span>{t("UserHome.ModernRating")}</span>
|
||||
<span>{ongekiData.playerRating.toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="rating">
|
||||
<span>{t("UserHome.LegacyRating")}</span>
|
||||
<span>{(d.user.rating / 100).toFixed(2)}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="rating">
|
||||
<span>{game === 'mai2' ? t("UserHome.DXRating"): t("UserHome.Rating")}</span>
|
||||
<span>{
|
||||
game === 'chu3' ?
|
||||
(d.user.rating / 100).toFixed(2) :
|
||||
d.user.rating.toLocaleString()
|
||||
}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="rank">
|
||||
<span>{t('UserHome.ServerRank')}</span>
|
||||
@@ -306,18 +328,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- I don't like doing this but it may be preferable to gaslighting the types -->
|
||||
<RatingComposition title={t("UserHome.RatingComposition.Best50")} comp={d.user.ratingComposition.best50} {allMusics} {game}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.Best35")} comp={d.user.ratingComposition.best35} {allMusics} {game}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.Best30")} comp={d.user.ratingComposition.best30} {allMusics} {game}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.Best15")} comp={d.user.ratingComposition.best15} {allMusics} {game}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.New15")} comp={d.user.ratingComposition.new15} {allMusics} {game}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.New10")} comp={d.user.ratingComposition.new10} {allMusics} {game}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.Best25Candidates")} comp={d.user.ratingComposition.best25_candidates} {allMusics} {game}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.Platinum")} comp={d.user.ratingComposition.pscore} {allMusics} {game}/>
|
||||
|
||||
<RatingComposition title="B30" comp={d.user.ratingComposition.best30} {allMusics} {game}/>
|
||||
<RatingComposition title="B35" comp={d.user.ratingComposition.best35} {allMusics} {game}/>
|
||||
<RatingComposition title="B15" comp={d.user.ratingComposition.best15} {allMusics} {game}/>
|
||||
<!-- <RatingComposition title="Hot 10" comp={d.user.ratingComposition.hot10} {allMusics} {game}/> -->
|
||||
<!-- <RatingComposition title="N10" comp={d.user.ratingComposition.next10} {allMusics} {game}/> -->
|
||||
<!-- Chuni -->
|
||||
{#if d.user.ratingComposition.new}
|
||||
<RatingComposition title="New 20" comp={d.user.ratingComposition.new} {allMusics} game="chu3"/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.New20")} comp={d.user.ratingComposition.new} {allMusics} game="chu3"/>
|
||||
{:else}
|
||||
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} {game} top={10}/>
|
||||
<RatingComposition title={t("UserHome.RatingComposition.Recent10")} comp={d.user.ratingComposition.recent10} {allMusics} {game} top={10}/>
|
||||
{/if}
|
||||
|
||||
<div class="recent">
|
||||
|
||||
@@ -57,7 +57,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
val time = millis()
|
||||
|
||||
// Check cache validity
|
||||
if (rankingCache.isEmpty()) (500 - "Rank is empty or is currently computing. Please come back later.")
|
||||
if (rankingCache.isEmpty()) (500 - "Rank is empty or is currently computing.")
|
||||
|
||||
val reqUser = token?.let { us.jwt.auth(it) }?.let { u ->
|
||||
// Optimization: If the user is not banned, we don't need to process user information
|
||||
@@ -98,7 +98,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
SUM(p.is_full_combo) AS fc,
|
||||
SUM(p.is_all_perfect) AS ap,
|
||||
c.ranking_banned or a.opt_out_of_leaderboard or c.status = 12 AS hide,
|
||||
a.username
|
||||
a.username ${if (name == "ongeki") ", u.new_player_rating" else ""}
|
||||
FROM ${tableName}_user_playlog_view p
|
||||
JOIN ${tableName}_user_data_view u ON p.user_id = u.id
|
||||
JOIN sega_card c ON u.aime_card_id = c.id
|
||||
@@ -115,7 +115,8 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
accuracy = it[4]!!.double,
|
||||
fullCombo = it[5]!!.int,
|
||||
allPerfect = it[6]!!.int,
|
||||
username = it[8]?.toString() ?: "user${it[0]}"
|
||||
username = it[8]?.toString() ?: "user${it[0]}",
|
||||
modernRating = if (name == "ongeki") it[9]?.int() ?: 0 else 0
|
||||
)
|
||||
}
|
||||
rankingSortedIndex = rankingCache.filter { !it.l }.map { it.r.rating }.reversed()
|
||||
|
||||
@@ -59,7 +59,8 @@ data class GenericRankingPlayer(
|
||||
val rating: Int,
|
||||
val allPerfect: Int,
|
||||
val fullCombo: Int,
|
||||
val lastSeen: String
|
||||
val lastSeen: String,
|
||||
val modernRating: Int = 0
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -60,7 +60,7 @@ class Maimai2(
|
||||
|
||||
val ratingComposition = mapOf(
|
||||
"best35" to (extra["recent_rating"] ?: ""),
|
||||
"best15" to (extra["recent_rating_new"] ?: "")
|
||||
"new15" to (extra["recent_rating_new"] ?: "")
|
||||
)
|
||||
|
||||
// if isLogin than boolean or null
|
||||
|
||||
@@ -42,16 +42,35 @@ class Ongeki(
|
||||
) }
|
||||
|
||||
override suspend fun userSummary(username: String, token: String?) = us.cardByName(username) { card ->
|
||||
val user = userDataRepo.findByCard_ExtId(card.extId) ?: (404 - "User not found")
|
||||
val extra = userGeneralDataRepository.findByUser_Card_ExtId(card.extId)
|
||||
.associate { it.propertyKey to it.propertyValue }
|
||||
|
||||
val ratingComposition = mapOf(
|
||||
"best30" to (extra["rating_base_best"] ?: ""),
|
||||
"best15" to (extra["rating_base_new_best"] ?: ""),
|
||||
"recent10" to (extra["rating_base_hot_best"] ?: "")
|
||||
)
|
||||
val isRefresh = user.newHighestRating > 0
|
||||
if (isRefresh)
|
||||
genericUserSummary(card, mapOf(
|
||||
"best50" to (extra["new_rating_base_best"] ?: ""),
|
||||
"new10" to (extra["new_rating_base_new_best"] ?: ""),
|
||||
"best25_candidates" to (extra["new_rating_base_next_best"] ?: ""),
|
||||
"pscore" to (extra["new_rating_base_pscore"] ?: "")
|
||||
))
|
||||
else
|
||||
genericUserSummary(card, mapOf(
|
||||
"best30" to (extra["rating_base_best"] ?: ""),
|
||||
"new15" to (extra["rating_base_new_best"] ?: ""),
|
||||
"recent10" to (extra["rating_base_hot_best"] ?: "")
|
||||
))
|
||||
}
|
||||
|
||||
genericUserSummary(card, ratingComposition)
|
||||
@API("refresh-data")
|
||||
suspend fun refreshData(@RP username: String) = us.cardByName(username) { card ->
|
||||
val user = userDataRepo.findByCard_ExtId(card.extId) ?: (404 - "User not found")
|
||||
if (user.newHighestRating > 0)
|
||||
mapOf(
|
||||
"playerRating" to user.newPlayerRating,
|
||||
"highestRating" to user.newHighestRating
|
||||
)
|
||||
else (400 - "User has not played Refresh")
|
||||
}
|
||||
|
||||
@API("user-option")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
ALTER TABLE ongeki_user_regions
|
||||
DROP FOREIGN KEY IF EXISTS fk_ongekiregions_on_ongeki_user_data;
|
||||
|
||||
ALTER TABLE ongeki_user_regions
|
||||
DROP FOREIGN KEY IF EXISTS fk_ongekiregions_on_aqua_net_user;
|
||||
|
||||
ALTER TABLE ongeki_user_regions
|
||||
ADD CONSTRAINT fk_ongekiregions_on_ongeki_user_data
|
||||
FOREIGN KEY (user_id) REFERENCES ongeki_user_data (id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
DROP VIEW IF EXISTS ongeki_user_data_view;
|
||||
CREATE VIEW ongeki_user_data_view AS
|
||||
SELECT * FROM ongeki_user_data;
|
||||
Reference in New Issue
Block a user