feat: display ongeki refresh data on web ui

Co-authored-by: Azalea <noreply@aza.moe>
This commit is contained in:
Raymond
2026-06-07 21:05:09 -04:00
committed by GitHub
co-authored by Azalea
parent d5d50d687c
commit 237ba1464a
13 changed files with 212 additions and 45 deletions
+55 -10
View File
@@ -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>
+2 -1
View File
@@ -79,7 +79,8 @@ export interface GenericRanking {
rating: number
fullCombo: number
allPerfect: number
lastSeen: string
lastSeen: string,
modernRating: number
}
export interface RankCount {
+14
View File
@@ -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
View File
@@ -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',
+5
View File
@@ -22,3 +22,8 @@ export interface OngekiUserSummaryEntry {
recent10: string
recent: GenericGamePlaylog[]
}
export interface OngekiRefreshData {
playerRating: number,
highestRating: number
}
+3
View File
@@ -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 = {
+31 -6
View File
@@ -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
+41 -17
View File
@@ -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;