yet another large nameless update

This commit is contained in:
zkldi
2021-08-06 23:57:50 +01:00
parent 7722ca6759
commit b71e56f106
29 changed files with 1036 additions and 973 deletions
+1 -1
View File
@@ -61,7 +61,7 @@
"postcss-normalize": "7.0.1",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.1",
"react": "17",
"react": "^17.0.2",
"react-async": "10.0.1",
"react-bootstrap": "1.0.1",
"react-dom": "16.12.0",
+627 -841
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -49,6 +49,7 @@
}
],
"@typescript-eslint/explicit-function-return-type": "off",
"react/no-unescaped-entities": ["error", {"forbid": [">", "}"]}],
"react/display-name": "off",
"import/no-unresolved": "off",
"react/prop-types": "warn",
+1 -1
View File
@@ -77,7 +77,7 @@ export default function LoginPage() {
<div className="text-center mb-10 mb-lg-20">
<h3 className="font-size-h1">Log In</h3>
<span className="font-weight-bold text-dark-50">
Don&apos;t have an account?
Don't have an account?
</span>
<Link to="/register" className="font-weight-bold ml-2">
Sign Up!
+1 -1
View File
@@ -154,7 +154,7 @@ export default function RegisterPage() {
<br />I know it sucks to wait around, but the few rules we have are
enforced strictly.
<br />
The last thing you&apos;d want is to accidentally get yourself
The last thing you'd want is to accidentally get yourself
banned!
</p>
) : (
@@ -137,7 +137,7 @@ export default function CreditsPage() {
These people (or teams) have created programs that have been massively
beneficial.
<br />
Note: I&apos;m leaving out huge teams here (like MongoDB and TypeScript), and
Note: I'm leaving out huge teams here (like MongoDB and TypeScript), and
focusing more on the smaller guys.
</p>
@@ -169,7 +169,7 @@ export default function CreditsPage() {
<ExternalLink href="https://node-tap.org">Node TAP</ExternalLink>
<br />
<small>
Node TAP is a testing framework that is.not.a(&quot;mess&quot;)
Node TAP is a testing framework that is.not.a("mess")
</small>
</li>
<li>
@@ -523,7 +523,7 @@ function StatShowcase({ reqUser, game, playtype }: { reqUser: PublicUserDocument
placement="top"
overlay={
<Tooltip id="quick-panel-tooltip">
Return to {reqUser.username}&apos;s selected stats.
Return to {reqUser.username}'s selected stats.
</Tooltip>
}
>
@@ -104,7 +104,11 @@ function PBsOverview({ reqUser, game, playtype }: { reqUser: PublicUserDocument
style={{ height: 500 }}
{...{ isLoading, error, dataset: data }}
>
<IIDXPBTable reqUser={reqUser} dataset={data!} />
<IIDXPBTable
reqUser={reqUser}
dataset={data!}
playtype={playtype as "SP" | "DP"}
/>
</LoadingWrapper>
) : (
<PBsSearch {...{ reqUser, game, playtype, search }} />
@@ -172,7 +176,12 @@ function PBsSearch({
return (
<LoadingWrapper style={{ height: 500 }} {...{ isLoading, error, dataset: data }}>
<IIDXPBTable reqUser={reqUser} indexCol={false} dataset={data!} />
<IIDXPBTable
reqUser={reqUser}
indexCol={false}
dataset={data!}
playtype={playtype as "SP" | "DP"}
/>
</LoadingWrapper>
);
}
+7 -2
View File
@@ -9,7 +9,7 @@ export default function GameRoutes() {
const { game } = useParams<{ game: string }>();
if (!IsSupportedGame(game)) {
return <ErrorPage statusCode={404} customMessage="This game is not supported." />;
return <ErrorPage statusCode={404} customMessage={`The game ${game} is not supported.`} />;
}
const gameConfig = GetGameConfig(game);
@@ -44,7 +44,12 @@ function GamePlaytypeRoutes({ game }: { game: Game }) {
const { playtype } = useParams<{ playtype: string }>();
if (!IsSupportedPlaytype(game, playtype)) {
return <ErrorPage statusCode={400} customMessage="This playtype is not supported." />;
return (
<ErrorPage
statusCode={400}
customMessage={`The playtype ${playtype} is not supported.`}
/>
);
}
return (
+7 -2
View File
@@ -69,7 +69,7 @@ function UserGameRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
const { game } = useParams<{ game: string }>();
if (!IsSupportedGame(game)) {
return <ErrorPage statusCode={400} customMessage="This game is not supported." />;
return <ErrorPage statusCode={400} customMessage={`The game ${game} is not supported.`} />;
}
const gameConfig = GetGameConfig(game);
@@ -100,7 +100,12 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: PublicUserDocument
const { playtype } = useParams<{ playtype: string }>();
if (!IsSupportedPlaytype(game, playtype)) {
return <ErrorPage statusCode={400} customMessage="This playtype is not supported." />;
return (
<ErrorPage
statusCode={400}
customMessage={`The playtype ${playtype} is not supported.`}
/>
);
}
const { isLoading, error, data } = useQuery<
@@ -159,7 +159,7 @@ export default function SearchBar() {
>
<div className="btn btn-icon btn-hover-transparent-white btn-lg mr-1">
<span className="svg-icon svg-icon-xl">
<SVG src={toAbsoluteUrl("/media/svg/icons/General/Search.svg")} />
{/* <SVG src={toAbsoluteUrl("/media/svg/icons/General/Search.svg")} /> */}
</span>
</div>
</div>
@@ -8,8 +8,7 @@ export default function IIDXLampCell({
}: {
sc: ScoreDocument<"iidx:SP" | "iidx:DP"> | PBScoreDocument<"iidx:SP" | "iidx:DP">;
}) {
// doesn't matter, both have same colours
const gptConfig = GetGamePTConfig("iidx", "SP");
const gptConfig = GetGamePTConfig("iidx", sc.playtype);
let gaugeText = null;
@@ -1,11 +1,12 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState } from "react";
import { Collapse } from "react-bootstrap";
import { JustChildren } from "types/react";
export default function DropdownRow({
children,
className,
dropdown,
}: { dropdown: React.ReactNode } & JustChildren) {
}: { dropdown: React.ReactNode; className?: string } & JustChildren) {
const [showDropdown, setShowDropdown] = useState(false);
const [renderDropdown, setRenderDropdown] = useState(false);
@@ -24,7 +25,9 @@ export default function DropdownRow({
return (
<>
<tr onClick={() => setShowDropdown(!showDropdown)}>{children}</tr>
<tr className={className} onClick={() => setShowDropdown(!showDropdown)}>
{children}
</tr>
<tr className="expandable-pseudo-row">
<td colSpan={100}>
<Collapse in={showDropdown}>
@@ -147,7 +147,7 @@ export default function FilterDirectivesIndicator<D>({
</table>
) : (
<span className="text-center">
Looks like this table has no data. We can&apos;t show anything about the
Looks like this table has no data. We can't show anything about the
directives.
</span>
)}
@@ -7,7 +7,7 @@ import NoDataWrapper from "./NoDataWrapper";
import PageSelector from "./PageSelector";
import SortableTH from "./SortableTH";
import FilterDirectivesIndicator from "./FilterDirectivesIndicator";
import { ComposeSearchFunction, ValueGetter, ValueGetterOrHybrid } from "util/ztable/search";
import { ComposeSearchFunction, ValueGetterOrHybrid } from "util/ztable/search";
export interface ZTableTHProps {
changeSort: (str: string) => void;
@@ -140,7 +140,11 @@ export default function TachiTable<D>({
<table className="table table-striped table-hover table-vertical-center text-center table-responsive-md">
<thead>{headersRow}</thead>
<tbody>
<NoDataWrapper>{window.map(rowFunction)}</NoDataWrapper>
<NoDataWrapper>
{window.map((e, i) => (
<React.Fragment key={i}>{rowFunction(e)}</React.Fragment>
))}
</NoDataWrapper>
</tbody>
</table>
</div>
@@ -1,33 +1,33 @@
import IIDXLampChart from "components/charts/IIDXLampChart";
import DeltaCell from "components/tables/cells/DeltaCell";
import IIDXLampCell from "components/tables/cells/IIDXLampCell";
import ScoreCell from "components/tables/cells/ScoreCell";
import TimestampCell from "components/tables/cells/TimestampCell";
import MiniTable from "components/tables/components/MiniTable";
import ExternalLink from "components/util/ExternalLink";
import Icon from "components/util/Icon";
import Loading from "components/util/Loading";
import SelectButton from "components/util/SelectButton";
import React, { useMemo, useState } from "react";
import { useQuery } from "react-query";
import { ChartDocument, PBScoreDocument, PublicUserDocument } from "tachi-common";
import { ChartDocument, PublicUserDocument } from "tachi-common";
import { UGPTChartPBComposition } from "types/api-returns";
import { GamePT } from "types/react";
import { GamePT, SetState } from "types/react";
import { APIFetchV1 } from "util/api";
import { IsScore } from "util/asserts";
import CommentContainer from "./components/CommentContainer";
import DropdownStructure from "./components/DropdownStructure";
import { IIDXGraphsComponent, ModsTable, ScoreInfo } from "./components/IIDXScoreDropdownParts";
import JudgementTable from "./components/JudgementTable";
import PBNote from "./components/PBNote";
import ScoreEditButtons from "./components/ScoreEditButtons";
export default function IIDXPBDropdown({
game,
playtype,
reqUser,
chart,
scoreState,
}: {
reqUser: PublicUserDocument;
chart: ChartDocument<"iidx:SP">;
chart: ChartDocument<"iidx:SP" | "iidx:DP">;
scoreState: {
highlight: boolean;
setHighlight: SetState<boolean>;
};
} & GamePT) {
const [view, setView] = useState<"pb" | "scorePB" | "lampPB" | "bpPB" | "history">("pb");
@@ -81,7 +81,11 @@ export default function IIDXPBDropdown({
</>
}
>
{view === "history" ? <></> : <DocumentDisplay pbData={data} view={view} />}
{view === "history" ? (
<></>
) : (
<DocumentDisplay pbData={data} view={view} scoreState={scoreState} />
)}
</DropdownStructure>
);
}
@@ -89,9 +93,14 @@ export default function IIDXPBDropdown({
function DocumentDisplay({
pbData,
view,
scoreState,
}: {
pbData: UGPTChartPBComposition<"iidx:SP">;
view: "pb" | "scorePB" | "lampPB" | "bpPB";
scoreState: {
highlight: boolean;
setHighlight: SetState<boolean>;
};
}) {
const idMap = {
scorePB: pbData.pb.composedFrom.scorePB,
@@ -108,8 +117,7 @@ function DocumentDisplay({
return pbData.scores.filter(e => e.scoreID === idMap[view])[0];
}, [view]);
// @ts-expect-error temp
currentDoc.comment = "THIS IS AN EXAMPLE COMMENT.";
const [comment, setComment] = useState(IsScore(currentDoc) ? currentDoc.comment : null);
return (
<>
@@ -119,11 +127,11 @@ function DocumentDisplay({
{IsScore(currentDoc) ? (
<>
<ScoreInfo score={currentDoc} />
{currentDoc.comment && (
<div className="col-12">
<em>&quot;{currentDoc.comment}&quot;</em>
</div>
)}
<CommentContainer comment={comment} />
<ScoreEditButtons
score={currentDoc}
scoreState={{ ...scoreState, comment, setComment }}
/>
</>
) : (
<div className="col-12">
@@ -3,15 +3,16 @@ import Loading from "components/util/Loading";
import SelectButton from "components/util/SelectButton";
import React, { useState } from "react";
import { UGPTChartPBComposition } from "types/api-returns";
import { GamePT } from "types/react";
import { GamePT, SetState } from "types/react";
import { APIFetchV1 } from "util/api";
import DropdownStructure from "./components/DropdownStructure";
import { PublicUserDocument, ChartDocument, ScoreDocument } from "tachi-common";
import { useQuery } from "react-query";
import { IsScore } from "util/asserts";
import { IIDXGraphsComponent, ScoreInfo, ModsTable } from "./components/IIDXScoreDropdownParts";
import { IIDXGraphsComponent, ModsTable } from "./components/IIDXScoreDropdownParts";
import JudgementTable from "./components/JudgementTable";
import PBNote from "./components/PBNote";
import CommentContainer from "./components/CommentContainer";
import ScoreEditButtons from "./components/ScoreEditButtons";
export default function IIDXScoreDropdown({
thisScore,
@@ -19,10 +20,17 @@ export default function IIDXScoreDropdown({
playtype,
reqUser,
chart,
scoreState,
}: {
thisScore: ScoreDocument<"iidx:SP" | "iidx:DP">;
reqUser: PublicUserDocument;
chart: ChartDocument<"iidx:SP">;
chart: ChartDocument<"iidx:SP" | "iidx:DP">;
scoreState: {
highlight: boolean;
comment: string | null;
setHighlight: SetState<boolean>;
setComment: SetState<string | null>;
};
} & GamePT) {
const [view, setView] = useState<"vsPB" | "moreInfo" | "history">("moreInfo");
@@ -55,19 +63,14 @@ export default function IIDXScoreDropdown({
let content = null;
thisScore.comment = "THIS IS AN EXAMPLE COMMENT.";
if (view === "moreInfo") {
content = (
<>
<div className="col-9">
<div className="row">
<IIDXGraphsComponent score={thisScore} />
{thisScore.comment && (
<div className="col-12">
<em>&quot;{thisScore.comment}&quot;</em>
</div>
)}
<CommentContainer comment={scoreState.comment} />
<ScoreEditButtons score={thisScore} scoreState={scoreState} />
</div>
</div>
<div className="col-3 align-self-center">
@@ -0,0 +1,13 @@
import React from "react";
export default function CommentContainer({ comment }: { comment: string | null }) {
if (!comment) {
return null;
}
return (
<div className="col-12">
<em>"{comment}"</em>
</div>
);
}
@@ -0,0 +1,179 @@
import QuickTooltip from "components/layout/misc/QuickTooltip";
import Icon from "components/util/Icon";
import { UserContext } from "context/UserContext";
import React, { useContext, useState } from "react";
import { ScoreDocument } from "tachi-common";
import { Button, Modal, Form } from "react-bootstrap";
import { SetState } from "types/react";
import { APIFetchV1 } from "util/api";
export default function ScoreEditButtons({
score,
scoreState,
}: {
score: ScoreDocument;
scoreState: {
highlight: boolean;
comment: string | null;
setHighlight: SetState<boolean>;
setComment: SetState<string | null>;
};
}) {
const { user } = useContext(UserContext);
if (!user || user.id !== score.userID) {
return null;
}
const { highlight, setHighlight, comment, setComment } = scoreState;
const [show, setShow] = useState(false);
return (
<div className="mt-4 d-flex w-100 justify-content-center">
<div className="btn-group">
{comment ? (
<>
<QuickTooltip text="Edit your comment on this score.">
<Button variant="outline-secondary" onClick={() => setShow(true)}>
<Icon noPad type="file-signature" />
</Button>
</QuickTooltip>
</>
) : (
<QuickTooltip text="Comment on this score.">
<Button variant="outline-secondary" onClick={() => setShow(true)}>
<Icon noPad type="file-signature" />
</Button>
</QuickTooltip>
)}
{highlight ? (
<QuickTooltip text="Unhighlight this score.">
<Button
variant="success"
onClick={() =>
ModifyScore(score.scoreID, { highlight: false }).then(
r => r && setHighlight(false)
)
}
>
<Icon noPad type="star" />
</Button>
</QuickTooltip>
) : (
<QuickTooltip text="Highlight this score.">
<Button
variant="outline-secondary"
onClick={() =>
ModifyScore(score.scoreID, { highlight: true }).then(
r => r && setHighlight(true)
)
}
>
<Icon noPad type="star" />
</Button>
</QuickTooltip>
)}
</div>
<CommentModal
show={show}
setShow={setShow}
comment={comment}
setComment={setComment}
scoreID={score.scoreID}
/>
</div>
);
}
async function ModifyScore(
scoreID: string,
content: { comment?: string | null; highlight?: boolean }
) {
const res = await APIFetchV1(
`/scores/${scoreID}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(content),
},
true,
true
);
return res.success;
}
function CommentModal({
show,
setShow,
comment,
setComment,
scoreID,
}: {
show: boolean;
setShow: SetState<boolean>;
comment: string | null;
setComment: SetState<string | null>;
scoreID: string;
}) {
const [innerComment, setInnerComment] = useState(comment ?? "");
return (
<Modal show={show} onHide={() => setShow(false)}>
<Modal.Header closeButton>
<Modal.Title>Edit Comment</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
ModifyScore(scoreID, { comment: innerComment }).then(r => {
if (r) {
setComment(innerComment);
setShow(false);
}
});
}}
>
<Form.Group>
<div className="input-group">
<input
className="form-control form-control-lg"
type="text"
placeholder={comment ?? "This score was great!"}
value={innerComment}
onChange={e => setInnerComment(e.target.value)}
/>
<div className="input-group-append">
<Button variant="outline-primary" type="submit">
Submit
</Button>
</div>
</div>
</Form.Group>
<QuickTooltip text="Remove your comment on this score.">
<Button
variant="outline-secondary"
onClick={() =>
ModifyScore(scoreID, { comment: null }).then(r => {
if (r) {
setComment(null);
setShow(false);
}
})
}
>
<Icon noPad type="trash" />
</Button>
</QuickTooltip>
</Form>
</Modal.Body>
</Modal>
);
}
@@ -3,11 +3,15 @@ import { FormatDifficulty } from "tachi-common/js/utils/util";
import TitleCell from "../cells/TitleCell";
import RankingCell from "../cells/RankingCell";
import TimestampCell from "../cells/TimestampCell";
import { ChangeOpacity } from "util/color-opacity";
import { NumericSOV, StrSOV } from "util/sorts";
import SelectableRating from "../components/SelectableRating";
import { PBDataset } from "types/tables";
import { GetGamePTConfig, PublicUserDocument, ScoreCalculatedDataLookup } from "tachi-common";
import {
GetGamePTConfig,
PublicUserDocument,
ScoreCalculatedDataLookup,
PBScoreDocument,
} from "tachi-common";
import TachiTable, { Header, ZTableTHProps } from "../components/TachiTable";
import DifficultyCell from "../cells/DifficultyCell";
import ScoreCell from "../cells/ScoreCell";
@@ -16,25 +20,28 @@ import DeltaCell from "../cells/DeltaCell";
import { HumanFriendlyStrToGradeIndex, HumanFriendlyStrToLampIndex } from "util/str-to-num";
import { nanoid } from "nanoid";
import DropdownRow from "../components/DropdownRow";
import IIDXPBDropdown from "./dropdowns/IIDXPBDropdown";
import IIDXPBDropdown from "../dropdowns/IIDXPBDropdown";
import IIDXLampCell from "../cells/IIDXLampCell";
import { IsNullish } from "util/misc";
export default function IIDXPBTable({
dataset,
indexCol = true,
reqUser,
playtype,
}: {
dataset: PBDataset<"iidx:SP">;
dataset: PBDataset<"iidx:SP" | "iidx:DP">;
indexCol?: boolean;
reqUser: PublicUserDocument;
playtype: "SP" | "DP";
}) {
const gptConfig = GetGamePTConfig<"iidx:SP">("iidx", "SP");
const gptConfig = GetGamePTConfig<"iidx:SP" | "iidx:DP">("iidx", playtype);
const [rating, setRating] = useState<ScoreCalculatedDataLookup["iidx:SP"]>(
const [rating, setRating] = useState<ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"]>(
gptConfig.defaultScoreRatingAlg
);
const headers: Header<PBDataset<"iidx:SP">[0]>[] = [
const headers: Header<PBDataset<"iidx:SP" | "iidx:DP">[0]>[] = [
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
["Song", "Song", StrSOV(x => x.__related.song.title)],
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
@@ -45,7 +52,7 @@ export default function IIDXPBTable({
"Rating",
NumericSOV(x => x.calculatedData[rating] ?? 0),
(thProps: ZTableTHProps) => (
<SelectableRating<"iidx:SP">
<SelectableRating<"iidx:SP" | "iidx:DP">
key={nanoid()}
game="iidx"
playtype="SP"
@@ -78,47 +85,69 @@ export default function IIDXPBTable({
ranking: x => x.rankingData.rank,
lamp: {
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
strToNum: HumanFriendlyStrToLampIndex("iidx", "SP"),
strToNum: HumanFriendlyStrToLampIndex("iidx", playtype),
},
grade: {
valueGetter: x => [x.scoreData.grade, x.scoreData.gradeIndex],
strToNum: HumanFriendlyStrToGradeIndex("iidx", "SP"),
strToNum: HumanFriendlyStrToGradeIndex("iidx", playtype),
},
}}
defaultSortMode={indexCol ? "#" : undefined}
rowFunction={pb => (
<DropdownRow
dropdown={
<IIDXPBDropdown
chart={pb.__related.chart}
reqUser={reqUser}
game={"iidx"}
playtype={"SP"}
/>
}
key={pb.chartID}
>
{indexCol && <IndexCell index={pb.__related.index} />}
<DifficultyCell chart={pb.__related.chart} game={"iidx"} />
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="iidx" />
<ScoreCell score={pb} game="iidx" playtype="SP" />
<DeltaCell
game="iidx"
playtype="SP"
score={pb.scoreData.score}
percent={pb.scoreData.percent}
grade={pb.scoreData.grade}
/>
<IIDXLampCell sc={pb} />
<td>
{pb.calculatedData[rating]
? pb.calculatedData[rating]!.toFixed(2)
: "No Data."}
</td>
<RankingCell rankingData={pb.rankingData} />
<TimestampCell time={pb.timeAchieved} />
</DropdownRow>
<Row pb={pb} reqUser={reqUser} indexCol={indexCol} rating={rating} />
)}
/>
);
}
function Row({
pb,
indexCol,
reqUser,
rating,
}: {
pb: PBDataset<"iidx:SP" | "iidx:DP">[0];
indexCol: boolean;
reqUser: PublicUserDocument;
rating: ScoreCalculatedDataLookup["iidx:SP"];
}) {
const [highlight, setHighlight] = useState(pb.highlight);
const scoreState = { highlight, setHighlight };
return (
<DropdownRow
dropdown={
<IIDXPBDropdown
chart={pb.__related.chart}
reqUser={reqUser}
game="iidx"
playtype={pb.playtype}
scoreState={scoreState}
/>
}
className={highlight ? "highlighted-row" : ""}
key={pb.chartID}
>
{indexCol && <IndexCell index={pb.__related.index} />}
<DifficultyCell chart={pb.__related.chart} game={"iidx"} />
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="iidx" />
<ScoreCell score={pb} game="iidx" playtype={pb.playtype} />
<DeltaCell
game="iidx"
playtype={pb.playtype}
score={pb.scoreData.score}
percent={pb.scoreData.percent}
grade={pb.scoreData.grade}
/>
<IIDXLampCell sc={pb} />
<td>
{!IsNullish(pb.calculatedData[rating])
? pb.calculatedData[rating]!.toFixed(2)
: "No Data."}
</td>
<RankingCell rankingData={pb.rankingData} />
<TimestampCell time={pb.timeAchieved} />
</DropdownRow>
);
}
@@ -2,7 +2,6 @@ import React, { useState } from "react";
import { FormatDifficulty } from "tachi-common/js/utils/util";
import TitleCell from "../cells/TitleCell";
import TimestampCell from "../cells/TimestampCell";
import BPICell from "../cells/BPICell";
import { NumericSOV, StrSOV } from "util/sorts";
import SelectableRating from "../components/SelectableRating";
import { ScoreDataset } from "types/tables";
@@ -20,7 +19,8 @@ import { HumanFriendlyStrToGradeIndex, HumanFriendlyStrToLampIndex } from "util/
import { nanoid } from "nanoid";
import IIDXLampCell from "../cells/IIDXLampCell";
import DropdownRow from "../components/DropdownRow";
import IIDXScoreDropdown from "../pbs/dropdowns/IIDXScoreDropdown";
import IIDXScoreDropdown from "../dropdowns/IIDXScoreDropdown";
import { IsNullish } from "util/misc";
export default function IIDXScoreTable({
reqUser,
@@ -81,41 +81,56 @@ export default function IIDXScoreTable({
strToNum: HumanFriendlyStrToGradeIndex("iidx", "SP"),
},
}}
rowFunction={sc => (
<DropdownRow
dropdown={
<IIDXScoreDropdown
chart={sc.__related.chart}
game="iidx"
playtype={sc.playtype}
reqUser={reqUser}
thisScore={sc}
/>
}
>
<DifficultyCell chart={sc.__related.chart} game={"iidx"} />
<TitleCell song={sc.__related.song} chart={sc.__related.chart} game="iidx" />
<ScoreCell score={sc} game="iidx" playtype="SP" />
<DeltaCell
game="iidx"
playtype="SP"
score={sc.scoreData.score}
percent={sc.scoreData.percent}
grade={sc.scoreData.grade}
/>
<IIDXLampCell sc={sc} />
{rating === "BPI" ? (
<BPICell bpi={sc.calculatedData.BPI} />
) : (
<td>
{sc.calculatedData[rating]
? sc.calculatedData[rating]!.toFixed(2)
: "No Data."}
</td>
)}
<TimestampCell time={sc.timeAchieved} />
</DropdownRow>
)}
rowFunction={sc => <Row sc={sc} reqUser={reqUser} rating={rating} />}
/>
);
}
function Row({
sc,
reqUser,
rating,
}: {
sc: ScoreDataset<"iidx:SP" | "iidx:DP">[0];
reqUser: PublicUserDocument;
rating: ScoreCalculatedDataLookup["iidx:SP"];
}) {
const [highlight, setHighlight] = useState(sc.highlight);
const [comment, setComment] = useState(sc.comment);
const scoreState = { highlight, comment, setHighlight, setComment };
return (
<DropdownRow
className={highlight ? "highlighted-row" : ""}
dropdown={
<IIDXScoreDropdown
chart={sc.__related.chart}
game="iidx"
playtype={sc.playtype}
reqUser={reqUser}
thisScore={sc}
scoreState={scoreState}
/>
}
>
<DifficultyCell chart={sc.__related.chart} game={"iidx"} />
<TitleCell song={sc.__related.song} chart={sc.__related.chart} game="iidx" />
<ScoreCell score={sc} game="iidx" playtype="SP" />
<DeltaCell
game="iidx"
playtype="SP"
score={sc.scoreData.score}
percent={sc.scoreData.percent}
grade={sc.scoreData.grade}
/>
<IIDXLampCell sc={sc} />
<td>
{IsNullish(sc.calculatedData[rating])
? sc.calculatedData[rating]!.toFixed(2)
: "No Data."}
</td>
<TimestampCell time={sc.timeAchieved} />
</DropdownRow>
);
}
+1 -1
View File
@@ -33,7 +33,7 @@ export default function UGPTHeader({
<div className="card card-custom">
<div className="card-header">
<h4>
{reqUser.username}&apos;s {gameConfig.name} {playtype} Profile
{reqUser.username}'s {gameConfig.name} {playtype} Profile
</h4>
</div>
<div className="card-body">
+1 -1
View File
@@ -20,7 +20,7 @@ export default function AsyncLoader<T = unknown>({
<Async.Rejected>
{error => (
<div className="text-center">
Fatal Error: {error.message}. That&apos;s not good!
Fatal Error: {error.message}. That's not good!
</div>
)}
</Async.Rejected>
+2 -2
View File
@@ -1,5 +1,5 @@
import React from "react";
export default function Icon({ type }: { type: string }) {
return <i className={`fas fa-${type}`} />;
export default function Icon({ type, noPad }: { type: string; noPad?: boolean }) {
return <i className={`fas fa-${type} ${noPad ? "p-0" : ""}`} />;
}
+4
View File
@@ -7,3 +7,7 @@ export const NO_OP = () => void 0;
export function UppercaseFirst(str: string) {
return str[0].toUpperCase() + str.substring(1);
}
export function IsNullish(value: unknown) {
return value === null || value === undefined;
}