tss site blah
This commit is contained in:
@@ -6,6 +6,10 @@ It reads two SQLite databases:
|
|||||||
|
|
||||||
- `TSS_BATTLES_DB` for `tss_battles.db` (matches, players, and the `match_logs` table)
|
- `TSS_BATTLES_DB` for `tss_battles.db` (matches, players, and the `match_logs` table)
|
||||||
- `TSS_TEAMS_DB` for `tss_teams.db`
|
- `TSS_TEAMS_DB` for `tss_teams.db`
|
||||||
|
- `TSS_TOURNAMENTS_DB` for `tss_tournaments.db`
|
||||||
|
|
||||||
|
If any of these are unset, the backend first looks under `STORAGE_VOL_PATH`
|
||||||
|
before falling back to the current working directory.
|
||||||
- `BACKEND_HOST` bind host, default `127.0.0.1`
|
- `BACKEND_HOST` bind host, default `127.0.0.1`
|
||||||
- `BACKEND_ALLOWED_ORIGINS` comma-separated browser origins allowed by CORS
|
- `BACKEND_ALLOWED_ORIGINS` comma-separated browser origins allowed by CORS
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -2299,9 +2299,13 @@ fn lookup_vehicle_icon(icons: &HashMap<String, String>, cdk: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_db_path(env_key: &str, default_file: &str) -> PathBuf {
|
fn resolve_db_path(env_key: &str, default_file: &str) -> PathBuf {
|
||||||
let raw = env::var(env_key).unwrap_or_else(|_| default_file.to_string());
|
let path = if let Ok(raw) = env::var(env_key) {
|
||||||
let expanded = expand_home(&raw);
|
PathBuf::from(expand_home(&raw))
|
||||||
let path = PathBuf::from(expanded);
|
} else if let Ok(storage) = env::var("STORAGE_VOL_PATH") {
|
||||||
|
PathBuf::from(expand_home(&storage)).join(default_file)
|
||||||
|
} else {
|
||||||
|
PathBuf::from(default_file)
|
||||||
|
};
|
||||||
if path.is_absolute() {
|
if path.is_absolute() {
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ BACKEND_HOST=127.0.0.1
|
|||||||
BACKEND_ALLOWED_ORIGINS=https://example.com
|
BACKEND_ALLOWED_ORIGINS=https://example.com
|
||||||
TSS_BATTLES_DB=./tss_battles.db
|
TSS_BATTLES_DB=./tss_battles.db
|
||||||
TSS_TEAMS_DB=./tss_teams.db
|
TSS_TEAMS_DB=./tss_teams.db
|
||||||
|
TSS_TOURNAMENTS_DB=./tss_tournaments.db
|
||||||
|
|
||||||
# Vehicle name translation + icon caches (shared STORAGE/CACHE, built by the bots).
|
# Vehicle name translation + icon caches (shared STORAGE/CACHE, built by the bots).
|
||||||
# The backend loads these at startup to translate vehicle_internal (cdk) -> name
|
# The backend loads these at startup to translate vehicle_internal (cdk) -> name
|
||||||
|
|||||||
+27
-10
@@ -3714,10 +3714,11 @@ function tournamentStatusLabel(status) {
|
|||||||
return raw.charAt(0).toUpperCase() + raw.slice(1)
|
return raw.charAt(0).toUpperCase() + raw.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sideFromMatch(match) {
|
function sideFromMatch(match, context = {}) {
|
||||||
|
const bracket = String(match?.type_bracket || '').toLowerCase()
|
||||||
|
if (context.hasLoserSide && bracket.includes('semifinal')) return 'loser'
|
||||||
const side = String(match?.side || '').toLowerCase()
|
const side = String(match?.side || '').toLowerCase()
|
||||||
if (side) return side
|
if (side) return side
|
||||||
const bracket = String(match?.type_bracket || '').toLowerCase()
|
|
||||||
if (bracket.includes('swiss')) return 'swiss'
|
if (bracket.includes('swiss')) return 'swiss'
|
||||||
if (bracket.includes('group')) return 'group'
|
if (bracket.includes('group')) return 'group'
|
||||||
if (bracket.includes('looser') || bracket.includes('loser')) return 'loser'
|
if (bracket.includes('looser') || bracket.includes('loser')) return 'loser'
|
||||||
@@ -3813,8 +3814,15 @@ function TournamentsPage({ navigate }) {
|
|||||||
|
|
||||||
function groupMatchesBySide(matches) {
|
function groupMatchesBySide(matches) {
|
||||||
const bySide = new Map()
|
const bySide = new Map()
|
||||||
|
const context = {
|
||||||
|
hasLoserSide: matches.some((match) => {
|
||||||
|
const bracket = String(match?.type_bracket || '').toLowerCase()
|
||||||
|
const side = String(match?.side || '').toLowerCase()
|
||||||
|
return bracket.includes('looser') || bracket.includes('loser') || side === 'loser'
|
||||||
|
}),
|
||||||
|
}
|
||||||
matches.forEach((match) => {
|
matches.forEach((match) => {
|
||||||
const side = sideFromMatch(match)
|
const side = sideFromMatch(match, context)
|
||||||
if (!bySide.has(side)) bySide.set(side, [])
|
if (!bySide.has(side)) bySide.set(side, [])
|
||||||
bySide.get(side).push(match)
|
bySide.get(side).push(match)
|
||||||
})
|
})
|
||||||
@@ -3862,7 +3870,7 @@ function TournamentMatchCard({ match, navigate }) {
|
|||||||
const bWon = winner && teamB && winner === teamB.toLowerCase()
|
const bWon = winner && teamB && winner === teamB.toLowerCase()
|
||||||
const battles = Array.isArray(match.battles) ? match.battles : []
|
const battles = Array.isArray(match.battles) ? match.battles : []
|
||||||
|
|
||||||
const teamRow = (name, score, won) => (
|
const teamRow = (name, score, won, emptyLabel = 'TBD') => (
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
{name ? (
|
{name ? (
|
||||||
<button
|
<button
|
||||||
@@ -3876,16 +3884,17 @@ function TournamentMatchCard({ match, navigate }) {
|
|||||||
{name}
|
{name}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="min-w-0 truncate font-semibold text-text-muted">TBD</span>
|
<span className="min-w-0 truncate font-semibold text-text-muted">{emptyLabel}</span>
|
||||||
)}
|
)}
|
||||||
<span className={`shrink-0 tabular-nums ${won ? 'text-win' : 'text-text-soft'}`}>{formatNumber(score)}</span>
|
<span className={`shrink-0 tabular-nums ${won ? 'text-win' : 'text-text-soft'}`}>{formatNumber(score)}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
const emptyLabel = match.status === 'bye' ? 'BYE' : 'TBD'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-border bg-bg p-2.5 text-sm shadow-sm">
|
<div className="rounded-md border border-border bg-bg p-2.5 text-sm shadow-sm">
|
||||||
{teamRow(teamA, match.score_a, aWon)}
|
{teamRow(teamA, match.score_a, aWon, emptyLabel)}
|
||||||
<div className="mt-1">{teamRow(teamB, match.score_b, bWon)}</div>
|
<div className="mt-1">{teamRow(teamB, match.score_b, bWon, emptyLabel)}</div>
|
||||||
<div className="mt-2 flex items-center justify-between gap-2 text-[10px] font-semibold uppercase tracking-wide text-text-muted">
|
<div className="mt-2 flex items-center justify-between gap-2 text-[10px] font-semibold uppercase tracking-wide text-text-muted">
|
||||||
<span>{tournamentStatusLabel(match.status)}</span>
|
<span>{tournamentStatusLabel(match.status)}</span>
|
||||||
{match.position !== null && match.position !== undefined ? <span>Slot {Number(match.position) + 1}</span> : null}
|
{match.position !== null && match.position !== undefined ? <span>Slot {Number(match.position) + 1}</span> : null}
|
||||||
@@ -3923,19 +3932,27 @@ function TournamentBracketSide({ side, navigate }) {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 text-sm font-semibold uppercase tracking-wide text-fury-cyan">{side.label}</h3>
|
<h3 className="mb-3 text-sm font-semibold uppercase tracking-wide text-fury-cyan">{side.label}</h3>
|
||||||
<div className="overflow-x-auto pb-2">
|
<div className="overflow-x-auto pb-2">
|
||||||
<div className="flex gap-4">
|
<div className="tournament-bracket-grid">
|
||||||
{rounds.map((round, roundIndex) => (
|
{rounds.map((round, roundIndex) => (
|
||||||
<div className="flex min-w-[190px] flex-col gap-3" key={round.round}>
|
<div className="tournament-round-column" key={round.round}>
|
||||||
<p className="text-xs font-semibold uppercase tracking-wide text-text-muted">
|
<p className="text-xs font-semibold uppercase tracking-wide text-text-muted">
|
||||||
{roundLabel(side.raw, round.round, roundIndex, rounds.length)}
|
{roundLabel(side.raw, round.round, roundIndex, rounds.length)}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-1 flex-col justify-around gap-3">
|
<div className="flex flex-1 flex-col justify-around gap-3">
|
||||||
{round.matches.map((match) => (
|
{round.matches.map((match) => (
|
||||||
<TournamentMatchCard
|
<div
|
||||||
|
className={[
|
||||||
|
'tournament-match-node',
|
||||||
|
roundIndex > 0 ? 'tournament-match-node-left' : '',
|
||||||
|
roundIndex < rounds.length - 1 ? 'tournament-match-node-right' : '',
|
||||||
|
].filter(Boolean).join(' ')}
|
||||||
key={`${match.type_bracket}-${match.match_id}`}
|
key={`${match.type_bracket}-${match.match_id}`}
|
||||||
|
>
|
||||||
|
<TournamentMatchCard
|
||||||
match={match}
|
match={match}
|
||||||
navigate={navigate}
|
navigate={navigate}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -831,6 +831,47 @@ h3 {
|
|||||||
font-size: 0.62rem;
|
font-size: 0.62rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tournament-bracket-grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 2.6rem;
|
||||||
|
min-width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tournament-round-column {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 190px;
|
||||||
|
min-width: 190px;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tournament-match-node {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tournament-match-node::before,
|
||||||
|
.tournament-match-node::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 0;
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
width: 1.3rem;
|
||||||
|
background: color-mix(in srgb, var(--color-border) 78%, var(--color-fury-violet));
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tournament-match-node-left::before {
|
||||||
|
right: calc(100% + 0.05rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tournament-match-node-right::after {
|
||||||
|
left: calc(100% + 0.05rem);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes scrollPulse {
|
@keyframes scrollPulse {
|
||||||
0% {
|
0% {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
|
|||||||
Reference in New Issue
Block a user