mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 09:20:24 -05:00
157 lines
4.5 KiB
TypeScript
157 lines
4.5 KiB
TypeScript
import clsx from "clsx";
|
|
import { TOURNAMENT } from "../../../tournament/tournament-constants";
|
|
import type { Bracket as BracketType } from "../../core/Bracket";
|
|
import { getRounds } from "../../core/rounds";
|
|
import styles from "./bracket.module.css";
|
|
import { Match } from "./Match";
|
|
import { RoundHeader } from "./RoundHeader";
|
|
|
|
interface EliminationBracketSideProps {
|
|
bracket: BracketType;
|
|
type: "winners" | "losers" | "single";
|
|
isExpanded?: boolean;
|
|
}
|
|
|
|
// these values must match --match-height and gap in bracket.module.css
|
|
const MATCH_HEIGHT = 55;
|
|
const GAP = 32;
|
|
const MATCH_SPACING = MATCH_HEIGHT + GAP;
|
|
|
|
export function EliminationBracketSide(props: EliminationBracketSideProps) {
|
|
const rounds = getRounds({ ...props, bracketData: props.bracket.data });
|
|
|
|
const firstRoundMatches = props.bracket.data.match.filter(
|
|
(match) => match.round_id === rounds[0]?.id,
|
|
);
|
|
const firstRoundHasOngoing = firstRoundMatches.some(
|
|
(match) =>
|
|
match.opponent1 &&
|
|
match.opponent2 &&
|
|
match.opponent1.result !== "win" &&
|
|
match.opponent2.result !== "win",
|
|
);
|
|
const firstRoundHidden =
|
|
!props.isExpanded && rounds.length > 2 && !firstRoundHasOngoing;
|
|
|
|
const firstVisibleRoundId = firstRoundHidden ? rounds[1]?.id : rounds[0]?.id;
|
|
const firstVisibleRoundMatchCount = props.bracket.data.match.filter(
|
|
(match) => match.round_id === firstVisibleRoundId,
|
|
).length;
|
|
|
|
let atLeastOneColumnHidden = false;
|
|
return (
|
|
<div
|
|
className={styles.elimContainer}
|
|
style={{ "--round-count": rounds.length }}
|
|
>
|
|
{rounds.flatMap((round, roundIdx) => {
|
|
const bestOf = round.maps?.count;
|
|
|
|
const matches = props.bracket.data.match.filter(
|
|
(match) => match.round_id === round.id,
|
|
);
|
|
|
|
const isLastRound = roundIdx === rounds.length - 1;
|
|
const nextRound = rounds[roundIdx + 1];
|
|
const nextRoundMatchCount = nextRound
|
|
? props.bracket.data.match.filter(
|
|
(match) => match.round_id === nextRound.id,
|
|
).length
|
|
: 0;
|
|
|
|
const someMatchOngoing = matches.some(
|
|
(match) =>
|
|
match.opponent1 &&
|
|
match.opponent2 &&
|
|
match.opponent1.result !== "win" &&
|
|
match.opponent2.result !== "win",
|
|
);
|
|
|
|
if (
|
|
!props.isExpanded &&
|
|
// always show at least 2 rounds per side
|
|
roundIdx < rounds.length - 2 &&
|
|
!someMatchOngoing
|
|
) {
|
|
atLeastOneColumnHidden = true;
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={round.id}
|
|
className={styles.elimRoundColumn}
|
|
data-round-id={round.id}
|
|
>
|
|
<RoundHeader
|
|
roundId={round.id}
|
|
name={round.name}
|
|
bestOf={bestOf}
|
|
showInfos={someMatchOngoing}
|
|
maps={round.maps}
|
|
/>
|
|
<div
|
|
className={clsx(styles.elimRoundMatchesContainer, {
|
|
[styles.elimRoundMatchesContainerTopBye]:
|
|
!atLeastOneColumnHidden &&
|
|
props.type === "winners" &&
|
|
(!props.bracket.data.match[0].opponent1 ||
|
|
!props.bracket.data.match[0].opponent2),
|
|
})}
|
|
>
|
|
{matches.map((match, matchIdx) => {
|
|
const lineType = (() => {
|
|
if (isLastRound) return "none" as const;
|
|
if (
|
|
nextRound?.name === TOURNAMENT.ROUND_NAMES.THIRD_PLACE_MATCH
|
|
)
|
|
return "none" as const;
|
|
if (nextRoundMatchCount === matches.length)
|
|
return "straight" as const;
|
|
return matchIdx % 2 === 0
|
|
? ("curve-down" as const)
|
|
: ("curve-up" as const);
|
|
})();
|
|
|
|
const verticalExtend = (() => {
|
|
if (matches.length <= 1) return undefined;
|
|
if (nextRoundMatchCount === matches.length) return undefined;
|
|
|
|
const spreadFactor =
|
|
firstVisibleRoundMatchCount / matches.length;
|
|
return GAP / 2 + (spreadFactor - 1) * (MATCH_SPACING / 2);
|
|
})();
|
|
|
|
return (
|
|
<Match
|
|
key={match.id}
|
|
match={match}
|
|
roundNumber={round.number}
|
|
isPreview={props.bracket.preview}
|
|
showSimulation={
|
|
round.name !== TOURNAMENT.ROUND_NAMES.BRACKET_RESET
|
|
}
|
|
bracket={props.bracket}
|
|
type={
|
|
round.name === TOURNAMENT.ROUND_NAMES.GRAND_FINALS ||
|
|
round.name === TOURNAMENT.ROUND_NAMES.BRACKET_RESET
|
|
? "grands"
|
|
: props.type === "winners"
|
|
? "winners"
|
|
: props.type === "losers"
|
|
? "losers"
|
|
: undefined
|
|
}
|
|
lineType={lineType}
|
|
lineVerticalExtend={verticalExtend}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|