diff --git a/typescript/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx b/typescript/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx index c13adb59b..3e80e98c9 100644 --- a/typescript/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx +++ b/typescript/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx @@ -36,7 +36,7 @@ import { useQuery } from "react-query"; import { Link, Route, Switch } from "react-router-dom"; import { type ChartDocument, - FormatDifficulty, + FormatDifficultyLong, GameToGameGroup, GetGameGroupConfig, type integer, @@ -54,7 +54,7 @@ export default function GPTChartPage({ song: SongDocument; } & GamePT) { const formatSongTitle = `${song.artist} - ${song.title}`; - const formatDiff = chart ? FormatDifficulty(chart) : "Loading..."; + const formatDiff = chart ? FormatDifficultyLong(chart) : "Loading..."; useSetSubheader( [ diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/EvolutionMilestoneFeedRow.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/EvolutionMilestoneFeedRow.tsx index 6ec4653e1..532dcd4c2 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/EvolutionMilestoneFeedRow.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/EvolutionMilestoneFeedRow.tsx @@ -54,7 +54,9 @@ export default function EvolutionMilestoneFeedRow({ ) : chart ? ( <> - Unknown chart + + Unknown chart + ({FormatDifficultyShort(chart)}) diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx index 036b21793..d81c4b22c 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumDistributionBreakdown.tsx @@ -14,17 +14,12 @@ function distributionRowChrome(enumColour: string | undefined): { fillStrong: string | undefined; trackBg: string; } { - const fallbackTrack = - "color-mix(in srgb, var(--bs-secondary) 14%, transparent)"; - const fallbackFillStrong = - "color-mix(in srgb, var(--bs-secondary) 42%, transparent)"; + const fallbackTrack = "color-mix(in srgb, var(--bs-secondary) 14%, transparent)"; + const fallbackFillStrong = "color-mix(in srgb, var(--bs-secondary) 42%, transparent)"; const borderLeftColor = enumColour ?? "var(--bs-secondary)"; - if ( - enumColour && - (enumColour.startsWith("#") || enumColour.startsWith("rgb")) - ) { + if (enumColour && (enumColour.startsWith("#") || enumColour.startsWith("rgb"))) { try { return { borderLeftColor: enumColour, @@ -97,8 +92,7 @@ export default function FolderEnumDistributionBreakdown({ const bucket = stats.stats[enumMetric] ?? {}; const minRelIx = conf.values.indexOf(conf.minimumRelevantValue); - const valueIndexFloor = - clipToMinimumRelevance && minRelIx !== -1 ? minRelIx : 0; + const valueIndexFloor = clipToMinimumRelevance && minRelIx !== -1 ? minRelIx : 0; if (total === 0) { return No charts in this folder.; @@ -115,13 +109,10 @@ export default function FolderEnumDistributionBreakdown({ const pct = total > 0 ? (100 * count) / total : 0; const chipFill = colours?.[label]; const chrome = distributionRowChrome(chipFill); - const pctWidth = - pct > 0 ? `${ToFixedFloor(Math.min(100, Math.max(0, pct)), 3)}%` : "0%"; + const pctWidth = pct > 0 ? `${ToFixedFloor(Math.min(100, Math.max(0, pct)), 3)}%` : "0%"; - const clickableRow = - Boolean(onEnumBreakdownRowClick && isLg && count > 0); - const rowHoverChromeClass = - clickableRow ? breakdownStyles.enumRowInteractive : ""; + const clickableRow = Boolean(onEnumBreakdownRowClick && isLg && count > 0); + const rowHoverChromeClass = clickableRow ? breakdownStyles.enumRowInteractive : ""; const rowShellStyle = { backgroundColor: chrome.trackBg, @@ -201,9 +192,19 @@ export default function FolderEnumDistributionBreakdown({ const remainderPct = total > 0 ? (100 * remainder) / total : 0; return ( -
+
{onActivate ? ( diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx index b2c8ae3cc..d657a3542 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderEnumProgressBar.tsx @@ -65,8 +65,7 @@ export default function FolderEnumProgressBar({ const bucket = stats.stats[enumMetric] ?? {}; const minRelIx = conf.values.indexOf(conf.minimumRelevantValue); - const valueIndexFloor = - clipToMinimumRelevance && minRelIx !== -1 ? minRelIx : 0; + const valueIndexFloor = clipToMinimumRelevance && minRelIx !== -1 ? minRelIx : 0; const segments: Array<{ count: number; label: string; rawFill: string }> = []; let filled = 0; @@ -136,18 +135,18 @@ export default function FolderEnumProgressBar({ } > -
+
{showInlinePct && ( {inlinePctText} diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx index f54995655..b82402467 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx @@ -37,7 +37,15 @@ import React, { useState, } from "react"; import { Col, Collapse, Form, Row } from "react-bootstrap"; -import { Link, Redirect, Route, Switch, useHistory, useLocation, useParams } from "react-router-dom"; +import { + Link, + Redirect, + Route, + Switch, + useHistory, + useLocation, + useParams, +} from "react-router-dom"; import { COLOUR_SET, EnumIndexToValue, @@ -107,7 +115,9 @@ export default function SpecificFolderPage({ reqUser, game }: Props) { queueMicrotask(() => { const anchor = document.getElementById(FOLDER_FOLDER_TABLE_SCROLL_INTO_VIEW_ID); - if (!anchor) return; + if (!anchor) { + return; + } const y = anchor.getBoundingClientRect().top + diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx index d3694c4bb..8890c0830 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderList.tsx @@ -4,10 +4,7 @@ import { GPT_CLIENT_IMPLEMENTATIONS } from "#lib/game-implementations"; import { APIFetchV1 } from "#util/api"; import React, { useContext, useMemo } from "react"; import { Link } from "react-router-dom"; -import { - GetGameConfig, - type TableDocument, -} from "tachi-common"; +import { GetGameConfig, type TableDocument } from "tachi-common"; import FolderEnumProgressBar from "./FolderEnumProgressBar"; import folderTableStyles from "./FolderTablePage.module.scss"; diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx index 26067b137..40ed1ad3f 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/TableFolderViewer.tsx @@ -59,10 +59,7 @@ export default function TableFolderViewer({ const gameConfig = useMemo(() => GetGameConfig(game), [game]); - const evolutionScope = useMemo( - () => ({ kind: "table" as const, table }), - [table], - ); + const evolutionScope = useMemo(() => ({ kind: "table" as const, table }), [table]); if (error) { return ; diff --git a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts index 7083a64e1..18817a1de 100644 --- a/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts +++ b/typescript/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/folderTableShared.ts @@ -1,11 +1,9 @@ -import type { - FolderStatsInfo, - TableEvolutionEventAPI, -} from "#types/api-returns"; +import type { FolderStatsInfo, TableEvolutionEventAPI } from "#types/api-returns"; + import { - GetScoreMetricConf, type FolderDocument, type GameConfig, + GetScoreMetricConf, type integer, type TableDocument, type UserDocument, diff --git a/typescript/client/src/app/routes/GameRoutes.tsx b/typescript/client/src/app/routes/GameRoutes.tsx index 715959d41..38b47f42b 100644 --- a/typescript/client/src/app/routes/GameRoutes.tsx +++ b/typescript/client/src/app/routes/GameRoutes.tsx @@ -38,7 +38,7 @@ import { Redirect, Route, Switch, useParams } from "react-router-dom"; import { type ChartDocument, COLOUR_SET, - FormatDifficulty, + FormatDifficultyLong, type GamesForGroup, GameToGameGroup, GetGameConfig, @@ -472,7 +472,7 @@ function DifficultyButton({ : undefined, }} > - {FormatDifficulty(chart)} + {FormatDifficultyLong(chart)} {chart.isPrimary ? ( "" ) : ( diff --git a/typescript/client/src/components/game/folder/FolderInfoHeader.tsx b/typescript/client/src/components/game/folder/FolderInfoHeader.tsx index 3b4625fe4..4b12885f7 100644 --- a/typescript/client/src/components/game/folder/FolderInfoHeader.tsx +++ b/typescript/client/src/components/game/folder/FolderInfoHeader.tsx @@ -69,7 +69,10 @@ export default function FolderInfoHeader({ onEnumBreakdownRowClick={ onBreakdownEnumValueClick ? (enumValueLabel: string) => { - onBreakdownEnumValueClick(metric, enumValueLabel); + onBreakdownEnumValueClick( + metric, + enumValueLabel, + ); } : undefined } diff --git a/typescript/client/src/components/game/folder/FolderMinimap.tsx b/typescript/client/src/components/game/folder/FolderMinimap.tsx index 03c130ff8..f81c70e29 100644 --- a/typescript/client/src/components/game/folder/FolderMinimap.tsx +++ b/typescript/client/src/components/game/folder/FolderMinimap.tsx @@ -125,11 +125,14 @@ function FolderMinimapMain({ // Switchboard tabs / unmount can leave portalled Bootstrap tooltips in while React // state still has show=true; drop those nodes so nothing sticks. - useLayoutEffect(() => () => { + useLayoutEffect( + () => () => { document.querySelectorAll(".tooltip-folder-minimap").forEach((el) => { el.remove(); }); - }, [enumMetric]); + }, + [enumMetric], + ); return (
diff --git a/typescript/client/src/components/game/songs/DefaultSongChartInfoFormat.tsx b/typescript/client/src/components/game/songs/DefaultSongChartInfoFormat.tsx index fe7cc0aff..fdcbd2b06 100644 --- a/typescript/client/src/components/game/songs/DefaultSongChartInfoFormat.tsx +++ b/typescript/client/src/components/game/songs/DefaultSongChartInfoFormat.tsx @@ -1,6 +1,11 @@ import { DisplayLevelNum } from "#components/tables/cells/DifficultyCell"; import React from "react"; -import { type ChartDocument, FormatDifficulty, type SongDocument, type V3Game } from "tachi-common"; +import { + type ChartDocument, + FormatDifficultyLong, + type SongDocument, + type V3Game, +} from "tachi-common"; export default function DefaultSongChartInfoFormat({ song, @@ -18,7 +23,7 @@ export default function DefaultSongChartInfoFormat({ {chart && ( <> -
({FormatDifficulty(chart)})
+
({FormatDifficultyLong(chart)})
0; return hasLevel ? FormatTables(chart.data.tableFolders) : "No Level"; } - return FormatDifficulty(chart); + return FormatDifficultyLong(chart); } diff --git a/typescript/client/src/components/tables/cells/DifficultyCell.tsx b/typescript/client/src/components/tables/cells/DifficultyCell.tsx index bd2cf1399..43db68991 100644 --- a/typescript/client/src/components/tables/cells/DifficultyCell.tsx +++ b/typescript/client/src/components/tables/cells/DifficultyCell.tsx @@ -6,7 +6,7 @@ import { ChangeOpacity } from "#util/color-opacity"; import React from "react"; import { type ChartDocument, - FormatDifficulty, + FormatDifficultyLong, FormatDifficultyShort, type V3Game, } from "tachi-common"; @@ -59,7 +59,9 @@ export default function DifficultyCell({ ? DIFFICULTY_CELL_WIDTH_PX + 16 : DIFFICULTY_CELL_WIDTH_PX; - const nativeTitleTooltip = [FormatDifficulty(chart), chart.level].filter(Boolean).join(" — "); + const nativeTitleTooltip = [FormatDifficultyLong(chart), chart.level] + .filter(Boolean) + .join(" — "); return ( - {FormatDifficulty(chart)} + {FormatDifficultyLong(chart)}
)} diff --git a/typescript/client/src/components/tables/components/TachiTable.tsx b/typescript/client/src/components/tables/components/TachiTable.tsx index 3c46867f2..2ae235bbc 100644 --- a/typescript/client/src/components/tables/components/TachiTable.tsx +++ b/typescript/client/src/components/tables/components/TachiTable.tsx @@ -12,11 +12,11 @@ import Form from "react-bootstrap/Form"; import InputGroup from "react-bootstrap/InputGroup"; import { type integer } from "tachi-common"; -import tachitableStyles from "./TachiTable.module.scss"; import FilterDirectivesIndicator from "./FilterDirectivesIndicator"; import NoDataWrapper from "./NoDataWrapper"; import PageSelector from "./PageSelector"; import SortableTH from "./SortableTH"; +import tachitableStyles from "./TachiTable.module.scss"; const DEFAULT_TACHI_PAGE_LEN_OPTIONS: integer[] = [10, 25, 50, 100, 1000]; @@ -105,6 +105,11 @@ export default function TachiTable({ defaultReverseSort?: boolean; defaultSortMode?: string; entryName: string; + /** + * When `nonce` changes (desktop breakpoints only), replaces the filter text — e.g. + * syncing from folder breakdown → table filter. + */ + externalSearchPreset?: { nonce: number; search: string } | null; headers: Header[]; noBottomDisplayPager?: boolean; noTopDisplayStr?: boolean; @@ -114,11 +119,6 @@ export default function TachiTable({ /** Stable keys for tbody rows (e.g. chartID) so filtering reconciles instead of re-mounting. */ rowKey?: (row: D) => Key; searchFunctions?: SearchFunctions; - /** - * When `nonce` changes (desktop breakpoints only), replaces the filter text — e.g. - * syncing from folder breakdown → table filter. - */ - externalSearchPreset?: { nonce: number; search: string } | null; }) { const [search, setSearch] = useState(""); const lastExternalNonce = useRef(undefined); diff --git a/typescript/client/src/components/tables/folders/FolderTable.tsx b/typescript/client/src/components/tables/folders/FolderTable.tsx index 5dd2454c0..34e959964 100644 --- a/typescript/client/src/components/tables/folders/FolderTable.tsx +++ b/typescript/client/src/components/tables/folders/FolderTable.tsx @@ -26,16 +26,15 @@ import { CreateRankingHeader } from "../headers/RankingHeader"; export type FolderEnumBreakdownTablePreset = { metricKey: string; - valueLabel: string; nonce: number; + valueLabel: string; }; /** Scroll target used when jumping from folder breakdown chips to this table (desktop-only flow). */ -export const FOLDER_FOLDER_TABLE_SCROLL_INTO_VIEW_ID = - "folder-folder-table-scroll-anchor"; +export const FOLDER_FOLDER_TABLE_SCROLL_INTO_VIEW_ID = "folder-folder-table-scroll-anchor"; function formatFolderEnumTableFilter(metricKey: string, valueLabel: string): string { - const escaped = valueLabel.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + const escaped = valueLabel.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"'); return `${metricKey}=="${escaped}"`; } @@ -45,8 +44,8 @@ export default function FolderTable({ folderBreakdownEnumTablePreset = null, }: { dataset: FolderDataset; - game: V3Game; folderBreakdownEnumTablePreset?: FolderEnumBreakdownTablePreset | null; + game: V3Game; }) { const defaultRating = useScoreRatingAlg(game); diff --git a/typescript/client/src/components/user/UGPTStatCreator.tsx b/typescript/client/src/components/user/UGPTStatCreator.tsx index 3e7512439..674735427 100644 --- a/typescript/client/src/components/user/UGPTStatCreator.tsx +++ b/typescript/client/src/components/user/UGPTStatCreator.tsx @@ -12,7 +12,7 @@ import React, { type ChangeEventHandler, useContext, useEffect, useState } from import { Button, Form, Modal } from "react-bootstrap"; import { type FolderDocument, - FormatDifficulty, + FormatDifficultyLong, GetGameConfig, GetScoreMetricConf, GetScoreMetrics, @@ -177,7 +177,7 @@ function UGPTStatInnerSearchyBit({ game, onCreate, setShow }: GamePT & Props) { data.push({ chartID: chart.chartID, - name: `${song!.title} ${FormatDifficulty(chart)}`, + name: `${song!.title} ${FormatDifficultyLong(chart)}`, }); } diff --git a/typescript/client/src/types/api-returns.ts b/typescript/client/src/types/api-returns.ts index 6b486aabb..3ce2b177b 100644 --- a/typescript/client/src/types/api-returns.ts +++ b/typescript/client/src/types/api-returns.ts @@ -165,7 +165,7 @@ export interface UGPTFolderEvolutionReturns { songs: SongDocument[]; } -export type UGPTEvolutionReplayReturns = UGPTTableEvolutionReturns | UGPTFolderEvolutionReturns; +export type UGPTEvolutionReplayReturns = UGPTFolderEvolutionReturns | UGPTTableEvolutionReturns; export interface UGPTFolderReturns { folder: FolderDocument; diff --git a/typescript/client/src/util/routing.ts b/typescript/client/src/util/routing.ts index 0ed7828b2..9f193bf56 100644 --- a/typescript/client/src/util/routing.ts +++ b/typescript/client/src/util/routing.ts @@ -4,7 +4,7 @@ function pathnameOnlyForRouteMatch(str: string): string { try { return new URL(str).pathname; } catch { - return str.split(/[?#]/)[0]; + return str.split(/[?#]/u)[0]; } } diff --git a/typescript/common/src/config/game-support/arcaea.ts b/typescript/common/src/config/game-support/arcaea.ts index 8d4fd1619..bec2efdbb 100644 --- a/typescript/common/src/config/game-support/arcaea.ts +++ b/typescript/common/src/config/game-support/arcaea.ts @@ -102,12 +102,13 @@ export const GAME_ARCAEA_CONF = { difficulties: { type: "FIXED", order: ["Past", "Present", "Future", "Beyond"], - format: { + formatShort: { Past: "PST", Present: "PRS", Future: "FTR", Beyond: "BYD", }, + formatLong: {}, default: "Future", }, diff --git a/typescript/common/src/config/game-support/bms.ts b/typescript/common/src/config/game-support/bms.ts index 3407d4ad8..8f28136eb 100644 --- a/typescript/common/src/config/game-support/bms.ts +++ b/typescript/common/src/config/game-support/bms.ts @@ -353,7 +353,8 @@ export const GAME_BMS_7K_CONF = { // "CHART" isn't a very creative name, but this is in a similar vein to how // games with no playtypes use the value "Single". order: ["CHART"], - format: {}, + formatShort: {}, + formatLong: {}, default: "CHART", }, diff --git a/typescript/common/src/config/game-support/chunithm.ts b/typescript/common/src/config/game-support/chunithm.ts index e9b53edf6..f3de15deb 100644 --- a/typescript/common/src/config/game-support/chunithm.ts +++ b/typescript/common/src/config/game-support/chunithm.ts @@ -137,13 +137,14 @@ export const GAME_CHUNITHM_CONF = { difficulties: { type: "CHUGEKIMAI_STYLE", order: ["BASIC", "ADVANCED", "EXPERT", "MASTER", "ULTIMA"], - format: { + formatShort: { BASIC: "B", ADVANCED: "A", EXPERT: "E", MASTER: "M", ULTIMA: "U", }, + formatLong: {}, default: "MASTER", }, diff --git a/typescript/common/src/config/game-support/ddr.ts b/typescript/common/src/config/game-support/ddr.ts index 4fa2b5ff8..1a8e985cb 100644 --- a/typescript/common/src/config/game-support/ddr.ts +++ b/typescript/common/src/config/game-support/ddr.ts @@ -176,13 +176,14 @@ export const GAME_DDR_SP_CONF = { difficulties: { type: "FIXED", order: DDR_DIFFICULTIES, - format: { + formatShort: { BEGINNER: "BEG", BASIC: "BAS", DIFFICULT: "DIF", EXPERT: "EXP", CHALLENGE: "CHA", }, + formatLong: {}, default: "EXPERT", }, diff --git a/typescript/common/src/config/game-support/gitadora.ts b/typescript/common/src/config/game-support/gitadora.ts index 7e21727f1..b32f58548 100644 --- a/typescript/common/src/config/game-support/gitadora.ts +++ b/typescript/common/src/config/game-support/gitadora.ts @@ -96,7 +96,7 @@ export const GAME_GITADORA_GITA_CONF = { "BASS EXTREME", "BASS MASTER", ], - format: { + formatShort: { BASIC: "G-BSC", ADVANCED: "G-ADV", EXTREME: "G-EXT", @@ -106,6 +106,7 @@ export const GAME_GITADORA_GITA_CONF = { "BASS EXTREME": "B-EXT", "BASS MASTER": "B-MAS", }, + formatLong: {}, default: "EXTREME", }, @@ -141,12 +142,13 @@ export const GAME_GITADORA_DORA_CONF = { difficulties: { type: "FIXED", order: ["BASIC", "ADVANCED", "EXTREME", "MASTER"], - format: { + formatShort: { BASIC: "D-BSC", ADVANCED: "D-ADV", EXTREME: "D-EXT", MASTER: "D-MAS", }, + formatLong: {}, default: "EXTREME", }, } as const satisfies INTERNAL_GAME_CONFIG; diff --git a/typescript/common/src/config/game-support/iidx.ts b/typescript/common/src/config/game-support/iidx.ts index ab835a175..3dc2f5305 100644 --- a/typescript/common/src/config/game-support/iidx.ts +++ b/typescript/common/src/config/game-support/iidx.ts @@ -237,7 +237,7 @@ export const GAME_IIDX_SP_CONF = { "Kiraku ANOTHER", "Kiraku LEGGENDARIA", ], - format: { + formatShort: { NORMAL: "SPN", HYPER: "SPH", ANOTHER: "SPA", @@ -255,6 +255,26 @@ export const GAME_IIDX_SP_CONF = { "Kiraku ANOTHER": "SPA (Kr.)", "Kiraku LEGGENDARIA": "SPL (Kr.)", }, + formatLong: { + NORMAL: "SP NORMAL", + HYPER: "SP HYPER", + ANOTHER: "SP ANOTHER", + LEGGENDARIA: "SP LEGGENDARIA", + + // deliberately short + "All Scratch NORMAL": "SPN (Scr.)", + "All Scratch HYPER": "SPH (Scr.)", + "All Scratch ANOTHER": "SPA (Scr.)", + "All Scratch LEGGENDARIA": "SPL (Scr.)", + "Kichiku NORMAL": "SPN (Kc.)", + "Kichiku HYPER": "SPH (Kc.)", + "Kichiku ANOTHER": "SPA (Kc.)", + "Kichiku LEGGENDARIA": "SPL (Kc.)", + "Kiraku NORMAL": "SPN (Kr.)", + "Kiraku HYPER": "SPH (Kr.)", + "Kiraku ANOTHER": "SPA (Kr.)", + "Kiraku LEGGENDARIA": "SPL (Kr.)", + }, default: "ANOTHER", }, @@ -371,7 +391,7 @@ export const GAME_IIDX_DP_CONF = { "Kiraku ANOTHER", "Kiraku LEGGENDARIA", ], - format: { + formatShort: { NORMAL: "DPN", HYPER: "DPH", ANOTHER: "DPA", @@ -389,6 +409,26 @@ export const GAME_IIDX_DP_CONF = { "Kiraku ANOTHER": "DPA (Kr.)", "Kiraku LEGGENDARIA": "DPL (Kr.)", }, + formatLong: { + NORMAL: "DP NORMAL", + HYPER: "DP HYPER", + ANOTHER: "DP ANOTHER", + LEGGENDARIA: "DP LEGGENDARIA", + + // deliberately short + "All Scratch NORMAL": "DPN (Scr.)", + "All Scratch HYPER": "DPH (Scr.)", + "All Scratch ANOTHER": "DPA (Scr.)", + "All Scratch LEGGENDARIA": "DPL (Scr.)", + "Kichiku NORMAL": "DPN (Kc.)", + "Kichiku HYPER": "DPH (Kc.)", + "Kichiku ANOTHER": "DPA (Kc.)", + "Kichiku LEGGENDARIA": "DPL (Kc.)", + "Kiraku NORMAL": "DPN (Kr.)", + "Kiraku HYPER": "DPH (Kr.)", + "Kiraku ANOTHER": "DPA (Kr.)", + "Kiraku LEGGENDARIA": "DPL (Kr.)", + }, default: "ANOTHER", }, } as const satisfies INTERNAL_GAME_CONFIG; diff --git a/typescript/common/src/config/game-support/jubeat.ts b/typescript/common/src/config/game-support/jubeat.ts index ae6e84436..be2286f74 100644 --- a/typescript/common/src/config/game-support/jubeat.ts +++ b/typescript/common/src/config/game-support/jubeat.ts @@ -99,7 +99,7 @@ export const GAME_JUBEAT_SINGLE_CONF = { difficulties: { type: "FIXED", order: ["BSC", "ADV", "EXT", "HARD BSC", "HARD ADV", "HARD EXT"], - format: { + formatShort: { BSC: "BSC", ADV: "ADV", EXT: "EXT", @@ -107,6 +107,7 @@ export const GAME_JUBEAT_SINGLE_CONF = { "HARD ADV": "H. ADV", "HARD EXT": "H. EXT", }, + formatLong: {}, default: "EXT", }, diff --git a/typescript/common/src/config/game-support/maimai-dx.ts b/typescript/common/src/config/game-support/maimai-dx.ts index 72ef005b9..571c2697c 100644 --- a/typescript/common/src/config/game-support/maimai-dx.ts +++ b/typescript/common/src/config/game-support/maimai-dx.ts @@ -188,7 +188,7 @@ export const GAME_MAIMAI_DX_CONF = { "DX Master", "DX Re:Master", ], - format: { + formatShort: { Basic: "BAS", Advanced: "ADV", Expert: "EXP", @@ -200,6 +200,7 @@ export const GAME_MAIMAI_DX_CONF = { "DX Master": "DX MAS", "DX Re:Master": "DX Re:MAS", }, + formatLong: {}, default: "Master", }, diff --git a/typescript/common/src/config/game-support/maimai.ts b/typescript/common/src/config/game-support/maimai.ts index 9abb39cd5..737355bbd 100644 --- a/typescript/common/src/config/game-support/maimai.ts +++ b/typescript/common/src/config/game-support/maimai.ts @@ -128,7 +128,7 @@ export const GAME_MAIMAI_CONF = { difficulties: { type: "CHUGEKIMAI_STYLE", order: ["Easy", "Basic", "Advanced", "Expert", "Master", "Re:Master"], - format: { + formatShort: { Easy: "ESY", Basic: "BAS", Advanced: "ADV", @@ -136,6 +136,7 @@ export const GAME_MAIMAI_CONF = { Master: "MAS", "Re:Master": "Re:MAS", }, + formatLong: {}, default: "Master", }, diff --git a/typescript/common/src/config/game-support/museca.ts b/typescript/common/src/config/game-support/museca.ts index 7d48ae79f..8cfc5da52 100644 --- a/typescript/common/src/config/game-support/museca.ts +++ b/typescript/common/src/config/game-support/museca.ts @@ -86,7 +86,8 @@ export const GAME_MUSECA_CONF = { difficulties: { type: "FIXED", order: ["Green", "Yellow", "Red"], - format: { Green: "G", Yellow: "Y", Red: "R" }, + formatShort: { Green: "G", Yellow: "Y", Red: "R" }, + formatLong: {}, default: "Red", }, diff --git a/typescript/common/src/config/game-support/ongeki.ts b/typescript/common/src/config/game-support/ongeki.ts index cccce588e..28b2a39ce 100644 --- a/typescript/common/src/config/game-support/ongeki.ts +++ b/typescript/common/src/config/game-support/ongeki.ts @@ -185,13 +185,14 @@ export const GAME_ONGEKI_CONF = { difficulties: { type: "FIXED", order: ["BASIC", "ADVANCED", "EXPERT", "MASTER", "LUNATIC"], - format: { + formatShort: { BASIC: "BAS", ADVANCED: "ADV", EXPERT: "EXP", MASTER: "MAS", LUNATIC: "LUN", }, + formatLong: {}, default: "MASTER", }, diff --git a/typescript/common/src/config/game-support/pms.ts b/typescript/common/src/config/game-support/pms.ts index f4ed4a146..3a6dc07fe 100644 --- a/typescript/common/src/config/game-support/pms.ts +++ b/typescript/common/src/config/game-support/pms.ts @@ -127,7 +127,8 @@ export const GAME_PMS_CONTROLLER_CONF = { difficulties: { type: "FIXED", order: ["CHART"], - format: {}, + formatShort: {}, + formatLong: {}, default: "CHART", }, diff --git a/typescript/common/src/config/game-support/popn.ts b/typescript/common/src/config/game-support/popn.ts index 50895e597..e020cdd2d 100644 --- a/typescript/common/src/config/game-support/popn.ts +++ b/typescript/common/src/config/game-support/popn.ts @@ -120,12 +120,13 @@ export const GAME_POPN_CONF = { difficulties: { type: "FIXED", order: ["Easy", "Normal", "Hyper", "EX"], - format: { + formatShort: { Easy: "E", Normal: "N", Hyper: "H", EX: "EX", }, + formatLong: {}, default: "EX", }, diff --git a/typescript/common/src/config/game-support/sdvx.ts b/typescript/common/src/config/game-support/sdvx.ts index 80c935e03..f8a482e05 100644 --- a/typescript/common/src/config/game-support/sdvx.ts +++ b/typescript/common/src/config/game-support/sdvx.ts @@ -160,7 +160,8 @@ export const GAME_SDVX_CONF = { difficulties: { type: "FIXED", order: ["NOV", "ADV", "EXH", "INF", "GRV", "HVN", "VVD", "XCD", "MXM", "ULT"], - format: {}, // they're all already short enough + formatShort: {}, // they're all already short enough + formatLong: {}, default: "EXH", }, diff --git a/typescript/common/src/config/game-support/usc.ts b/typescript/common/src/config/game-support/usc.ts index 8523b4032..92e136054 100644 --- a/typescript/common/src/config/game-support/usc.ts +++ b/typescript/common/src/config/game-support/usc.ts @@ -91,7 +91,8 @@ export const GAME_USC_CONTROLLER_CONF = { order: ["NOV", "ADV", "EXH", "INF"], // deliberately not ported to v3 - formatting of charts as // "CON EXH" etc. - format: {}, + formatShort: {}, + formatLong: {}, default: "EXH", }, diff --git a/typescript/common/src/config/game-support/wacca.ts b/typescript/common/src/config/game-support/wacca.ts index 9a8279951..28341303c 100644 --- a/typescript/common/src/config/game-support/wacca.ts +++ b/typescript/common/src/config/game-support/wacca.ts @@ -115,12 +115,13 @@ export const GAME_WACCA_CONF = { difficulties: { type: "FIXED", order: ["NORMAL", "HARD", "EXPERT", "INFERNO"], - format: { + formatShort: { NORMAL: "NRM", HARD: "HRD", EXPERT: "EXP", INFERNO: "INF", }, + formatLong: {}, default: "EXPERT", }, diff --git a/typescript/common/src/types/game-config-utils.ts b/typescript/common/src/types/game-config-utils.ts index 6bc348fd6..286aeeff2 100644 --- a/typescript/common/src/types/game-config-utils.ts +++ b/typescript/common/src/types/game-config-utils.ts @@ -130,9 +130,16 @@ export interface FixedDifficulties { order: ReadonlyArray; /** - * How should we format these difficulty names? + * How should we format these difficulty names for short usage? + * if not specified, will use the difficulty name as-is */ - format: Partial>; + formatShort: Partial>; + + /** + * How should we format these difficulty names for long, full usage? + * if not specified, will use the difficulty name as-is + */ + formatLong: Partial>; default: Difficulty; } @@ -161,7 +168,13 @@ export interface ChuGekiMaiDifficulties { * Dynamic ones (i.e. ones not in the Order set) are not formatted - they are printed * exactly as-is. */ - format: Partial>; + formatShort: Partial>; + + /** + * How should we format these difficulty names for long, full usage? + * if not specified, will use the difficulty name as-is + */ + formatLong: Partial>; default: TDifficulty; } diff --git a/typescript/common/src/utils/util.ts b/typescript/common/src/utils/util.ts index b928a4056..26c7604a3 100644 --- a/typescript/common/src/utils/util.ts +++ b/typescript/common/src/utils/util.ts @@ -45,12 +45,12 @@ export function FormatInt(v: number): string { return Math.floor(v).toFixed(0); } -export function FormatDifficulty(chart: ChartDocument): string { - const gameGroup = GameToGameGroup(chart.game); - if (gameGroup === "ongeki" || gameGroup === "chunithm" || gameGroup === "maimaidx") { - return `${chart.difficulty} ${chart.level}`.trim(); - } - return FormatDifficultyShort(chart); +/** + * Formats a chart's difficulty into a longer variant. This handles a lot of + * game-specific strange edge cases. + */ +export function FormatDifficultyLong(chart: ChartDocument): string { + return FormatDifficultyInternal(chart, false); } /** @@ -58,6 +58,10 @@ export function FormatDifficulty(chart: ChartDocument): string { * game-specific strange edge cases. */ export function FormatDifficultyShort(chart: ChartDocument): string { + return FormatDifficultyInternal(chart, true); +} + +function FormatDifficultyInternal(chart: ChartDocument, short: boolean): string { const gameGroup = GameToGameGroup(chart.game); const gameConfig = GetGameConfig(chart.game); @@ -84,7 +88,14 @@ export function FormatDifficultyShort(chart: ChartDocument): string { gameConfig.difficulties.type === "FIXED" || gameConfig.difficulties.type === "CHUGEKIMAI_STYLE" ) { - const diff = gameConfig.difficulties.format[chart.difficulty] ?? chart.difficulty; + let diff; + if (short) { + diff = gameConfig.difficulties.formatShort[chart.difficulty]; + } else { + diff = gameConfig.difficulties.formatLong[chart.difficulty]; + } + + diff ??= chart.difficulty; return `${diff} ${chart.level}`.trim(); } @@ -97,7 +108,7 @@ export function FormatDifficultyShort(chart: ChartDocument): string { * Formats a chart's difficulty for searching, such as forwarding this query to youtube. */ export function FormatDifficultySearch(chart: ChartDocument): string | null { - return FormatDifficulty(chart); + return FormatDifficultyLong(chart); } export function FormatGame(game: V3Game): string { @@ -189,7 +200,7 @@ export function FormatChart(chart: ChartDocument): string { if (gameConfig.difficulties.type === "DYNAMIC") { diff = chart.difficulty; } else { - diff = gameConfig.difficulties.format[chart.difficulty] ?? chart.difficulty; + diff = gameConfig.difficulties.formatShort[chart.difficulty] ?? chart.difficulty; } // return the most recent version this chart appeared in if it diff --git a/typescript/server/src/lib/folders/table-evolution.ts b/typescript/server/src/lib/folders/table-evolution.ts index 6779ca657..6ed715c2b 100644 --- a/typescript/server/src/lib/folders/table-evolution.ts +++ b/typescript/server/src/lib/folders/table-evolution.ts @@ -1,21 +1,21 @@ +import type { ConfEnumScoreMetric } from "tachi-common/types/metrics"; + import { SELECT_CHART, ToChartDocument } from "#lib/db-formats/chart"; import { GetSongsByIDs } from "#lib/db-formats/song"; import DB from "#services/pg/db"; import { GetScoresForTableEvolution } from "#utils/queries/scores"; import { + type ChartDocument, + type FolderDocument, GetGameConfig, GetScoreEnumConfs, type integer, - type ChartDocument, - type FolderDocument, type ScoreDocument, type SongDocument, type TableDocument, type V3Game, } from "tachi-common"; -import type { ConfEnumScoreMetric } from "tachi-common/types/metrics"; - import { GetFolderChartIDs } from "./folders"; /** One strict improvement on a `(chart × metric)` enum index within table evolution semantics. */ @@ -29,7 +29,9 @@ export interface TableEvolutionEvent { value: string; } -export async function GetTableEvolutionFolderChartMembership(folders: Array): Promise<{ +export async function GetTableEvolutionFolderChartMembership( + folders: Array, +): Promise<{ distinctChartIDs: Array; folderChartIDs: Record>; }> { @@ -77,12 +79,17 @@ export function ComputeTableEvolutionEvents( let currentChartID: string | undefined; const prevMaxForMetric: Record = {}; - const readEnumIdx = (scoreData: ScoreDocument["scoreData"], metric: string): integer | undefined => { + const readEnumIdx = ( + scoreData: ScoreDocument["scoreData"], + metric: string, + ): integer | undefined => { const fromRoot = scoreData.enumIndexes?.[metric as keyof typeof scoreData.enumIndexes] as | integer | undefined; const fromOptional = - scoreData.optional?.enumIndexes?.[metric as keyof typeof scoreData.optional.enumIndexes]; + scoreData.optional?.enumIndexes?.[ + metric as keyof typeof scoreData.optional.enumIndexes + ]; const coerced = typeof fromOptional === "number" ? fromOptional : undefined; @@ -160,7 +167,8 @@ export async function LoadTableEvolutionPayload( }> { const enumConfs = GetScoreEnumConfs(GetGameConfig(game)); - const { distinctChartIDs, folderChartIDs } = await GetTableEvolutionFolderChartMembership(folders); + const { distinctChartIDs, folderChartIDs } = + await GetTableEvolutionFolderChartMembership(folders); const scores = await GetScoresForTableEvolution(userID, game, distinctChartIDs); const events = ComputeTableEvolutionEvents(scores, enumConfs); @@ -183,7 +191,9 @@ export async function LoadFolderEvolutionPayload( }> { const enumConfs = GetScoreEnumConfs(GetGameConfig(game)); - const { distinctChartIDs, folderChartIDs } = await GetTableEvolutionFolderChartMembership([folder]); + const { distinctChartIDs, folderChartIDs } = await GetTableEvolutionFolderChartMembership([ + folder, + ]); const scores = await GetScoresForTableEvolution(userID, game, distinctChartIDs); const events = ComputeTableEvolutionEvents(scores, enumConfs); diff --git a/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderSlug/router.test.ts b/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderSlug/router.test.ts index 8739fafdf..14a6c743b 100644 --- a/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderSlug/router.test.ts +++ b/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderSlug/router.test.ts @@ -257,17 +257,14 @@ describe("GET /api/v1/users/:userID/games/:game/folders/:folderSlug/evolution", const uid = `${Date.now()}_${Math.floor(Math.random() * 100_000)}`; async function insScore(scoreId: string, lamp: string, iso: string) { - const { data, derived, judgements } = mongoScoreDataToPg( - "iidx-sp", - { - score: 1400, - lamp, - percent: 93, - optional: {}, - judgements: { pgreat: 1, great: 0 }, - grade: "AAA", - } as ScoreData<"iidx-sp">, - ); + const { data, derived, judgements } = mongoScoreDataToPg("iidx-sp", { + score: 1400, + lamp, + percent: 93, + optional: {}, + judgements: { pgreat: 1, great: 0 }, + grade: "AAA", + } as ScoreData<"iidx-sp">); await DB.insertInto("score") .values({ diff --git a/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts b/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts index 471e357a3..508466ea9 100644 --- a/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts +++ b/typescript/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts @@ -138,17 +138,14 @@ describe("GET /api/v1/users/:userID/games/:game/tables/:tableID", () => { const uid = `${Date.now()}_${Math.floor(Math.random() * 100_000)}`; async function insScore(scoreId: string, lamp: string, iso: string) { - const { data, derived, judgements } = mongoScoreDataToPg( - "iidx-sp", - { - score: 1400, - lamp, - percent: 93, - optional: {}, - judgements: { pgreat: 1, great: 0 }, - grade: "AAA", - } as ScoreData<"iidx-sp">, - ); + const { data, derived, judgements } = mongoScoreDataToPg("iidx-sp", { + score: 1400, + lamp, + percent: 93, + optional: {}, + judgements: { pgreat: 1, great: 0 }, + grade: "AAA", + } as ScoreData<"iidx-sp">); await DB.insertInto("score") .values({ @@ -190,9 +187,7 @@ describe("GET /api/v1/users/:userID/games/:game/tables/:tableID", () => { expect(lampEvents[0].value).toBe("EASY CLEAR"); expect(lampEvents[1].value).toBe("HARD CLEAR"); - expect(res.body.body.folderChartIDs["ugtfolder"]).toContain( - TestingIIDXSPScorePB.chartID, - ); + expect(res.body.body.folderChartIDs.ugtfolder).toContain(TestingIIDXSPScorePB.chartID); }); it("returns 404 when the table does not exist", async () => {