mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-06 05:07:36 -05:00
Add elimination bracket lines
This commit is contained in:
parent
863bf78140
commit
3ff60df9ea
|
|
@ -12,9 +12,18 @@ interface EliminationBracketSideProps {
|
|||
isExpanded?: boolean;
|
||||
}
|
||||
|
||||
// these values must match --match-height and gap in bracket.module.css
|
||||
const MATCH_HEIGHT = 55;
|
||||
const GAP = 28;
|
||||
const MATCH_SPACING = MATCH_HEIGHT + GAP;
|
||||
|
||||
export function EliminationBracketSide(props: EliminationBracketSideProps) {
|
||||
const rounds = getRounds({ ...props, bracketData: props.bracket.data });
|
||||
|
||||
const firstRoundMatchCount = props.bracket.data.match.filter(
|
||||
(match) => match.round_id === rounds[0]?.id,
|
||||
).length;
|
||||
|
||||
let atLeastOneColumnHidden = false;
|
||||
return (
|
||||
<div
|
||||
|
|
@ -28,6 +37,14 @@ export function EliminationBracketSide(props: EliminationBracketSideProps) {
|
|||
(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 &&
|
||||
|
|
@ -68,28 +85,49 @@ export function EliminationBracketSide(props: EliminationBracketSideProps) {
|
|||
!props.bracket.data.match[0].opponent2),
|
||||
})}
|
||||
>
|
||||
{matches.map((match) => (
|
||||
<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
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{matches.map((match, matchIdx) => {
|
||||
const lineType = (() => {
|
||||
if (isLastRound) 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 = firstRoundMatchCount / 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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import parentStyles from "../../tournament-bracket.module.css";
|
|||
import { matchEndedEarly } from "../../tournament-bracket-utils";
|
||||
import styles from "./bracket.module.css";
|
||||
|
||||
type LineType = "none" | "straight" | "curve-up" | "curve-down";
|
||||
|
||||
interface MatchProps {
|
||||
match: Unpacked<TournamentData["data"]["match"]>;
|
||||
isPreview?: boolean;
|
||||
|
|
@ -31,26 +33,39 @@ interface MatchProps {
|
|||
showSimulation: boolean;
|
||||
bracket: Bracket;
|
||||
hideMatchTimer?: boolean;
|
||||
lineType?: LineType;
|
||||
lineVerticalExtend?: number;
|
||||
}
|
||||
|
||||
export function Match(props: MatchProps) {
|
||||
const isBye = !props.match.opponent1 || !props.match.opponent2;
|
||||
|
||||
if (isBye) {
|
||||
return <div className={styles.matchBye} />;
|
||||
return (
|
||||
<div className={clsx(styles.matchBye, styles.matchWrapper)}>
|
||||
<MatchLine
|
||||
lineType={props.lineType}
|
||||
verticalExtend={props.lineVerticalExtend}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className={styles.matchWrapper}>
|
||||
<MatchHeader {...props} />
|
||||
<MatchWrapper {...props}>
|
||||
<MatchContent {...props}>
|
||||
<MatchRow {...props} side={1} />
|
||||
<div className={styles.matchSeparator} />
|
||||
<MatchRow {...props} side={2} />
|
||||
</MatchWrapper>
|
||||
</MatchContent>
|
||||
{!props.hideMatchTimer ? (
|
||||
<MatchTimer match={props.match} bracket={props.bracket} />
|
||||
) : null}
|
||||
<MatchLine
|
||||
lineType={props.lineType}
|
||||
verticalExtend={props.lineVerticalExtend}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -133,7 +148,7 @@ function MatchHeader({ match, type, roundNumber, group }: MatchProps) {
|
|||
);
|
||||
}
|
||||
|
||||
function MatchWrapper({
|
||||
function MatchContent({
|
||||
match,
|
||||
isPreview,
|
||||
children,
|
||||
|
|
@ -367,3 +382,36 @@ function MatchTimer({ match, bracket }: Pick<MatchProps, "match" | "bracket">) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface MatchLineProps {
|
||||
lineType?: LineType;
|
||||
verticalExtend?: number;
|
||||
}
|
||||
|
||||
function MatchLine({ lineType, verticalExtend }: MatchLineProps) {
|
||||
if (!lineType || lineType === "none") return null;
|
||||
|
||||
const lineClass =
|
||||
lineType === "straight"
|
||||
? styles.matchLineStraight
|
||||
: lineType === "curve-up"
|
||||
? styles.matchLineCurveUp
|
||||
: styles.matchLineCurveDown;
|
||||
|
||||
const style = verticalExtend
|
||||
? ({
|
||||
"--bracket-vertical-extend": `${verticalExtend}px`,
|
||||
} as React.CSSProperties)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.matchLineContainer, lineClass)} style={style}>
|
||||
{lineType === "curve-down" ? (
|
||||
<div className={styles.matchLineConnectorDown} style={style} />
|
||||
) : null}
|
||||
{lineType === "curve-up" ? (
|
||||
<div className={styles.matchLineConnectorUp} style={style} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* --match-height and gap must match constants in Elimination.tsx */
|
||||
.bracket {
|
||||
--match-width: 140px;
|
||||
--match-height: 55px;
|
||||
|
|
@ -164,6 +165,82 @@ a.match:hover {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.matchWrapper {
|
||||
position: relative;
|
||||
width: var(--match-width);
|
||||
height: var(--match-height);
|
||||
}
|
||||
|
||||
.matchLineContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
width: var(--line-width);
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.matchLineStraight::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.matchLineCurveDown::before,
|
||||
.matchLineCurveUp::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.matchLineCurveDown::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: calc(50% - 1px);
|
||||
width: 2px;
|
||||
height: calc(50% + var(--bracket-vertical-extend, 50px));
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.matchLineCurveUp::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: calc(50% - 1px);
|
||||
width: 2px;
|
||||
height: calc(50% + var(--bracket-vertical-extend, 50px));
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.matchLineConnectorDown {
|
||||
position: absolute;
|
||||
top: calc(100% + var(--bracket-vertical-extend, 50px) - 1px);
|
||||
left: calc(50% - 1px);
|
||||
width: calc(50% + 1px);
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.matchLineConnectorUp {
|
||||
position: absolute;
|
||||
bottom: calc(100% + var(--bracket-vertical-extend, 50px) - 1px);
|
||||
left: calc(50% - 1px);
|
||||
width: calc(50% + 1px);
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.rrPlacementsTable {
|
||||
font-size: var(--fonts-xs);
|
||||
font-weight: var(--semi-bold);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user