mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-12 17:28:31 +03:00
320 lines
7.8 KiB
TypeScript
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}.`);
|
|
}
|