mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-02 11:58:12 +03:00
yet another large nameless update
This commit is contained in:
+1
-1
@@ -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",
|
||||
|
||||
Generated
+627
-841
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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't have an account?
|
||||
Don't have an account?
|
||||
</span>
|
||||
<Link to="/register" className="font-weight-bold ml-2">
|
||||
Sign Up!
|
||||
|
||||
@@ -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'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'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("mess")
|
||||
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}'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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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'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>
|
||||
|
||||
+26
-18
@@ -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>"{currentDoc.comment}"</em>
|
||||
</div>
|
||||
)}
|
||||
<CommentContainer comment={comment} />
|
||||
<ScoreEditButtons
|
||||
score={currentDoc}
|
||||
scoreState={{ ...scoreState, comment, setComment }}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="col-12">
|
||||
+14
-11
@@ -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>"{thisScore.comment}"</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function UGPTHeader({
|
||||
<div className="card card-custom">
|
||||
<div className="card-header">
|
||||
<h4>
|
||||
{reqUser.username}'s {gameConfig.name} {playtype} Profile
|
||||
{reqUser.username}'s {gameConfig.name} {playtype} Profile
|
||||
</h4>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function AsyncLoader<T = unknown>({
|
||||
<Async.Rejected>
|
||||
{error => (
|
||||
<div className="text-center">
|
||||
Fatal Error: {error.message}. That's not good!
|
||||
Fatal Error: {error.message}. That's not good!
|
||||
</div>
|
||||
)}
|
||||
</Async.Rejected>
|
||||
|
||||
@@ -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" : ""}`} />;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user