Files
zkldi_Tachi/client/src/components/user/UGPTStatCreator.tsx
T
zkldi cbc83c74e4 checkpoint: 34 (100% tests passing)
do you guys even realise how insane this is
2023-01-19 16:39:42 +00:00

320 lines
7.8 KiB
TypeScript

import { APIFetchV1 } from "util/api";
import { CreateSongMap } from "util/data";
import { useFormik } from "formik";
import React, { ChangeEventHandler, useContext, useEffect, useState } from "react";
import { Button, Form, Modal } from "react-bootstrap";
import {
FolderDocument,
FormatDifficulty,
Game,
GetGamePTConfig,
UserDocument,
ShowcaseStatDetails,
Playtype,
} from "tachi-common";
import { GamePT, SetState } from "types/react";
import { SongChartsSearch } from "types/api-returns";
import DebounceSearch from "components/util/DebounceSearch";
import Muted from "components/util/Muted";
import { UserContext } from "context/UserContext";
interface Props {
reqUser: UserDocument;
game: Game;
playtype: Playtype;
onCreate: (stat: ShowcaseStatDetails) => void;
show: boolean;
setShow: SetState<boolean>;
}
export default function UGPTStatCreator({
reqUser,
game,
playtype,
onCreate,
show,
setShow,
}: Props) {
const formik = useFormik({
initialValues: {
mode: "chart",
metric: "lamp",
folderID: undefined,
chartID: undefined,
},
onSubmit: (values) => {
let stat: ShowcaseStatDetails;
if (values.mode === "chart") {
stat = {
mode: "chart",
metric: values.property as ShowcaseStatDetails["metric"],
chartID: values.chartID ?? "",
};
} else if (values.mode === "folder") {
stat = {
mode: "folder",
metric: values.property as "lamp" | "score" | "percent" | "grade",
folderID: values.folderID ?? "",
gte,
};
} else {
throw new Error(`Unknown values.mode ${values.mode}.`);
}
onCreate(stat);
setShow(false);
},
});
useEffect(() => {
if (formik.values.mode === "folder" && formik.values.property === "playcount") {
formik.setValues({ ...formik.values, metric: "lamp" });
}
}, [formik.values.mode]);
const { user } = useContext(UserContext);
const [chartData, setChartData] = useState<{ chartID: string; name: string }[]>([]);
const [chartSearch, setChartSearch] = useState("");
const [requesterHasPlayed, setRequesterHasPlayed] = useState(user !== null);
const [folderData, setFolderData] = useState<{ folderID: string; name: string }[]>([]);
const [folderSearch, setFolderSearch] = useState("");
const [gte, setGte] = useState(0);
useEffect(() => {
(async () => {
const search = folderSearch;
const params = new URLSearchParams({
search,
});
const res = await APIFetchV1<FolderDocument[]>(
`/games/${game}/${playtype}/folders?${params.toString()}`,
{},
false,
true
);
if (!res.success) {
throw new Error(res.description);
}
setFolderData(res.body.map((e) => ({ folderID: e.folderID, name: e.title })));
})();
}, [folderSearch]);
useEffect(() => {
(async () => {
const search = chartSearch;
const params = new URLSearchParams({
search,
});
if (requesterHasPlayed) {
params.set("requesterHasPlayed", "true");
}
const res = await APIFetchV1<SongChartsSearch>(
`/games/${game}/${playtype}/charts?${params.toString()}`,
{},
false,
true
);
if (!res.success) {
throw new Error(res.description);
}
const songMap = CreateSongMap(res.body.songs);
const data = [];
for (const chart of res.body.charts) {
const song = songMap.get(chart.songID);
data.push({
chartID: chart.chartID,
name: `${song!.title} ${FormatDifficulty(chart, game)}`,
});
}
setChartData(data);
})();
}, [chartSearch, requesterHasPlayed]);
return (
<Modal show={show} onHide={() => setShow(false)}>
<Modal.Header closeButton>
<Modal.Title>Showcase Stat Creator</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={formik.handleSubmit}>
<Form.Group>
<Form.Label>Mode</Form.Label>
<Form.Control
as="select"
id="mode"
value={formik.values.mode}
onChange={formik.handleChange}
>
<option value="chart">Chart</option>
<option value="folder">Folder</option>
</Form.Control>
<Form.Text>What kind of stat should this be?</Form.Text>
</Form.Group>
<Form.Group>
<Form.Label>Property</Form.Label>
<Form.Control
as="select"
id="property"
value={formik.values.property}
onChange={formik.handleChange}
>
<option value="lamp">Lamp</option>
<option value="grade">Grade</option>
<option value="percent">Percent</option>
<option value="score">Score</option>
{formik.values.mode === "chart" && (
<option value="playcount">Playcount</option>
)}
</Form.Control>
<Form.Text>What kind of statistic should this check for?</Form.Text>
</Form.Group>
{formik.values.mode === "chart" ? (
<Form.Group>
<Form.Label>Chart</Form.Label>
<DebounceSearch setSearch={setChartSearch} placeholder="Chart Name" />
{user && (
<Form.Check
id="requesterHasPlayed"
checked={requesterHasPlayed}
onChange={(e) => setRequesterHasPlayed(e.target.checked)}
className="mt-4 mb-4"
label="Only show charts you've played?"
/>
)}
{chartData.length ? (
<Form.Control
id="chartID"
value={formik.values.chartID}
onChange={formik.handleChange}
as="select"
>
<option value="">Select a chart...</option>
{chartData.map((e, i) => (
<option key={i} value={e.chartID}>
{e.name}
</option>
))}
</Form.Control>
) : (
<Muted>Your search returned nothing... :(</Muted>
)}
</Form.Group>
) : (
<>
<Form.Group>
<Form.Label>Target</Form.Label>
<FolderGTESelect
value={gte}
onChange={(e) => setGte(Number(e.target.value))}
{...{
game,
playtype,
property: formik.values.property,
}}
/>
</Form.Group>
<Form.Group>
<Form.Label>Folder</Form.Label>
<DebounceSearch
setSearch={setFolderSearch}
placeholder="Folder Name"
/>
{folderData.length ? (
<Form.Control
id="folderID"
value={formik.values.folderID}
onChange={formik.handleChange}
as="select"
className="mt-4"
>
<option value="">Select a folder...</option>
{folderData.map((e, i) => (
<option key={i} value={e.folderID}>
{e.name}
</option>
))}
</Form.Control>
) : (
<></>
)}
</Form.Group>
</>
)}
<Form.Group className="d-flex justify-content-end">
<Button
disabled={formik.values.mode === "chart" && !formik.values.chartID}
type="submit ml-auto"
>
Submit
</Button>
</Form.Group>
</Form>
</Modal.Body>
</Modal>
);
}
function FolderGTESelect({
property,
game,
playtype,
value,
onChange,
}: {
property: string;
value: number;
onChange: ChangeEventHandler<HTMLSelectElement | HTMLInputElement>;
} & GamePT) {
const gptConfig = GetGamePTConfig(game, playtype);
const props = { value, onChange };
if (property === "grade") {
return (
<select className="form-control" {...props}>
{gptConfig.grades.map((e, i) => (
<option key={i} value={i}>
{e}
</option>
))}
</select>
);
} else if (property === "lamp" || property === "playcount") {
return (
<select className="form-control" {...props}>
{gptConfig.lamps.map((e, i) => (
<option key={i} value={i}>
{e}
</option>
))}
</select>
);
} else if (property === "percent") {
return (
<input
className="form-control"
type="number"
min={0}
max={gptConfig.percentMax}
{...props}
/>
);
} else if (property === "score") {
return <input className="form-control" type="number" min={0} {...props} />;
}
throw new Error(`Invalid property ${property}.`);
}