These updates suck

This commit is contained in:
zkldi
2021-09-17 12:35:44 +01:00
parent 8a4b9b58f1
commit 77c3dca2a2
27 changed files with 555 additions and 139 deletions
+1 -1
View File
@@ -75,7 +75,7 @@
"resolve": "1.12.0",
"resolve-url-loader": "3.1.0",
"socicon": "3.0.5",
"tachi-common": "0.2.12"
"tachi-common": "0.2.15"
},
"devDependencies": {
"@types/humanize-duration": "3.25.1",
+4 -4
View File
@@ -77,7 +77,7 @@ specifiers:
serve: 11.2.0
socicon: 3.0.5
style-loader: 1.0.0
tachi-common: 0.2.12
tachi-common: 0.2.15
terser-webpack-plugin: 1.4.1
ts-pnp: 1.1.4
typescript: 4.3.5
@@ -142,7 +142,7 @@ dependencies:
resolve: 1.12.0
resolve-url-loader: 3.1.0
socicon: 3.0.5
tachi-common: 0.2.12
tachi-common: 0.2.15
devDependencies:
'@types/humanize-duration': 3.25.1
@@ -11178,8 +11178,8 @@ packages:
strip-ansi: 6.0.0
dev: true
/tachi-common/0.2.12:
resolution: {integrity: sha512-xwbQUZefLUs1sus3nrrys84qJyRMWLl/Mb4UzFYd74n0ZmOejrxpwIb18ILWww4J/IxQurPd4Id+DCP12SEW1w==}
/tachi-common/0.2.15:
resolution: {integrity: sha512-p7nt7pupl2JWNb20K7n18YOBtxUYeoyDsE6YbPVVyPvY1oj74a2YxALpmajGV2pbVAkdywrJS94In2sQUGzJtw==}
dependencies:
monk: 7.3.4
transitivePeerDependencies:
@@ -1,22 +1,38 @@
import { ResponsiveBar } from "@nivo/bar";
import { BarChartTooltip } from "components/charts/ChartTooltip";
import QuickTooltip from "components/layout/misc/QuickTooltip";
import Card from "components/layout/page/Card";
import FolderTable from "components/tables/folders/FolderTable";
import ApiError from "components/util/ApiError";
import Divider from "components/util/Divider";
import Icon from "components/util/Icon";
import Loading from "components/util/Loading";
import Muted from "components/util/Muted";
import useApiQuery from "components/util/query/useApiQuery";
import React, { useMemo } from "react";
import SelectButton from "components/util/SelectButton";
import { useFormik } from "formik";
import React, { useMemo, useState } from "react";
import { Col, Form, Row } from "react-bootstrap";
import { useParams } from "react-router-dom";
import { Game, GetGamePTConfig, PublicUserDocument } from "tachi-common";
import {
ChartTierlistInfo,
FormatDifficultyShort,
Game,
GetGamePTConfig,
GPTTierlists,
IDStrings,
PublicUserDocument,
} from "tachi-common";
import { UGPTFolderReturns } from "types/api-returns";
import { FolderDataset } from "types/tables";
import { Playtype } from "types/tachi";
import { DEFAULT_BAR_PROPS } from "util/charts";
import { ChangeOpacity } from "util/color-opacity";
import { CreateChartIDMap, CreateSongMap } from "util/data";
import { ComposeExpFn, ComposeInverseExpFn } from "util/misc";
import { ComposeExpFn, ComposeInverseExpFn, IsNullish, NO_OP } from "util/misc";
import { GetGradeChartExpScale } from "util/scales";
import { NumericSOV, StrSOV } from "util/sorts";
import { GetScaleAchievedFn } from "util/tierlist";
interface Props {
reqUser: PublicUserDocument;
@@ -31,6 +47,10 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
`/users/${reqUser.id}/games/${game}/${playtype}/folders/${folderID}`
);
const [mode, setMode] = useState<"normal" | "ladder">("normal");
const gptConfig = GetGamePTConfig(game, playtype);
const folderDataset = useMemo(() => {
if (!data) {
return null;
@@ -56,6 +76,22 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
return folderDataset;
}, [data]);
const folderInfoHeader = useMemo(() => {
if (!folderDataset || !data) {
return <Loading />;
}
return (
<FolderInfoHeader
folderDataset={folderDataset}
data={data}
game={game}
playtype={playtype}
reqUser={reqUser}
/>
);
}, [folderDataset]);
if (isLoading || !data || !folderDataset) {
return <Loading />;
}
@@ -69,35 +105,326 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
<div className="col-12">
<Divider className="mb-4" />
</div>
<div className="col-12">{folderInfoHeader}</div>
<div className="col-12">
<FolderInfoHeader {...{ folderDataset, data, game, playtype, reqUser }} />
<Divider className="my-4" />
</div>
<div className="col-12 d-flex">
<div className="btn-group mx-auto">
<SelectButton value={mode} setValue={setMode} id="normal">
<Icon type="table" />
Normal View
</SelectButton>
{gptConfig.tierlists.length && (
<SelectButton value={mode} setValue={setMode} id="ladder">
<Icon type="sort-alpha-up" />
Tierlist View
</SelectButton>
)}
</div>
</div>
<div className="col-12">
<Divider className="my-4" />
</div>
<div className="col-12">
<FolderTable
dataset={folderDataset}
game={game}
playtype={playtype}
reqUser={reqUser}
indexCol={false}
/>
{mode === "normal" ? (
<FolderTable
dataset={folderDataset}
game={game}
playtype={playtype}
reqUser={reqUser}
indexCol={false}
/>
) : (
<TierlistBreakdown
folderDataset={folderDataset}
game={game}
playtype={playtype}
reqUser={reqUser}
data={data}
/>
)}
</div>
</div>
);
}
function FolderInfoHeader({
type InfoProps = Props & {
folderDataset: FolderDataset;
data: UGPTFolderReturns;
};
function TierlistBreakdown({ game, folderDataset, playtype, reqUser }: InfoProps) {
const gptConfig = GetGamePTConfig(game, playtype);
const formik = useFormik({
initialValues: {
__hideAchieved: false,
...Object.fromEntries(gptConfig.tierlists.map(e => [e, true])),
} as { __hideAchieved: boolean } & Partial<Record<GPTTierlists[IDStrings], true>>,
onSubmit: NO_OP,
});
const tierlistInfo = FolderDatasetToTierlistInfo(folderDataset, game, playtype, formik.values);
const dataMap = CreateChartIDMap(folderDataset);
return (
<Row>
<Col xs={12}>
<Card header="Tierlist View Configuration">
<span>Here you can select what tierlist blocks to show!</span>
<Divider className="my-4" />
{gptConfig.tierlists.length > 1 &&
gptConfig.tierlists.map(e => (
<>
<Form.Check
key={e}
type="checkbox"
id={e}
checked={formik.values[e]}
onChange={formik.handleChange}
label={e}
/>
<Form.Text>{gptConfig.tierlistDescriptions[e]}</Form.Text>
</>
))}
<Form.Check
type="checkbox"
id="__hideAchieved"
checked={formik.values.__hideAchieved}
onChange={formik.handleChange}
label="Hide Achieved"
/>
<Form.Text>Hide achieved elements of the tierlist.</Form.Text>
</Card>
</Col>
<Col xs={12}>
<Divider className="my-4" />
</Col>
<Col xs={12}>
<TierlistInfoLadder
tierlistInfo={tierlistInfo}
dataMap={dataMap}
game={game}
playtype={playtype}
/>
</Col>
</Row>
);
}
function TierlistInfoLadder({
tierlistInfo,
dataMap,
game,
playtype,
reqUser,
folderDataset,
data,
}: Props & { folderDataset: FolderDataset; data: UGPTFolderReturns }) {
}: {
tierlistInfo: TierlistInfo[];
dataMap: Map<string, FolderDataset[0]>;
game: Game;
playtype: Playtype;
}) {
const buckets: TierlistInfo[][] = useMemo(() => {
const buckets: TierlistInfo[][] = [];
let currentBucket: TierlistInfo[] = [];
let lastValue;
for (const tl of tierlistInfo) {
if (lastValue && tl.data.value !== lastValue) {
buckets.push(currentBucket);
currentBucket = [tl];
} else {
currentBucket.push(tl);
}
lastValue = tl.data.value;
}
if (currentBucket.length) {
buckets.push(currentBucket);
}
return buckets;
}, tierlistInfo);
const gptConfig = GetGamePTConfig(game, playtype);
for (const bucket of buckets) {
bucket.sort(NumericSOV(x => gptConfig.tierlists.indexOf(x.key), true));
}
if (tierlistInfo.length === 0) {
return <Row className="justify-content-center">Got no tierlist data to show you!</Row>;
}
return (
<Row className="text-center">
{buckets.map((bucket, i) => (
<React.Fragment key={i}>
<Col className="ladder-header" xs={12}>
{bucket[0].data.value}
</Col>
{bucket.map((tierlistInfo, i) => {
const data = dataMap.get(tierlistInfo.chartID)!;
const lastKey = bucket[i - 1];
let statusClass;
switch (tierlistInfo.achieved) {
case AchievedStatuses.ACHIEVED:
statusClass = "achieved";
break;
case AchievedStatuses.FAILED:
statusClass = "unachieved";
break;
case AchievedStatuses.NOT_PLAYED:
case AchievedStatuses.SCORE_BASED:
statusClass = "";
}
return (
<>
{lastKey && lastKey.key !== tierlistInfo.key && (
<Col xl={12} className="my-2" />
)}
<Col
className={`ladder-element ${
i % 12 < 6 ? "ladder-element-dark" : ""
} ladder-element-${statusClass}`}
key={tierlistInfo.chartID}
xs={6}
sm={6}
md={4}
lg={3}
xl={2}
>
{data.__related.song.title} {FormatDifficultyShort(data, game)}
<Divider className="my-2" />
{tierlistInfo.key}{" "}
{tierlistInfo.data.individualDifference && (
<>
<br />
<div className="mt-1">
<QuickTooltip tooltipContent="Individual Difference - The difficulty of this varies massively between people!">
<span>
<Icon type="balance-scale-left" />
</span>
</QuickTooltip>
</div>
</>
)}
<Divider className="my-2" />
<Muted>
{tierlistInfo.achieved === AchievedStatuses.NOT_PLAYED
? "Not Played"
: tierlistInfo.achieved === AchievedStatuses.SCORE_BASED
? `You have: ${data.__related.pb!.scoreData.grade}`
: `You have: ${data.__related.pb!.scoreData.lamp}`}
</Muted>
</Col>
</>
);
})}
</React.Fragment>
))}
</Row>
);
}
interface TierlistInfo {
chartID: string;
key: GPTTierlists[IDStrings];
data: ChartTierlistInfo;
achieved: AchievedStatuses;
}
enum AchievedStatuses {
NOT_PLAYED,
FAILED,
ACHIEVED,
SCORE_BASED,
}
function FolderDatasetToTierlistInfo(
folderDataset: FolderDataset,
game: Game,
playtype: Playtype,
options: Partial<Record<GPTTierlists[IDStrings], boolean>> & { __hideAchieved: boolean }
) {
const tierlistInfo: TierlistInfo[] = [];
const tierlistKeys: GPTTierlists[IDStrings][] = [];
for (const k in options) {
const key = k as GPTTierlists[IDStrings];
if (options[key]) {
tierlistKeys.push(key);
}
}
for (const data of folderDataset) {
for (const key of tierlistKeys) {
if (IsNullish(data.tierlistInfo[key])) {
continue;
}
let achieved: AchievedStatuses;
if (!data.__related.pb) {
achieved = AchievedStatuses.NOT_PLAYED;
} else {
const fn = GetScaleAchievedFn(game, playtype, key);
if (fn) {
achieved = fn(data.__related.pb)
? AchievedStatuses.ACHIEVED
: AchievedStatuses.FAILED;
} else {
achieved = AchievedStatuses.SCORE_BASED;
}
}
if (options.__hideAchieved && achieved === AchievedStatuses.ACHIEVED) {
continue;
}
tierlistInfo.push({
chartID: data.chartID,
key,
data: data.tierlistInfo[key]!,
achieved,
});
}
}
return tierlistInfo.sort(NumericSOV(x => x.data.value, true));
}
function FolderInfoHeader({ game, playtype, reqUser, folderDataset, data }: InfoProps) {
return (
<Card header={`${reqUser.username}'s ${data.folder.title} Breakdown`}>
<ScoreDistributionChart
game={game}
data={data}
folderDataset={folderDataset}
playtype={playtype}
reqUser={reqUser}
/>
</Card>
);
}
function ScoreDistributionChart({ game, playtype, reqUser, folderDataset, data }: InfoProps) {
const dataMap = CreateChartIDMap(folderDataset);
const gptConfig = GetGamePTConfig(game, playtype);
const dataset = [];
const expScale = 1;
const expScale = GetGradeChartExpScale(game);
const expFn = ComposeExpFn(expScale);
const invExpFn = ComposeInverseExpFn(expScale);
@@ -114,70 +441,69 @@ function FolderInfoHeader({
dataset.sort(NumericSOV(x => x.expValue));
const dataMap = CreateChartIDMap(folderDataset);
const gptConfig = GetGamePTConfig(game, playtype);
return (
<Card header={`${reqUser.username}'s ${data.folder.title} Breakdown`}>
<div style={{ height: 400 }}>
<ResponsiveBar
indexBy="chartID"
tooltip={d => (
<BarChartTooltip
point={d}
renderFn={d => {
const data = dataMap.get(d.indexValue as string)!;
<div style={{ height: 400 }}>
<ResponsiveBar
indexBy="chartID"
tooltip={d => (
<BarChartTooltip
point={d}
renderFn={d => {
const data = dataMap.get(d.indexValue as string)!;
return (
<div className="w-100 text-center">
{data.__related.song.title}
<br />
{data.__related.pb?.scoreData.percent.toFixed(2)}%
</div>
);
}}
/>
)}
key={"value"}
return (
<div className="w-100 text-center">
{data.__related.song.title}
<br />
{data.__related.pb?.scoreData.percent.toFixed(2)}%
</div>
);
}}
/>
)}
key={"value"}
colors={k => {
if (!k.data.grade) {
return "black";
}
// @ts-expect-error temp
return ChangeOpacity(gptConfig.gradeColours[k.data.grade], 0.5);
}}
// @ts-expect-error temp
borderColor={k => gptConfig.gradeColours[k.data.grade]}
borderWidth={1}
padding={0.2}
// @ts-expect-error temp
data={dataset}
minValue={0}
maxValue={expFn(100)}
margin={{ left: 50, top: 20, bottom: 20 }}
valueFormat={e => `${invExpFn(e).toFixed(2)}%`}
axisLeft={{
tickValues: gptConfig.gradeBoundaries.map(e => (e === 0 ? 0 : expFn(e))),
format: x => {
let nearest;
colors={k => ChangeOpacity(gptConfig.gradeColours[k.data.grade], 0.5)}
// @ts-expect-error temp
const lgv = invExpFn(x);
borderColor={k => gptConfig.gradeColours[k.data.grade]}
borderWidth={1}
padding={0.2}
// @ts-expect-error temp
data={dataset}
minValue={0}
maxValue={expFn(100)}
margin={{ left: 50, top: 20, bottom: 20 }}
axisLeft={{
tickValues: gptConfig.gradeBoundaries.map(e => (e === 0 ? 0 : expFn(e))),
format: x => {
let nearest;
const lgv = invExpFn(x);
for (const [i, gradeBnd] of gptConfig.gradeBoundaries.entries()) {
if (Math.abs(gradeBnd - lgv) < 0.00005) {
nearest = i;
break;
}
for (const [i, gradeBnd] of gptConfig.gradeBoundaries.entries()) {
if (Math.abs(gradeBnd - lgv) < 0.00005) {
nearest = i;
break;
}
}
if (nearest === undefined) {
return null;
}
if (nearest === undefined) {
return null;
}
return gptConfig.grades[nearest];
},
}}
axisBottom={null}
{...DEFAULT_BAR_PROPS}
/>
</div>
</Card>
return gptConfig.grades[nearest];
},
}}
axisBottom={null}
{...DEFAULT_BAR_PROPS}
labelSkipWidth={40}
/>
</div>
);
}
@@ -68,7 +68,9 @@ function GPTSearchResult({
playtype
)} profile, if they have one.`}
>
<Icon type="exchange-alt" />
<span>
<Icon type="exchange-alt" />
</span>
</QuickTooltip>
</Link>
</strong>
@@ -21,7 +21,7 @@ export default function BMSDifficultyCell({
}}
>
<span>{levelText}</span>
<TierlistInfoPart chart={chart} />
<TierlistInfoPart chart={chart} game="bms" />
{!chart.isPrimary && (
<QuickTooltip tooltipContent="This chart is an alternate, old chart.">
<div>
@@ -30,16 +30,14 @@ export default function DifficultyCell({
}}
>
{!alwaysShort && (
<>
<span className="d-none d-lg-block">{FormatDifficulty(chart, game)}</span>
{Object.keys(chart.tierlistInfo).length > 0 && (
<TierlistInfoPart chart={chart} />
)}
</>
<span className="d-none d-lg-block">{FormatDifficulty(chart, game)}</span>
)}
<span className={!alwaysShort ? "d-lg-none" : ""}>
{FormatDifficultyShort(chart, game)}
</span>
{Object.keys(chart.tierlistInfo).length > 0 && (
<TierlistInfoPart chart={chart} game={game} />
)}
{!chart.isPrimary && (
<QuickTooltip tooltipContent="This chart is an alternate, old chart.">
<div>
@@ -2,47 +2,61 @@ import QuickTooltip from "components/layout/misc/QuickTooltip";
import Icon from "components/util/Icon";
import Muted from "components/util/Muted";
import React from "react";
import { ChartDocument } from "tachi-common";
import { ChartDocument, Game, GetGamePTConfig } from "tachi-common";
import MiniTable from "../components/MiniTable";
// I hate the fact object.keys doesnt infer types.
type K = keyof ChartDocument["tierlistInfo"];
export default function TierlistInfoPart({ chart }: { chart: ChartDocument }) {
const keys = Object.keys(chart.tierlistInfo) as K[];
export default function TierlistInfoPart({ chart, game }: { chart: ChartDocument; game: Game }) {
const keys = GetGamePTConfig(game, chart.playtype).tierlists.filter(e => chart.tierlistInfo[e]);
return (
<QuickTooltip
tooltipContent={
<MiniTable className="table-sm">
<thead>
<tr>
<th>Tierlist</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{keys.map(k => (
<tr key={k}>
<td>{k}</td>
<td>
{chart.tierlistInfo[k]!.text} ({chart.tierlistInfo[k]!.value})
{chart.tierlistInfo[k]!.individualDifference && (
<>
<br />
<Icon type="balance-scale-left" />
</>
)}
</td>
</tr>
))}
</tbody>
</MiniTable>
}
>
<div>
<Muted>{keys.map(k => chart.tierlistInfo[k]!.text).join(" / ")}</Muted>
<>
<div className="d-none d-lg-block">
<QuickTooltip
tooltipContent={
<MiniTable className="table-sm">
<thead>
<tr>
<th>Tierlist</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{keys.map(k => (
<tr key={k}>
<td>{k}</td>
<td>
{chart.tierlistInfo[k]!.text} (
{chart.tierlistInfo[k]!.value})
{chart.tierlistInfo[k]!.individualDifference && (
<>
<br />
<Icon type="balance-scale-left" />
</>
)}
</td>
</tr>
))}
</tbody>
</MiniTable>
}
>
<div>
<Muted>{keys.map(k => chart.tierlistInfo[k]!.text).join(" / ")}</Muted>
</div>
</QuickTooltip>
</div>
</QuickTooltip>
<div className="d-block d-lg-none">
<Muted>
{keys.map(k => (
<>
{chart.tierlistInfo[k]!.text} <br />
</>
))}
</Muted>
</div>
</>
);
}
@@ -56,7 +56,10 @@ export default function FilterDirectivesIndicator<D>({
component={
<>
You can use{" "}
<a rel="noreferrer" href="#">
<a
rel="noreferrer"
href="https://tachi.readthedocs.io/en/latest/user/filter-directives/"
>
<strong>Filter Directives</strong>
</a>{" "}
inside this filter box.
@@ -79,7 +82,10 @@ export default function FilterDirectivesIndicator<D>({
<p>
This is a slightly advanced feature for{" "}
<HoverText hover="nerds">power users</HoverText>. You can read about it{" "}
<a href="#">here</a>.<br />
<a href="https://tachi.readthedocs.io/en/latest/user/filter-directives/">
here
</a>
.<br />
The quick explanation is that you can use directives like{" "}
<code>Title:conflict Score:&gt;=100</code> to perform advanced filters!
</p>
@@ -31,7 +31,7 @@ export default function BMSFolderTable({
const headers: Header<FolderDataset<"bms:7K" | "bms:14K">[0]>[] = [
[
"Chart",
"Ch.",
"Chart",
NumericSOV(
x =>
x.tierlistInfo["sgl-EC"]?.value ?? x.tierlistInfo["sgl-HC"]?.value ?? x.levelNum
@@ -35,7 +35,7 @@ export default function GenericFolderTable({
const gptConfig = GetGamePTConfig(game, playtype);
const headers: Header<FolderDataset[0]>[] = [
["Chart", "Ch.", NumericSOV(x => x.levelNum)],
["Chart", "Chart", NumericSOV(x => x.levelNum)],
FolderIndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.__related.pb?.scoreData.percent ?? -Infinity)],
@@ -39,7 +39,7 @@ export default function IIDXFolderTable({
const headers: Header<FolderDataset<"iidx:SP" | "iidx:DP">[0]>[] = [
[
"Chart",
"Ch.",
"Chart",
NumericSOV(
x => x.tierlistInfo["kt-NC"]?.value ?? x.tierlistInfo["kt-HC"]?.value ?? x.levelNum
),
@@ -31,7 +31,7 @@ export default function MusecaFolderTable({
playtype: Playtype;
}) {
const headers: Header<FolderDataset<"museca:Single">[0]>[] = [
["Chart", "Ch.", NumericSOV(x => x.levelNum)],
["Chart", "Chart", NumericSOV(x => x.levelNum)],
FolderIndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.__related.pb?.scoreData.percent ?? -Infinity)],
@@ -30,7 +30,7 @@ export default function SDVXFolderTable({
playtype: Playtype;
}) {
const headers: Header<FolderDataset<"sdvx:Single">[0]>[] = [
["Chart", "Ch.", NumericSOV(x => x.levelNum)],
["Chart", "Chart", NumericSOV(x => x.levelNum)],
FolderIndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.__related.pb?.scoreData.percent ?? -Infinity)],
@@ -48,7 +48,7 @@ export default function BMSPBTable({
const headers: Header<PBDataset<"bms:7K" | "bms:14K">[0]>[] = [
[
"Chart",
"Ch.",
"Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["sgl-EC"]?.value ??
@@ -40,7 +40,7 @@ export default function GenericPBTable({
const gptConfig = GetGamePTConfig(game, playtype);
const headers: Header<PBDataset[0]>[] = [
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
@@ -48,7 +48,7 @@ export default function IIDXPBTable({
const headers: Header<PBDataset<"iidx:SP" | "iidx:DP">[0]>[] = [
[
"Chart",
"Ch.",
"Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["kt-NC"]?.value ??
@@ -34,7 +34,7 @@ export default function MusecaPBTable({
playtype: "Single";
}) {
const headers: Header<PBDataset<"museca:Single">[0]>[] = [
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
@@ -33,7 +33,7 @@ export default function SDVXPBTable({
playtype: "7K" | "14K";
}) {
const headers: Header<PBDataset<"sdvx:Single">[0]>[] = [
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
@@ -34,7 +34,7 @@ export default function BMSScoreTable({
[
[
"Chart",
"Ch.",
"Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["sgl-EC"]?.value ??
@@ -36,7 +36,7 @@ export default function GenericScoreTable({
dataset={dataset}
pageLen={pageLen}
headers={[
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
@@ -42,7 +42,7 @@ export default function IIDXScoreTable({
[
[
"Chart",
"Ch.",
"Chart",
NumericSOV(
x =>
x.__related.chart.tierlistInfo["kt-NC"]?.value ??
@@ -30,7 +30,7 @@ export default function MusecaScoreTable({
pageLen={pageLen}
headers={
[
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
@@ -30,7 +30,7 @@ export default function SDVXScoreTable({
pageLen={pageLen}
headers={
[
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
IndicatorHeader,
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
+1 -1
View File
@@ -64,7 +64,7 @@ export function UserHeaderBody({ reqUser }: { reqUser: PublicUserDocument }) {
href="https://steamcommunity.com/id/"
mode="steam"
/>
<ConditionalSocialMediaRender href="https://twitch.tv/" mode="twitch" />
<ConditionalSocialMediaRender href="https://twitCharttv/" mode="twitch" />
<ConditionalSocialMediaRender href="https://twitter.com/" mode="twitter" />
<ConditionalSocialMediaRender
href="https://youtube.com/channel/"
+17
View File
@@ -0,0 +1,17 @@
import { Game } from "tachi-common";
const scales: Record<Game, number> = {
iidx: 2,
bms: 2,
chunithm: 1,
ddr: 1,
gitadora: 1,
maimai: 1,
museca: 5,
sdvx: 5,
usc: 5,
};
export function GetGradeChartExpScale(game: Game) {
return scales[game];
}
+2
View File
@@ -63,6 +63,7 @@ export const loggedInSplashes = [
"Play ULTRAKILL.",
"A Cutting-Edge Score Tracker (heh).",
"Winnipeg is a warm, nice place to live.",
"I accidentally the score tracker.",
];
// Stuff that's bad for the kids, you know?
@@ -70,4 +71,5 @@ export const contentiousSplashes = [
"Formerly Chucks",
"Node.js Is Bad Ass Rock Star Tech",
"i'm obligated to ask for you to seek immediate psychological attention",
"Is IIDX still a good game or are you all basically stupid?",
];
+51
View File
@@ -0,0 +1,51 @@
import { Game, GPTTierlists, IDStrings, IIDX_LAMPS, ScoreDocument } from "tachi-common";
import { Playtype } from "types/tachi";
type ScaleAchievedFns = {
[I in IDStrings]: {
[K in GPTTierlists[I]]: ((k: ScoreDocument<I>) => boolean) | null;
};
};
const ScaleNameAchievedFns: ScaleAchievedFns = {
"bms:14K": {
"sgl-EC": k => k.scoreData.lampIndex >= IIDX_LAMPS.EASY_CLEAR,
"sgl-HC": k => k.scoreData.lampIndex >= IIDX_LAMPS.HARD_CLEAR,
},
"bms:7K": {
"sgl-EC": k => k.scoreData.lampIndex >= IIDX_LAMPS.EASY_CLEAR,
"sgl-HC": k => k.scoreData.lampIndex >= IIDX_LAMPS.HARD_CLEAR,
},
"chunithm:Single": {},
"ddr:DP": {},
"ddr:SP": {},
"gitadora:Dora": {},
"gitadora:Gita": {},
"iidx:DP": {},
"iidx:SP": {
"kt-NC": k => k.scoreData.lampIndex >= IIDX_LAMPS.CLEAR,
"kt-HC": k => k.scoreData.lampIndex >= IIDX_LAMPS.HARD_CLEAR,
"kt-EXHC": k => k.scoreData.lampIndex >= IIDX_LAMPS.EX_HARD_CLEAR,
},
"maimai:Single": {},
"museca:Single": {
"tachi-score": null,
},
"sdvx:Single": {},
"usc:Single": {},
};
export function GetScaleAchievedFn(
game: Game,
playtype: Playtype,
tierlist: GPTTierlists[IDStrings]
) {
const scl = ScaleNameAchievedFns[`${game}:${playtype}` as IDStrings];
if (!scl) {
throw new Error(`Invalid game + pt combination ${game}:${playtype}. Can't find scale.`);
}
// @ts-expect-error hack haha
return scl[tierlist];
}