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_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_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 {
|
||||
let raw = env::var(env_key).unwrap_or_else(|_| default_file.to_string());
|
||||
let expanded = expand_home(&raw);
|
||||
let path = PathBuf::from(expanded);
|
||||
let path = if let Ok(raw) = env::var(env_key) {
|
||||
PathBuf::from(expand_home(&raw))
|
||||
} 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() {
|
||||
path
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ BACKEND_HOST=127.0.0.1
|
||||
BACKEND_ALLOWED_ORIGINS=https://example.com
|
||||
TSS_BATTLES_DB=./tss_battles.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).
|
||||
# The backend loads these at startup to translate vehicle_internal (cdk) -> name
|
||||
|
||||
+30
-13
@@ -3714,10 +3714,11 @@ function tournamentStatusLabel(status) {
|
||||
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()
|
||||
if (side) return side
|
||||
const bracket = String(match?.type_bracket || '').toLowerCase()
|
||||
if (bracket.includes('swiss')) return 'swiss'
|
||||
if (bracket.includes('group')) return 'group'
|
||||
if (bracket.includes('looser') || bracket.includes('loser')) return 'loser'
|
||||
@@ -3813,8 +3814,15 @@ function TournamentsPage({ navigate }) {
|
||||
|
||||
function groupMatchesBySide(matches) {
|
||||
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) => {
|
||||
const side = sideFromMatch(match)
|
||||
const side = sideFromMatch(match, context)
|
||||
if (!bySide.has(side)) bySide.set(side, [])
|
||||
bySide.get(side).push(match)
|
||||
})
|
||||
@@ -3862,7 +3870,7 @@ function TournamentMatchCard({ match, navigate }) {
|
||||
const bWon = winner && teamB && winner === teamB.toLowerCase()
|
||||
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">
|
||||
{name ? (
|
||||
<button
|
||||
@@ -3876,16 +3884,17 @@ function TournamentMatchCard({ match, navigate }) {
|
||||
{name}
|
||||
</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>
|
||||
</div>
|
||||
)
|
||||
const emptyLabel = match.status === 'bye' ? 'BYE' : 'TBD'
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-border bg-bg p-2.5 text-sm shadow-sm">
|
||||
{teamRow(teamA, match.score_a, aWon)}
|
||||
<div className="mt-1">{teamRow(teamB, match.score_b, bWon)}</div>
|
||||
{teamRow(teamA, match.score_a, aWon, emptyLabel)}
|
||||
<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">
|
||||
<span>{tournamentStatusLabel(match.status)}</span>
|
||||
{match.position !== null && match.position !== undefined ? <span>Slot {Number(match.position) + 1}</span> : null}
|
||||
@@ -3923,19 +3932,27 @@ function TournamentBracketSide({ side, navigate }) {
|
||||
<div>
|
||||
<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="flex gap-4">
|
||||
<div className="tournament-bracket-grid">
|
||||
{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">
|
||||
{roundLabel(side.raw, round.round, roundIndex, rounds.length)}
|
||||
</p>
|
||||
<div className="flex flex-1 flex-col justify-around gap-3">
|
||||
{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}`}
|
||||
match={match}
|
||||
navigate={navigate}
|
||||
/>
|
||||
>
|
||||
<TournamentMatchCard
|
||||
match={match}
|
||||
navigate={navigate}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -831,6 +831,47 @@ h3 {
|
||||
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 {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
|
||||
Reference in New Issue
Block a user