fix frontend issues with ongeki having too much data (#1607)

* fix frontend issues with ongeki having too much data

* probably better

* I'm so done
This commit is contained in:
nairobi
2026-06-01 01:41:14 +01:00
committed by GitHub
parent 45b07723ee
commit c8feb01723
13 changed files with 102 additions and 37 deletions
@@ -38,6 +38,13 @@ import { type ConfEnumScoreMetric } from "tachi-common/types/metrics";
type SetScores = (scores: ScoreDocument[]) => void;
const Plural = (str: string) => str + (str.trimEnd().endsWith("s") ? "" : "s");
const FormatEnumTitle = (str: string) =>
Plural(UppercaseFirst(str))
.split(/(?=[A-Z])/u)
.join("\u00a0"); // nbsp
export default function SessionRaiseBreakdown({
sessionData,
setScores,
@@ -48,7 +55,9 @@ export default function SessionRaiseBreakdown({
setScores?: SetScores;
}) {
const game = sessionData.session.game;
const lampName = game === "ongeki" || game === "chunithm" ? "noteLamp" : "lamp";
const gameConfig = GetGameConfig(game);
const enumMetrics = GetScoreMetrics(gameConfig, "ENUM");
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[game];
const { user } = useContext(UserContext);
@@ -82,15 +91,21 @@ export default function SessionRaiseBreakdown({
<div className="col-12 col-lg-6 offset-lg-3">
<div className="d-none d-lg-flex justify-content-center">
<div className="btn-group">
<SelectButton id={lampName} setValue={setView} value={view}>
<Icon type="lightbulb" /> Lamps Only
</SelectButton>
<SelectButton id={null} setValue={setView} value={view}>
<Icon type="bolt" /> All
</SelectButton>
<SelectButton id="grade" setValue={setView} value={view}>
<Icon type="sort-alpha-up" /> Grades Only
</SelectButton>
{enumMetrics.map((metric) => (
<SelectButton
id={metric}
key={metric}
setValue={setView}
value={view}
>
{/* @ts-expect-error ctrl+f `enumIcons[` - standard procedure */}
<Icon type={gptImpl.enumIcons[metric] ?? "lightbulb"} />{" "}
{FormatEnumTitle(metric)}
</SelectButton>
))}
</div>
</div>
</div>
@@ -197,10 +212,11 @@ function SessionScoreStatBreakdown({
{enumMetrics.map((metric) => (
<div key={metric} style={{ flex: 1 }}>
<MiniTable
className={enumMetrics.length > 2 ? "no-max-width" : ""}
colSpan={[1, 100]}
headers={[
`${UppercaseFirst(metric)}s`,
`New ${UppercaseFirst(metric)}s`,
FormatEnumTitle(metric),
`New ${FormatEnumTitle(metric)}`,
]}
>
<ElementStatTable
@@ -222,7 +238,7 @@ function SessionScoreStatBreakdown({
<div className="col-12">
<MiniTable
colSpan={[1, 100]}
headers={[`${UppercaseFirst(view)}s`, `New ${UppercaseFirst(view)}s`]}
headers={[`${FormatEnumTitle(view)}`, `New ${FormatEnumTitle(view)}`]}
>
<ElementStatTable
chartMap={chartMap}
@@ -49,7 +49,7 @@ export default function DifficultyCell({
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[game];
if (game === "iidx-sp" || game === "iidx-dp" || game === "maimaidx") {
if (["iidx-dp", "iidx-sp", "maimaidx", "ongeki"].includes(game)) {
alwaysShort = true;
}
@@ -120,7 +120,11 @@ export function DisplayLevelNum({
return null;
}
if (["chunithm", "maimai", "maimaidx", "ongeki", "wacca"].includes(game)) {
if (game === "ongeki" && level === "0") {
return null;
}
if (["arcaea", "chunithm", "maimai", "maimaidx", "ongeki", "wacca"].includes(game)) {
return (
<Muted>
{prefix}
@@ -8,6 +8,14 @@ import { type PBScoreDocument, type ScoreDocument } from "tachi-common";
import MiniTable from "../components/MiniTable";
import LampCell from "./LampCell";
const CoreCell = ({ ratingValue }: { ratingValue: number }) => (
<td>
<div className="underline-on-hover">
{FormatScoreRating("ongeki", "scoreRating", ratingValue)}
</div>
</td>
);
export default function OngekiScoreRatingCell({
score,
}: {
@@ -33,7 +41,12 @@ export default function OngekiScoreRatingCell({
? 0.1
: 0;
const bellRating = bellLamp === "FULL BELL" ? 0.05 : 0;
const techRating = ratingValue - gradeRating - noteRating - bellRating;
const techRating = ratingValue === 0 ? 0 : ratingValue - gradeRating - noteRating - bellRating;
if (techRating <= 0) {
// This value is N/A if techRating isn't positive
return <CoreCell ratingValue={ratingValue} />;
}
return (
<>
@@ -88,11 +101,7 @@ export default function OngekiScoreRatingCell({
}
wide
>
<td>
<div className="underline-on-hover">
{FormatScoreRating("ongeki", "scoreRating", ratingValue)}
</div>
</td>
<CoreCell ratingValue={ratingValue} />
</QuickTooltip>
</>
);
@@ -1,6 +1,7 @@
import { FormatTime, MillisToSince } from "#util/time";
import { IsNullish } from "#util/misc";
import { FormatTime, FormatTimeOfDay, FormatTimeSmall, MillisToSince } from "#util/time";
import React from "react";
import { type integer } from "tachi-common";
import { type integer, type V3Game } from "tachi-common";
/** Same truncation pattern as TitleCell metadata lines. */
const truncLineCls = "d-block w-100 text-truncate";
@@ -9,14 +10,26 @@ export default function TimestampCell({
service,
tableFixedLayoutCompat,
time,
alwaysShort,
game,
}: {
alwaysShort?: boolean;
game?: V3Game;
service?: string | null;
tableFixedLayoutCompat?: boolean;
time: integer | null;
}) {
if (game === "ongeki" && IsNullish(service)) {
alwaysShort = true;
}
const widthStyle = tableFixedLayoutCompat
? undefined
: { maxWidth: "200px", minWidth: "140px", overflow: "hidden" as const };
: {
maxWidth: "200px",
minWidth: alwaysShort ? "50px" : "140px",
overflow: "hidden" as const,
};
return (
<td
@@ -25,10 +38,18 @@ export default function TimestampCell({
>
{time ? (
<>
{MillisToSince(time)}
{MillisToSince(time, alwaysShort)}
<br />
<small className="text-body-secondary">{FormatTime(time)}</small>
<small className="text-body-secondary">
{alwaysShort ? FormatTimeSmall(time) : FormatTime(time)}
</small>
{alwaysShort && (
<>
<br />
<small className="text-body-secondary">{FormatTimeOfDay(time)}</small>
</>
)}
</>
) : (
<span
@@ -44,10 +65,11 @@ export default function TimestampCell({
<br />
<small
className={`${truncLineCls} text-body-secondary`}
style={{ fontSize: "0.75rem", minWidth: 0 }}
style={{ fontSize: "0.75rem", minWidth: 0, whiteSpace: "normal" }}
title={`Played On: ${service}`}
>
Played On: {service}
{!alwaysShort && "Played On: "}
{service}
</small>
</>
)}
@@ -50,6 +50,8 @@ export default function TitleCell({
? tooltipParts.filter(Boolean).join(" — ")
: undefined;
const maxWidth = game === "ongeki" ? TITLE_CELL_WIDTH_PX * 0.6 : TITLE_CELL_WIDTH_PX;
if (game === "popn" && chart) {
backgroundImage = `url(${ToCDNURL(
`/misc/popn/banners/${(chart as any).data.inGameID}.png`,
@@ -71,11 +73,11 @@ export default function TitleCell({
className="fading-image-td-left title-cell-wrapper"
style={{
boxSizing: "border-box",
maxWidth: `${TITLE_CELL_WIDTH_PX}px`,
maxWidth: `${maxWidth}px`,
minWidth: 0,
overflow: "hidden",
textAlign: "left",
width: `${TITLE_CELL_WIDTH_PX}px`,
width: `${maxWidth}px`,
["--image-url" as string]: backgroundImage,
backgroundPosition: center ? "center" : undefined,
}}
@@ -40,8 +40,12 @@ export function ScoreInfo({
<tbody>
<tr>
<ScoreCoreCells chart={chart} game={game} rating={rating} score={score} />
{/* @ts-expect-error yeah we know service doesnt necessarily exist */}
<TimestampCell service={score?.service} time={score.timeAchieved} />
<TimestampCell
game={game}
/* @ts-expect-error yeah we know service doesnt necessarily exist */
service={score?.service}
time={score.timeAchieved}
/>
</tr>
</tbody>
</table>
@@ -211,7 +211,7 @@ const RowInner = memo(
rankingViewMode={rankingViewMode}
userID={score.userID}
/>
<TimestampCell time={score.timeAchieved} />
<TimestampCell game={game} time={score.timeAchieved} />
</DropdownRow>
);
},
@@ -80,7 +80,7 @@ function Row({
nested
>
<ScoreCoreCells chart={chart} game={game} rating={rating as any} score={sc} />
<TimestampCell service={sc.service} time={sc.timeAchieved} />
<TimestampCell game={game} service={sc.service} time={sc.timeAchieved} />
<DropdownIndicatorCell />
</DropdownRow>
);
@@ -137,7 +137,7 @@ function Row({
rankingViewMode={rankingViewMode}
userID={pb.userID}
/>
<TimestampCell time={pb.timeAchieved} />
<TimestampCell game={game} time={pb.timeAchieved} />
{showPlaycount && <td>{pb.__playcount ?? 0}</td>}
<DropdownIndicatorCell />
</DropdownRow>
@@ -110,7 +110,7 @@ function Row({
rankingViewMode={rankingViewMode}
userID={pb.userID}
/>
<TimestampCell time={pb.timeAchieved} />
<TimestampCell game={game} time={pb.timeAchieved} />
</DropdownRow>
);
}
@@ -119,7 +119,7 @@ function Row({
song={sc.__related.song}
/>
<ScoreCoreCells chart={sc.__related.chart} game={game} rating={rating} score={sc} />
<TimestampCell service={sc.service} time={sc.timeAchieved} />
<TimestampCell game={game} service={sc.service} time={sc.timeAchieved} />
<DropdownIndicatorCell />
</DropdownRow>
);
@@ -24,6 +24,8 @@
.session-raise-container {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
}
@include media-breakpoint-down(md) {
@@ -34,6 +36,12 @@
}
}
@include media-breakpoint-up(lg) {
.session-raise-container .no-max-width {
max-width: 400px;
}
}
.MuiTabs-scrollButtons.Mui-disabled {
opacity: 0.3;
}
+3 -3
View File
@@ -1,8 +1,8 @@
import humaniseDuration from "humanize-duration";
import { DateTime } from "luxon";
export function MillisToSince(ms: number) {
return DateTime.fromMillis(ms).toRelative();
export function MillisToSince(ms: number, short?: boolean) {
return DateTime.fromMillis(ms).toRelative({ style: short ? "narrow" : "long" });
}
export function FormatTime(ms: number) {
@@ -33,5 +33,5 @@ export function FormatDurationHours(ms: number) {
}
export function FormatTimeSmall(ms: number) {
return DateTime.fromMillis(ms).toLocaleString(DateTime.DATE_SHORT);
return DateTime.fromMillis(ms).toISODate();
}