Misc tournament things

This commit is contained in:
Kalle 2023-05-17 20:52:54 +03:00
parent ce60895e8f
commit 02abc0b99c
10 changed files with 110 additions and 35 deletions

View File

@ -1,4 +1,4 @@
import { Form, useFetcher } from "@remix-run/react";
import { useFetcher } from "@remix-run/react";
import React from "react";
import invariant from "tiny-invariant";
import { useTranslation } from "~/hooks/useTranslation";
@ -12,12 +12,14 @@ export function FormWithConfirm({
dialogHeading,
deleteButtonText,
action,
submitButtonTestId = "submit-button",
}: {
fields?: [name: string, value: string | number][];
children: React.ReactNode;
dialogHeading: string;
deleteButtonText?: string;
action?: string;
submitButtonTestId?: string;
}) {
const fetcher = useFetcher();
const { t } = useTranslation(["common"]);
@ -50,7 +52,7 @@ export function FormWithConfirm({
<SubmitButton
form={id}
variant="destructive"
testId={dialogOpen ? "confirm-button" : undefined}
testId={dialogOpen ? "confirm-button" : submitButtonTestId}
>
{deleteButtonText ?? t("common:actions.delete")}
</SubmitButton>

View File

@ -1,6 +1,10 @@
import { useTranslation } from "~/hooks/useTranslation";
import type { MainWeaponId } from "~/modules/in-game-lists";
import { mainWeaponImageUrl, outlinedMainWeaponImageUrl } from "~/utils/urls";
import type { MainWeaponId, ModeShort } from "~/modules/in-game-lists";
import {
mainWeaponImageUrl,
modeImageUrl,
outlinedMainWeaponImageUrl,
} from "~/utils/urls";
interface ImageProps {
path: string;
@ -10,6 +14,7 @@ interface ImageProps {
containerClassName?: string;
width?: number;
height?: number;
size?: number;
style?: React.CSSProperties;
testId?: string;
onClick?: () => void;
@ -22,6 +27,7 @@ export function Image({
className,
width,
height,
size,
style,
testId,
containerClassName,
@ -40,8 +46,8 @@ export function Image({
alt={alt}
src={`${path}.png`}
className={className}
width={width}
height={height}
width={size ?? width}
height={size ?? height}
style={style}
draggable="false"
data-testid={testId}
@ -77,3 +83,21 @@ export function WeaponImage({
/>
);
}
type ModeImageProps = {
mode: ModeShort;
} & Omit<ImageProps, "path" | "alt" | "title">;
export function ModeImage({ mode, testId, ...rest }: ModeImageProps) {
const { t } = useTranslation(["game-misc"]);
return (
<Image
{...rest}
alt={t(`game-misc:MODE_LONG_${mode}`)}
title={t(`game-misc:MODE_LONG_${mode}`)}
testId={testId}
path={modeImageUrl(mode)}
/>
);
}

View File

@ -128,6 +128,7 @@ export const loader = ({ params }: LoaderArgs) => {
};
};
// xxx: banner that shows for participants not mobile friendly
export default function TournamentBracketsPage() {
const user = useUser();
const data = useLoaderData<typeof loader>();

View File

@ -143,7 +143,6 @@ export const action: ActionFunction = async ({ request, params }) => {
return null;
};
// xxx: seed order for challonge download
export default function TournamentAdminPage() {
const { t } = useTranslation(["calendar"]);
const data = useOutletContext<TournamentLoaderData>();
@ -172,6 +171,7 @@ export default function TournamentAdminPage() {
name: data.event.name,
})}
action={calendarEventPage(data.event.eventId)}
submitButtonTestId="delete-submit-button"
>
<Button
className="ml-auto"
@ -385,6 +385,15 @@ function DownloadParticipants() {
.join("\n");
}
function simpleListInSeededOrder() {
return data.teams
.slice()
.sort((a, b) => (a.seed ?? Infinity) - (b.seed ?? Infinity))
.filter((team) => team.checkedInAt)
.map((team) => team.name)
.join("\n");
}
return (
<div>
<label>{t("tournament:admin.download")} (Discord format)</label>
@ -411,6 +420,17 @@ function DownloadParticipants() {
>
Not checked in participants
</Button>
<Button
size="tiny"
onClick={() =>
handleDownload({
filename: "teams-in-seeded-order.txt",
content: simpleListInSeededOrder(),
})
}
>
Simple list in seeded order
</Button>
</div>
</div>
);

View File

@ -1,20 +1,28 @@
import {
redirect,
type ActionFunction,
type LoaderArgs,
type SerializeFrom,
redirect,
} from "@remix-run/node";
import { useFetcher, useLoaderData, useOutletContext } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import { useCopyToClipboard } from "react-use";
import invariant from "tiny-invariant";
import { Alert } from "~/components/Alert";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { Image } from "~/components/Image";
import { Divider } from "~/components/Divider";
import { Image, ModeImage } from "~/components/Image";
import { Input } from "~/components/Input";
import { Label } from "~/components/Label";
import { SubmitButton } from "~/components/SubmitButton";
import { CheckmarkIcon } from "~/components/icons/Checkmark";
import { ClockIcon } from "~/components/icons/Clock";
import { CrossIcon } from "~/components/icons/Cross";
import { UserIcon } from "~/components/icons/User";
import { useAutoRerender } from "~/hooks/useAutoRerender";
import { useIsMounted } from "~/hooks/useIsMounted";
import { useTranslation } from "~/hooks/useTranslation";
import { useUser } from "~/modules/auth";
import { getUser, requireUser } from "~/modules/auth/user.server";
@ -26,12 +34,14 @@ import type {
import { stageIds } from "~/modules/in-game-lists";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
import { MapPool } from "~/modules/map-pool-serializer";
import { databaseTimestampToDate } from "~/utils/dates";
import {
notFoundIfFalsy,
parseRequestFormData,
validate,
type SendouRouteHandle,
} from "~/utils/remix";
import { booleanToInt } from "~/utils/sql";
import { discordFullName } from "~/utils/strings";
import type { Unpacked } from "~/utils/types";
import { assertUnreachable } from "~/utils/types";
@ -44,40 +54,31 @@ import {
tournamentBracketsPage,
tournamentJoinPage,
} from "~/utils/urls";
import { checkIn } from "../queries/checkIn.server";
import { createTeam } from "../queries/createTeam.server";
import deleteTeamMember from "../queries/deleteTeamMember.server";
import { findByIdentifier } from "../queries/findByIdentifier.server";
import { findOwnTeam } from "../queries/findOwnTeam.server";
import { findTeamsByTournamentId } from "../queries/findTeamsByTournamentId.server";
import type { TrustedPlayer } from "../queries/findTrustedPlayers.server";
import { findTrustedPlayers } from "../queries/findTrustedPlayers.server";
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
import { joinTeam } from "../queries/joinLeaveTeam.server";
import { updateTeamInfo } from "../queries/updateTeamInfo.server";
import { upsertCounterpickMaps } from "../queries/upsertCounterpickMaps.server";
import { TOURNAMENT } from "../tournament-constants";
import { useSelectCounterpickMapPoolState } from "../tournament-hooks";
import { registerSchema } from "../tournament-schemas.server";
import {
isOneModeTournamentOf,
HACKY_resolvePicture,
tournamentIdFromParams,
resolveOwnedTeam,
HACKY_resolveCheckInTime,
HACKY_resolvePicture,
isOneModeTournamentOf,
modesIncluded,
resolveOwnedTeam,
tournamentIdFromParams,
validateCanCheckIn,
} from "../tournament-utils";
import type { TournamentLoaderData } from "./to.$id";
import { createTeam } from "../queries/createTeam.server";
import { ClockIcon } from "~/components/icons/Clock";
import { databaseTimestampToDate } from "~/utils/dates";
import { UserIcon } from "~/components/icons/User";
import { useIsMounted } from "~/hooks/useIsMounted";
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
import { CheckmarkIcon } from "~/components/icons/Checkmark";
import { CrossIcon } from "~/components/icons/Cross";
import clsx from "clsx";
import { checkIn } from "../queries/checkIn.server";
import { useAutoRerender } from "~/hooks/useAutoRerender";
import type { TrustedPlayer } from "../queries/findTrustedPlayers.server";
import { findTrustedPlayers } from "../queries/findTrustedPlayers.server";
import { Divider } from "~/components/Divider";
import { joinTeam } from "../queries/joinLeaveTeam.server";
import { booleanToInt } from "~/utils/sql";
export const handle: SendouRouteHandle = {
breadcrumb: () => ({
@ -262,6 +263,13 @@ export default function TournamentRegisterPage() {
})
: null}
</div>
<div className="stack horizontal sm mt-1">
{modesIncluded(parentRouteData.event).map((mode) => (
<div key={mode} className="tournament___info__mode-container">
<ModeImage mode={mode} size={18} />
</div>
))}
</div>
</div>
</div>
</div>

View File

@ -57,7 +57,7 @@ export const action: ActionFunction = async ({ request, params }) => {
const hasStarted = hasTournamentStarted(tournamentId);
validate(canAdminTournament({ user, event: tournament }));
validate(hasStarted, "Tournament has started");
validate(!hasStarted, "Tournament has started");
updateTeamSeeds({ tournamentId, teamIds: data.seeds });

View File

@ -23,7 +23,7 @@ import type { Unpacked } from "~/utils/types";
import { findByIdentifier } from "../queries/findByIdentifier.server";
import type { FindTeamsByTournamentId } from "../queries/findTeamsByTournamentId.server";
import { findTeamsByTournamentId } from "../queries/findTeamsByTournamentId.server";
import { tournamentIdFromParams } from "../tournament-utils";
import { teamHasCheckedIn, tournamentIdFromParams } from "../tournament-utils";
import styles from "../tournament.css";
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
@ -72,14 +72,16 @@ export const loader = async ({ params, request }: LoaderArgs) => {
const mapListGeneratorAvailable =
canAdminTournament({ user, event }) || event.showMapListGenerator;
const teams = findTeamsByTournamentId(tournamentId);
const hasStarted = hasTournamentStarted(tournamentId);
let teams = findTeamsByTournamentId(tournamentId);
if (hasStarted) {
teams = teams.filter(teamHasCheckedIn);
}
const ownedTeamId = teams.find((team) =>
team.members.some((member) => member.userId === user?.id && member.isOwner)
)?.id;
const hasStarted = hasTournamentStarted(tournamentId);
return {
event,
tieBreakerMapPool: db.calendarEvents.findTieBreakerMapPoolByEventId(

View File

@ -154,6 +154,12 @@
padding: var(--s-1) 0;
}
.tournament___info__mode-container {
background-color: var(--bg-lighter);
border-radius: 100%;
padding: var(--s-2);
}
.tournament__by {
color: var(--text-lighter);
font-size: var(--fonts-sm);

View File

@ -1077,6 +1077,14 @@ dialog::backdrop {
}
#nprogress .bar {
background: var(--theme) !important;
margin-top: 3rem !important;
background: var(--theme) !important;
}
#nprogress .spinner {
display: none !important;
}
#nprogress .peg {
display: none !important;
}

View File

@ -134,6 +134,10 @@
margin-block-start: var(--s-0);
}
.mt-1 {
margin-block-start: var(--s-1);
}
.mt-2 {
margin-block-start: var(--s-2);
}