Merge pull request #632 from TNG-dev/zkldi/session-raise-comments

This commit is contained in:
zkldi
2022-08-17 23:32:32 +01:00
committed by GitHub
5 changed files with 163 additions and 126 deletions
@@ -264,7 +264,7 @@ function TopShowcase({
if (user?.id === bestPlay.userID) {
return (
<Col xs={12}>
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} />
<PlayCard name="Best PB" pb={bestPlay} user={bestUser} />
</Col>
);
}
@@ -273,10 +273,10 @@ function TopShowcase({
return (
<>
<Col xs={12} lg={6}>
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} />
<PlayCard name="Best PB" pb={bestPlay} user={bestUser} />
</Col>
<Col xs={12} lg={6}>
<Card header="Your Score" className="text-center">
<Card header="Your PB" className="text-center">
<Muted>You've not played this chart.</Muted>
</Card>
</Col>
@@ -326,7 +326,7 @@ function PlayCard({
<span className="text-muted">/{pb.rankingData.outOf}</span>
</Col>
<Col lg={6}>
<MiniTable headers={["Score Info"]}>
<MiniTable headers={["PB Info"]}>
<tr>
<ScoreCell score={pb} />
</tr>
@@ -18,7 +18,7 @@ import SelectButton from "components/util/SelectButton";
import { useProfileRatingAlg } from "components/util/useScoreRatingAlg";
import { DateTime } from "luxon";
import React, { useMemo, useState } from "react";
import { Badge } from "react-bootstrap";
import { Badge, Row } from "react-bootstrap";
import {
FormatGame,
GetGameConfig,
@@ -31,6 +31,7 @@ import {
import { SessionReturns, UGPTHistory } from "types/api-returns";
import { GamePT } from "types/react";
import { ScoreDataset } from "types/tables";
import SessionRaiseBreakdown from "components/sessions/SessionRaiseBreakdown";
export default function OverviewPage({
reqUser,
@@ -84,81 +85,76 @@ function LastSession({ reqUser, game, playtype }: { reqUser: PublicUserDocument
data && (
<Card className="mt-4" header="Most Recent Session">
<div className="row d-flex justify-content-center">
{
<>
<div className="col-12 text-center">
<h4 className="display-4">{data.session.name}</h4>
<span className="text-muted">{data.session.desc}</span>
<Divider className="mt-4 mb-8" />
</div>
<div className="col-12 text-center">
<h4 className="display-4">{data.session.name}</h4>
<span className="text-muted">{data.session.desc}</span>
<Divider className="mt-4 mb-8" />
</div>
<div className="col-12 col-lg-3 text-center">
<MiniTable
className="mt-4"
headers={["Information"]}
colSpan={2}
>
<tr>
<td>Started</td>
<td>{FormatTime(data.session.timeStarted)}</td>
</tr>
<tr>
{/* If session ended less than 2 hours ago, it can still be appended to. */}
{/* Kinda - time is a bit nonlinear wrt. importing scores, as scores don't have to have */}
{/* happened at the same time they were imported. */}
{/* that doesn't matter though - this is just a UI thing */}
<td>
{Date.now() - data.session.timeEnded <
ONE_HOUR * 2
? "Last Score"
: "Ended At"}
</td>
<td>
{FormatTime(data.session.timeEnded)}
{/* if the last score was less than 10 mins ago, this session is probably still being populated */}
{Date.now() - data.session.timeEnded <
ONE_MINUTE * 10 && (
<>
<br />
<Badge
variant="primary"
className="mt-2"
>
Ongoing!
</Badge>
</>
)}
</td>
</tr>
<tr>
<td>Duration</td>
<td>
{FormatDuration(
data.session.timeEnded -
data.session.timeStarted
)}
</td>
</tr>
<tr>
<td>Scores</td>
<td>{data.session.scoreInfo.length}</td>
</tr>
<tr>
<td>PBs</td>
<td>{GetPBs(data.session.scoreInfo).length}</td>
</tr>
</MiniTable>
</div>
<div className="col-12 col-lg-9">
<RecentSessionScoreInfo
{...data}
game={game}
playtype={playtype}
reqUser={reqUser}
/>
</div>
</>
}
<div className="col-12 col-lg-3 text-center">
<MiniTable className="mt-4" headers={["Information"]} colSpan={2}>
<tr>
<td>Started</td>
<td>{FormatTime(data.session.timeStarted)}</td>
</tr>
<tr>
{/* If session ended less than 2 hours ago, it can still be appended to. */}
{/* Kinda - time is a bit nonlinear wrt. importing scores, as scores don't have to have */}
{/* happened at the same time they were imported. */}
{/* that doesn't matter though - this is just a UI thing */}
<td>
{Date.now() - data.session.timeEnded < ONE_HOUR * 2
? "Last Score"
: "Ended At"}
</td>
<td>
{FormatTime(data.session.timeEnded)}
{/* if the last score was less than 10 mins ago, this session is probably still being populated */}
{Date.now() - data.session.timeEnded <
ONE_MINUTE * 10 && (
<>
<br />
<Badge variant="primary" className="mt-2">
Ongoing!
</Badge>
</>
)}
</td>
</tr>
<tr>
<td>Duration</td>
<td>
{FormatDuration(
data.session.timeEnded - data.session.timeStarted
)}
</td>
</tr>
<tr>
<td>Scores</td>
<td>{data.session.scoreInfo.length}</td>
</tr>
<tr>
<td>PBs</td>
<td>{GetPBs(data.session.scoreInfo).length}</td>
</tr>
</MiniTable>
</div>
<div className="col-12 col-lg-9">
<Row>
<SessionRaiseBreakdown
sessionData={data.sessionData}
setScores={() => void 0}
/>
</Row>
</div>
<div className="col-12">
<Divider />
<RecentSessionScoreInfo
session={data.session}
sessionData={data.sessionData}
reqUser={reqUser}
/>
</div>
</div>
</Card>
)
@@ -170,14 +166,14 @@ function LastSession({ reqUser, game, playtype }: { reqUser: PublicUserDocument
function RecentSessionScoreInfo({
session,
sessionData,
game,
playtype,
reqUser,
}: {
session: SessionDocument;
sessionData: SessionReturns;
reqUser: PublicUserDocument;
} & GamePT) {
}) {
const { game, playtype } = session;
const highlightedScores = sessionData.scores.filter((e) => e.highlight);
const [mode, setMode] = useState<"highlight" | "best" | "recent">(
@@ -40,7 +40,7 @@ export default function SessionOverview({
<Col xs={12}>
<Divider />
<Card header="Breakdown">
<Card header="Raise Breakdown">
<Row>
<SessionRaiseBreakdown sessionData={sessionData} setScores={setScores} />
</Row>
@@ -30,7 +30,7 @@ import {
TableDocument,
} from "tachi-common";
import { SessionReturns } from "types/api-returns";
import { ModifyScore } from "components/tables/dropdowns/components/ScoreEditButtons";
import { CommentModal, ModifyScore } from "components/tables/dropdowns/components/ScoreEditButtons";
import { SetState } from "types/react";
import { ScoreDataset } from "types/tables";
@@ -117,7 +117,7 @@ export default function SessionRaiseBreakdown({
<>
<div className="col-12">
<div className="row">
<div className="col-6 col-lg-6 offset-lg-3">
<div className="col-12 col-lg-6 offset-lg-3">
<div className="d-flex justify-content-center">
<div className="btn-group">
<SelectButton value={view} setValue={setView} id="lamps">
@@ -138,7 +138,7 @@ export default function SessionRaiseBreakdown({
</div>
</div>
<div className="col-6 col-lg-3">
<div className="col-lg-3 d-none d-lg-block">
<select
className="form-control ml-auto"
value={filter}
@@ -357,7 +357,7 @@ function ElementStatTable({
fullSize?: boolean;
}) {
function makeModifyScoreFn(score: ScoreDocument) {
return ({ highlight, comment }: { highlight?: boolean; comment?: string }) => {
return ({ highlight, comment }: { highlight?: boolean; comment?: string | null }) => {
const scoreID = score.scoreID;
ModifyScore(scoreID, { highlight, comment }).then((r) => {
@@ -464,13 +464,14 @@ function BreakdownChartContents({
songMap: Map<integer, SongDocument>;
chartMap: Map<string, ChartDocument>;
gptConfig: GamePTConfig;
modifyScore: ({ highlight, comment }: { highlight?: boolean; comment?: string }) => void;
modifyScore: ({ highlight, comment }: { highlight?: boolean; comment?: string | null }) => void;
type: "lamp" | "grade";
}) {
const chart = chartMap.get(score.chartID)!;
const song = songMap.get(score.songID)!;
const [highlight, setHighlight] = useState(score.highlight);
const [comment, setComment] = useState(score.comment);
const [firstRun, setFirstRun] = useState(true);
useEffect(() => {
@@ -479,8 +480,8 @@ function BreakdownChartContents({
return;
}
modifyScore({ highlight });
}, [highlight]);
modifyScore({ highlight, comment });
}, [highlight, comment]);
if (!chart || !song) {
console.error(`No chart for ${score.chartID}/${score.songID}???`);
@@ -531,22 +532,55 @@ function BreakdownChartContents({
return (
<>
<TitleCell noArtist chart={chart} game={game} song={song} />
<PseudHighlight highlight={highlight} setHighlight={setHighlight} />
<TitleCell noArtist chart={chart} game={game} song={song} comment={comment} />
<CommentHighlightManager
highlight={highlight}
setHighlight={setHighlight}
comment={comment}
setComment={setComment}
/>
<DifficultyCell alwaysShort chart={chart} game={game} />
</>
);
}
function PseudHighlight({
/**
* It manages the comment and highlight stuff.
*
* I don't know what else to call this function.
*/
function CommentHighlightManager({
highlight,
setHighlight,
comment,
setComment,
}: {
highlight: boolean;
setHighlight: (hl: boolean) => void;
comment: string | null;
setComment: (cm: string | null) => void;
}) {
const [showCommentModal, setShowCommentModal] = useState(false);
return (
<td style={{ verticalAlign: "center" }}>
<CommentModal
show={showCommentModal}
setShow={setShowCommentModal}
initialComment={comment}
onUpdate={(comment) => {
setComment(comment);
setShowCommentModal(false);
}}
/>
<span className="breakdown-hover-highlight-button">
<Icon
onClick={() => setShowCommentModal(true)}
type="comment"
regular
style={{ paddingTop: "0.1rem", paddingRight: "0.33rem" }}
/>
</span>
{highlight ? (
<Icon
onClick={() => setHighlight(false)}
@@ -2,7 +2,7 @@ import { APIFetchV1 } from "util/api";
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 React, { useContext, useEffect, useRef, useState } from "react";
import { Game, ScoreDocument } from "tachi-common";
import { Button, Modal, Form } from "react-bootstrap";
import { SetState } from "types/react";
@@ -101,10 +101,16 @@ export default function ScoreEditButtons({
<CommentModal
show={show}
setShow={setShow}
comment={comment}
setComment={setComment}
scoreID={score.scoreID}
score={score}
initialComment={comment}
onUpdate={(comment) => {
ModifyScore(score.scoreID, { comment }).then((r) => {
if (r) {
setComment(comment);
score.comment = comment;
setShow(false);
}
});
}}
/>
</div>
);
@@ -130,22 +136,26 @@ export async function ModifyScore(
return res.success;
}
function CommentModal({
export function CommentModal({
show,
setShow,
comment,
setComment,
scoreID,
score,
initialComment,
onUpdate,
}: {
show: boolean;
setShow: SetState<boolean>;
comment: string | null;
setComment: SetState<string | null>;
scoreID: string;
score: ScoreDocument;
initialComment: string | null;
onUpdate: (newComment: string | null) => void;
}) {
const [innerComment, setInnerComment] = useState(comment ?? "");
const [innerComment, setInnerComment] = useState(initialComment ?? "");
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (show && ref.current) {
ref.current.focus();
}
}, [show]);
return (
<Modal show={show} onHide={() => setShow(false)}>
@@ -157,24 +167,25 @@ function CommentModal({
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
ModifyScore(scoreID, { comment: innerComment }).then((r) => {
if (r) {
setComment(innerComment);
score.comment = innerComment;
setShow(false);
}
});
if (innerComment === "") {
onUpdate(null);
} else {
onUpdate(innerComment);
}
}}
>
<Form.Group>
<div className="input-group">
<input
ref={ref}
autoFocus
className="form-control form-control-lg"
type="text"
placeholder={comment ?? "This score was great!"}
placeholder={initialComment ?? "This score was great!"}
value={innerComment}
onChange={(e) => setInnerComment(e.target.value)}
/>
<div className="input-group-append">
<Button variant="primary" type="submit">
Submit
@@ -183,19 +194,15 @@ function CommentModal({
</div>
</Form.Group>
{comment !== null && (
{initialComment !== null && (
<QuickTooltip tooltipContent="Remove your comment on this score.">
<Button
variant="outline-danger"
onClick={() =>
ModifyScore(scoreID, { comment: null }).then((r) => {
if (r) {
setComment(null);
score.comment = null;
setShow(false);
}
})
}
onClick={() => {
onUpdate(null);
setInnerComment("");
}}
>
<Icon noPad type="trash" />
</Button>