meow
This commit is contained in:
+59
-63
@@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import Tree, { prewarmTreeCanvas } from '../Tree/Tree'
|
||||
import FallingLeaves from '../Tree/FallingLeaves'
|
||||
import ReplayCanvasPanel from './ReplayCanvas'
|
||||
import { buildBracket, parentPosition } from './bracket'
|
||||
import { buildBracket, computeBracketLayout, feederParent, layoutKey } from './bracket'
|
||||
|
||||
const numberFormat = new Intl.NumberFormat('en-GB')
|
||||
const dateFormat = new Intl.DateTimeFormat('en-GB', {
|
||||
@@ -3840,66 +3840,59 @@ function TournamentMatchCard({ match, navigate }) {
|
||||
)
|
||||
}
|
||||
|
||||
// Map each match to the match it feeds into in the next column, so we can draw a
|
||||
// connector between them. Parent is found by slot position (faithful to the real
|
||||
// tree even with byes hidden); falls back to proportional index if the exact
|
||||
// parent slot was itself a hidden bye.
|
||||
function feederParent(match, columnIndex, columns) {
|
||||
const cur = columns[columnIndex]
|
||||
const next = columns[columnIndex + 1]
|
||||
if (!next) return null
|
||||
const pPos = parentPosition(Number(match.position), cur.matches.length, next.matches.length)
|
||||
const exact = next.matches.find((m) => Number(m.position) === pPos)
|
||||
if (exact) return exact
|
||||
const order = cur.matches.indexOf(match)
|
||||
const idx = Math.min(next.matches.length - 1, Math.floor((order * next.matches.length) / cur.matches.length))
|
||||
return next.matches[idx] || null
|
||||
}
|
||||
|
||||
function TournamentBracketSide({ side, navigate }) {
|
||||
const gridRef = useRef(null)
|
||||
const nodeRefs = useRef(new Map())
|
||||
// `tops` places each match; once placed we read back the DOM to draw connectors.
|
||||
const [layout, setLayout] = useState({ tops: new Map(), height: 0 })
|
||||
const [connectors, setConnectors] = useState({ width: 0, height: 0, paths: [] })
|
||||
|
||||
const nodeKey = (columnIndex, match) => `${columnIndex}:${match.match_id}`
|
||||
|
||||
// Pass 1: measure card heights, then position every match centred on its feeders.
|
||||
useLayoutEffect(() => {
|
||||
const grid = gridRef.current
|
||||
if (!grid) return undefined
|
||||
|
||||
const measure = () => {
|
||||
const gridBox = grid.getBoundingClientRect()
|
||||
const boxOf = (key) => {
|
||||
const el = nodeRefs.current.get(key)
|
||||
if (!el) return null
|
||||
const b = el.getBoundingClientRect()
|
||||
return {
|
||||
left: b.left - gridBox.left,
|
||||
right: b.right - gridBox.left,
|
||||
mid: b.top - gridBox.top + b.height / 2,
|
||||
}
|
||||
}
|
||||
const paths = []
|
||||
for (let c = 0; c < side.columns.length - 1; c += 1) {
|
||||
for (const match of side.columns[c].matches) {
|
||||
const parent = feederParent(match, c, side.columns)
|
||||
if (!parent) continue
|
||||
const from = boxOf(nodeKey(c, match))
|
||||
const to = boxOf(nodeKey(c + 1, parent))
|
||||
if (!from || !to) continue
|
||||
const midX = (from.right + to.left) / 2
|
||||
paths.push(`M ${from.right} ${from.mid} H ${midX} V ${to.mid} H ${to.left}`)
|
||||
}
|
||||
}
|
||||
setConnectors({ width: grid.scrollWidth, height: grid.scrollHeight, paths })
|
||||
const relayout = () => {
|
||||
const heights = new Map()
|
||||
for (const [key, el] of nodeRefs.current) heights.set(key, el.offsetHeight)
|
||||
const next = computeBracketLayout(side.columns, heights)
|
||||
setLayout({ tops: next.tops, height: next.height })
|
||||
}
|
||||
|
||||
measure()
|
||||
const observer = new ResizeObserver(measure)
|
||||
relayout()
|
||||
const observer = new ResizeObserver(relayout)
|
||||
observer.observe(grid)
|
||||
return () => observer.disconnect()
|
||||
}, [side])
|
||||
|
||||
// Pass 2: with matches positioned, read real edges and draw elbow connectors.
|
||||
useLayoutEffect(() => {
|
||||
const grid = gridRef.current
|
||||
if (!grid) return
|
||||
const gridBox = grid.getBoundingClientRect()
|
||||
const boxOf = (key) => {
|
||||
const el = nodeRefs.current.get(key)
|
||||
if (!el) return null
|
||||
const b = el.getBoundingClientRect()
|
||||
return {
|
||||
left: b.left - gridBox.left,
|
||||
right: b.right - gridBox.left,
|
||||
mid: b.top - gridBox.top + b.height / 2,
|
||||
}
|
||||
}
|
||||
const paths = []
|
||||
for (let c = 0; c < side.columns.length - 1; c += 1) {
|
||||
for (const match of side.columns[c].matches) {
|
||||
const parent = feederParent(match, c, side.columns)
|
||||
if (!parent) continue
|
||||
const from = boxOf(layoutKey(c, match))
|
||||
const to = boxOf(layoutKey(c + 1, parent))
|
||||
if (!from || !to) continue
|
||||
const midX = (from.right + to.left) / 2
|
||||
paths.push(`M ${from.right} ${from.mid} H ${midX} V ${to.mid} H ${to.left}`)
|
||||
}
|
||||
}
|
||||
setConnectors({ width: grid.scrollWidth, height: grid.scrollHeight, paths })
|
||||
}, [side, layout])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="mb-3 text-sm font-semibold uppercase tracking-wide text-fury-cyan">{side.label}</h3>
|
||||
@@ -3917,23 +3910,26 @@ function TournamentBracketSide({ side, navigate }) {
|
||||
</svg>
|
||||
{side.columns.map((column, columnIndex) => (
|
||||
<div className="tournament-round-column" key={column.id}>
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-text-muted">
|
||||
<p className="mb-3 text-xs font-semibold uppercase tracking-wide text-text-muted">
|
||||
{column.label}
|
||||
</p>
|
||||
<div className="flex flex-1 flex-col justify-around gap-3">
|
||||
{column.matches.map((match) => (
|
||||
<div
|
||||
className="tournament-match-node"
|
||||
key={`${match.type_bracket}-${match.match_id}`}
|
||||
ref={(el) => {
|
||||
const key = nodeKey(columnIndex, match)
|
||||
if (el) nodeRefs.current.set(key, el)
|
||||
else nodeRefs.current.delete(key)
|
||||
}}
|
||||
>
|
||||
<TournamentMatchCard match={match} navigate={navigate} />
|
||||
</div>
|
||||
))}
|
||||
<div className="tournament-round-track" style={{ height: layout.height || undefined }}>
|
||||
{column.matches.map((match) => {
|
||||
const key = layoutKey(columnIndex, match)
|
||||
return (
|
||||
<div
|
||||
className="tournament-match-node"
|
||||
key={`${match.type_bracket}-${match.match_id}`}
|
||||
ref={(el) => {
|
||||
if (el) nodeRefs.current.set(key, el)
|
||||
else nodeRefs.current.delete(key)
|
||||
}}
|
||||
style={{ top: layout.tops.get(key) ?? 0 }}
|
||||
>
|
||||
<TournamentMatchCard match={match} navigate={navigate} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user