From 88dc19f450592399dee4f1bd04e1f897044d82d9 Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Wed, 22 Dec 2021 18:19:41 +0200 Subject: [PATCH] =?UTF-8?q?Create=20tournaments=20rounds=20almost=E2=84=A2?= =?UTF-8?q?=EF=B8=8F=20edition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/tournament/bracket.ts | 5 +- app/models/Tournament.ts | 1 + app/routes/to/$organization.$tournament.tsx | 3 +- .../to/$organization.$tournament/start.tsx | 36 +++++++--- app/services/tournament.ts | 67 ++++++++++++++++++- app/styles/global.css | 3 +- app/styles/tournament.css | 2 +- 7 files changed, 100 insertions(+), 17 deletions(-) diff --git a/app/core/tournament/bracket.ts b/app/core/tournament/bracket.ts index 065e38a43..5a46f1387 100644 --- a/app/core/tournament/bracket.ts +++ b/app/core/tournament/bracket.ts @@ -1,4 +1,5 @@ import type { BracketType, Stage, TeamOrder } from ".prisma/client"; +import { v4 as uuidv4 } from "uuid"; import invariant from "tiny-invariant"; import { TOURNAMENT_TEAM_ROSTER_MIN_SIZE } from "../../constants"; import { FindTournamentByNameForUrlI } from "../../services/tournament"; @@ -182,7 +183,8 @@ export function countParticipants(teams: FindTournamentByNameForUrlI["teams"]) { }, 0); } -interface TournamentRoundForDB { +export interface TournamentRoundForDB { + id: string; position: number; stages: { position: number; @@ -256,6 +258,7 @@ export function tournamentRoundsForDB({ }); result.push({ + id: uuidv4(), position, stages, matches, diff --git a/app/models/Tournament.ts b/app/models/Tournament.ts index 81d37cd7d..aa1586a91 100644 --- a/app/models/Tournament.ts +++ b/app/models/Tournament.ts @@ -50,6 +50,7 @@ export async function findByNameForUrl({ }, brackets: { select: { + id: true, type: true, }, }, diff --git a/app/routes/to/$organization.$tournament.tsx b/app/routes/to/$organization.$tournament.tsx index 6d80dd14d..fcaf932a4 100644 --- a/app/routes/to/$organization.$tournament.tsx +++ b/app/routes/to/$organization.$tournament.tsx @@ -117,7 +117,8 @@ export default function TournamentPage() {
-
+
+ {/* TODO: pass context instead of useMatches */}
); diff --git a/app/routes/to/$organization.$tournament/start.tsx b/app/routes/to/$organization.$tournament/start.tsx index 96919eb87..d7042d4a4 100644 --- a/app/routes/to/$organization.$tournament/start.tsx +++ b/app/routes/to/$organization.$tournament/start.tsx @@ -1,6 +1,11 @@ import type { Mode, Stage } from ".prisma/client"; import classNames from "classnames"; -import type { ActionFunction, LinksFunction, LoaderFunction } from "remix"; +import { + ActionFunction, + LinksFunction, + LoaderFunction, + useMatches, +} from "remix"; import { Form, json, redirect, useLoaderData } from "remix"; import invariant from "tiny-invariant"; import { Alert } from "~/components/Alert"; @@ -9,6 +14,7 @@ import { Catcher } from "~/components/Catcher"; import { modesShort, modesShortToLong } from "~/constants"; import { eliminationBracket } from "~/core/tournament/algorithms"; import { + EliminationBracket, EliminationBracketSide, participantCountToRoundsInfo, } from "~/core/tournament/bracket"; @@ -21,6 +27,7 @@ import type { import { createTournamentRounds, findTournamentByNameForUrl, + FindTournamentByNameForUrlI, } from "~/services/tournament"; import startBracketTabStylesUrl from "~/styles/tournament-start.css"; import { requireUser } from "~/utils"; @@ -32,11 +39,13 @@ export const links: LinksFunction = () => { }; export const action: ActionFunction = async ({ request, params, context }) => { - const mapListString = (await request.formData()).get("map-list"); + const formData = await request.formData(); + const mapListString = formData.get("map-list"); + const bracketId = formData.get("bracket-id"); invariant(typeof mapListString === "string", "Type of map list not string"); - const mapList = JSON.parse( - mapListString - ) as UseTournamentRoundsState["bracket"]; + invariant(typeof bracketId === "string", "Type of bracket id not string"); + // TODO: could use Zod here + const mapList = JSON.parse(mapListString) as EliminationBracket; const organizationNameForUrl = params.organization; const tournamentNameForUrl = params.tournament; @@ -50,10 +59,9 @@ export const action: ActionFunction = async ({ request, params, context }) => { organizationNameForUrl, tournamentNameForUrl, userId: user.id, + bracketId, }); - return null; - return redirect( `/to/${organizationNameForUrl}/${tournamentNameForUrl}/bracket` ); @@ -94,18 +102,26 @@ export const loader: LoaderFunction = async ({ params }) => { // TODO: component that shows a table of map, counts in the map pool, which rounds // TODO: handle warning if check-in has not concluded export default function StartBracketTab() { + const [, parentRoute] = useMatches(); + const { brackets } = parentRoute.data as FindTournamentByNameForUrlI; const args = useLoaderData(); const [{ bracket, showAlert, actionButtonsDisabled }, dispatch] = useTournamentRounds(args); + // TODO: dropdown to select this + const bracketId = brackets[0].id; + return (
- round.mapList), + winners: bracket.winners.map((round) => round.mapList), + })} /> +
@@ -126,11 +130,13 @@ export async function createTournamentRounds({ tournamentNameForUrl, mapList, userId, + bracketId, }: { organizationNameForUrl: string; tournamentNameForUrl: string; - mapList: UseTournamentRoundsState["bracket"]; + mapList: EliminationBracket; userId: string; + bracketId: string; }) { const tournament = await Tournament.findByNameForUrl({ organizationNameForUrl, @@ -141,6 +147,61 @@ export async function createTournamentRounds({ if (!isTournamentAdmin({ organization: tournament.organizer, userId })) { throw new Response("Not tournament admin", { status: 401 }); } + + const bracket = tournament.brackets.find( + (bracket) => bracket.id === bracketId + ); + // TODO: OR rounds i.e. bracket was already started + if (!bracket) { + throw new Response("Invalid bracket id provided", { status: 400 }); + } + + const participantsSeeded = tournament.teams.sort( + sortTeamsBySeed(tournament.seeds) + ); + + const rounds = tournamentRoundsForDB({ + mapList, + bracketType: bracket.type, + participantsSeeded, + }); + + return db.$transaction([ + db.tournamentRound.createMany({ + data: rounds.map((round) => ({ + bracketId: bracket.id, + id: round.id, + position: round.position, + })), + }), + db.tournamentRoundStage.createMany({ + data: rounds.flatMap((round) => { + return round.stages.map((stage) => ({ ...stage, roundId: round.id })); + }), + }), + db.tournamentMatch.createMany({ + data: rounds.flatMap((round) => { + return round.matches.map((match) => ({ + ...match, + roundId: round.id, + })); + }), + }), + db.tournamentMatchParticipant.createMany({ + data: rounds.flatMap((round) => { + return round.matches.flatMap((match) => { + return match.participants.flatMap((participant) => { + if (participant.team === "BYE") return []; + return { + teamId: participant.team.id, + matchId: match.id, + order: participant.order, + }; + }); + }); + }), + }), + ]); } export async function joinTeamViaInviteCode({ diff --git a/app/styles/global.css b/app/styles/global.css index f42e94447..e45af7d35 100644 --- a/app/styles/global.css +++ b/app/styles/global.css @@ -3,7 +3,7 @@ --bg-lighter: hsl(237.3deg 42.3% 35.6%); --bg-lighter-transparent: hsla(237.3deg 42.3% 35.6% / 50%); --border: hsl(237.3deg 42.3% 45.6%); - --button-text: rgba(0, 0, 0, 0.85); + --button-text: rgb(0 0 0 / 85%); --text: rgb(255 255 255 / 95%); --text-lighter: rgb(255 255 255 / 55%); --theme-error: rgb(219 70 65); @@ -13,6 +13,7 @@ --theme: hsl(255deg 66.7% 75%); --theme-transparent: hsl(255deg 66.7% 75% / 40%); --theme-secondary: hsl(85deg 66.7% 55.3%); + /* background-pattern.svg not using this currently */ --theme-pattern: #6741d9; --rounded: 16px; diff --git a/app/styles/tournament.css b/app/styles/tournament.css index 512d1d3c7..dfcd79e7c 100644 --- a/app/styles/tournament.css +++ b/app/styles/tournament.css @@ -216,7 +216,7 @@ outline: 2px solid var(--tournaments-text-transparent); } -.tournament__outlet-container { +.tournament__outlet-spacer { padding-block-start: var(--s-8); }