mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-23 19:46:28 -05:00
Disable buttons on /start if round being edited
This commit is contained in:
@@ -14,39 +14,41 @@ export function useTournamentRounds(args: UseTournamentRoundsArgs) {
|
||||
switch (action.type) {
|
||||
case "START_EDITING_ROUND": {
|
||||
const newState = clone(oldState);
|
||||
newState[action.data.side] = newState[action.data.side].map(
|
||||
(round, i) =>
|
||||
i === action.data.index
|
||||
? { ...round, editing: true, newMapList: [...round.mapList] }
|
||||
: round
|
||||
newState.bracket[action.data.side] = newState.bracket[
|
||||
action.data.side
|
||||
].map((round, i) =>
|
||||
i === action.data.index
|
||||
? { ...round, editing: true, newMapList: [...round.mapList] }
|
||||
: round
|
||||
);
|
||||
|
||||
return newState;
|
||||
return calculateRoundsBeingEditedAndAdjustState(newState);
|
||||
}
|
||||
case "CANCEL_EDITING_ROUND": {
|
||||
const newState = clone(oldState);
|
||||
newState[action.data.side] = newState[action.data.side].map(
|
||||
(round, i) =>
|
||||
i === action.data.index ? { ...round, editing: false } : round
|
||||
newState.bracket[action.data.side] = newState.bracket[
|
||||
action.data.side
|
||||
].map((round, i) =>
|
||||
i === action.data.index ? { ...round, editing: false } : round
|
||||
);
|
||||
|
||||
return newState;
|
||||
return calculateRoundsBeingEditedAndAdjustState(newState);
|
||||
}
|
||||
case "SAVE_ROUND": {
|
||||
const newState = clone(oldState);
|
||||
newState[action.data.side] = newState[action.data.side].map(
|
||||
(round, i) => {
|
||||
if (i !== action.data.index) return round;
|
||||
invariant(round.newMapList, "round.newMapList is undefined");
|
||||
return { ...round, editing: false, mapList: round.newMapList };
|
||||
}
|
||||
);
|
||||
newState.bracket[action.data.side] = newState.bracket[
|
||||
action.data.side
|
||||
].map((round, i) => {
|
||||
if (i !== action.data.index) return round;
|
||||
invariant(round.newMapList, "round.newMapList is undefined");
|
||||
return { ...round, editing: false, mapList: round.newMapList };
|
||||
});
|
||||
|
||||
return newState;
|
||||
return calculateRoundsBeingEditedAndAdjustState(newState);
|
||||
}
|
||||
case "EDIT_STAGE": {
|
||||
const newState = clone(oldState);
|
||||
const roundToEdit = newState[action.data.side].find(
|
||||
const roundToEdit = newState.bracket[action.data.side].find(
|
||||
(_, i) => i === action.data.index
|
||||
);
|
||||
invariant(
|
||||
@@ -64,43 +66,70 @@ export function useTournamentRounds(args: UseTournamentRoundsArgs) {
|
||||
return newState;
|
||||
}
|
||||
case "REGENERATE_MAP_LIST": {
|
||||
return regenMapList(oldState);
|
||||
return regenMapList({ oldState });
|
||||
}
|
||||
case "SET_ROUND_BEST_OF": {
|
||||
const newState = clone(oldState);
|
||||
newState[action.data.side][action.data.index].bestOf =
|
||||
newState.bracket[action.data.side][action.data.index].bestOf =
|
||||
action.data.newBestOf;
|
||||
|
||||
return regenMapList(newState);
|
||||
return regenMapList({ oldState, newState });
|
||||
}
|
||||
case "SHOW_ALERT": {
|
||||
return { ...oldState, showAlert: true };
|
||||
}
|
||||
default: {
|
||||
return oldState;
|
||||
}
|
||||
}
|
||||
},
|
||||
args.initialState
|
||||
{ bracket: args.initialState, showAlert: false }
|
||||
);
|
||||
|
||||
function regenMapList(
|
||||
state: UseTournamentRoundsState
|
||||
): UseTournamentRoundsState {
|
||||
function regenMapList({
|
||||
oldState,
|
||||
newState: _newState,
|
||||
}: {
|
||||
oldState: UseTournamentRoundsState;
|
||||
newState?: UseTournamentRoundsState;
|
||||
}): UseTournamentRoundsState {
|
||||
const newState = _newState ?? oldState;
|
||||
const newMapLists = generateMapListForRounds({
|
||||
mapPool: args.mapPool,
|
||||
rounds: {
|
||||
winners: state.winners.map((r) => r.bestOf),
|
||||
losers: state.losers.map((r) => r.bestOf),
|
||||
winners: newState.bracket.winners.map((r) => r.bestOf),
|
||||
losers: newState.bracket.losers.map((r) => r.bestOf),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
winners: state.winners.map((round, i) => ({
|
||||
...round,
|
||||
mapList: newMapLists.winners[i],
|
||||
})),
|
||||
losers: state.losers.map((round, i) => ({
|
||||
...round,
|
||||
mapList: newMapLists.losers[i],
|
||||
})),
|
||||
showAlert: oldState.showAlert,
|
||||
bracket: {
|
||||
winners: newState.bracket.winners.map((round, i) => ({
|
||||
...round,
|
||||
mapList: newMapLists.winners[i],
|
||||
})),
|
||||
losers: newState.bracket.losers.map((round, i) => ({
|
||||
...round,
|
||||
mapList: newMapLists.losers[i],
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function calculateRoundsBeingEditedAndAdjustState(
|
||||
newState: UseTournamentRoundsState
|
||||
): UseTournamentRoundsState {
|
||||
const beingEditedCount = [newState.bracket.winners, newState.bracket.losers]
|
||||
.flat()
|
||||
.reduce((acc, cur) => {
|
||||
return acc + (cur.editing ? 1 : 0);
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
...newState,
|
||||
showAlert: beingEditedCount === 0 ? false : newState.showAlert,
|
||||
actionButtonsDisabled: beingEditedCount > 0 ? true : false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,22 @@ import type {
|
||||
} from "~/core/tournament/bracket";
|
||||
import { MyReducerAction } from "~/utils";
|
||||
|
||||
export type UseTournamentRoundsState = EliminationBracket<
|
||||
{
|
||||
bestOf: BestOf;
|
||||
name: string;
|
||||
mapList: Stage[];
|
||||
newMapList?: Stage[];
|
||||
editing?: boolean;
|
||||
}[]
|
||||
>;
|
||||
export type UseTournamentRoundsState = {
|
||||
bracket: EliminationBracket<
|
||||
{
|
||||
bestOf: BestOf;
|
||||
name: string;
|
||||
mapList: Stage[];
|
||||
newMapList?: Stage[];
|
||||
editing?: boolean;
|
||||
}[]
|
||||
>;
|
||||
showAlert: boolean;
|
||||
actionButtonsDisabled?: boolean;
|
||||
};
|
||||
|
||||
export interface UseTournamentRoundsArgs {
|
||||
initialState: UseTournamentRoundsState;
|
||||
initialState: UseTournamentRoundsState["bracket"];
|
||||
mapPool: Stage[];
|
||||
}
|
||||
|
||||
@@ -47,20 +51,5 @@ export type UseTournamentRoundsAction =
|
||||
| MyReducerAction<
|
||||
"SET_ROUND_BEST_OF",
|
||||
{ newBestOf: BestOf; side: EliminationBracketSide; index: number }
|
||||
>;
|
||||
|
||||
// export type Action =
|
||||
// | {
|
||||
// type: "SET_PLAYER";
|
||||
// name: string;
|
||||
// number: number;
|
||||
// }
|
||||
// | { type: "CREATE_FIRST_MATCH" }
|
||||
// | { type: "SET_WINNER"; winner: "alpha" | "bravo" }
|
||||
// | {
|
||||
// type: "SET_AMOUNT_OF_ROUNDS_WITH_SAME_TEAMS";
|
||||
// amountOfRoundsWithSameTeams: number;
|
||||
// }
|
||||
// | { type: "UNDO_LATEST_MATCH" }
|
||||
// | { type: "RESET" }
|
||||
// | { type: "SET_NO_PLACING_TO_SAME_TEAM"; id: number; checked: boolean };
|
||||
>
|
||||
| MyReducerAction<"SHOW_ALERT">;
|
||||
|
||||
@@ -3,6 +3,7 @@ import classNames from "classnames";
|
||||
import type { LinksFunction, LoaderFunction } from "remix";
|
||||
import { json, useLoaderData } from "remix";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Alert } from "~/components/Alert";
|
||||
import { Button } from "~/components/Button";
|
||||
import { modesShort, modesShortToLong } from "~/constants";
|
||||
import { eliminationBracket } from "~/core/tournament/algorithms";
|
||||
@@ -19,7 +20,6 @@ import type {
|
||||
import { findTournamentByNameForUrl } from "~/services/tournament";
|
||||
import startBracketTabStylesUrl from "~/styles/tournament-start.css";
|
||||
|
||||
// TODO: some warning if trying to continue and some round card is editing
|
||||
// TODO: error if not admin AND keep the links available
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
@@ -61,45 +61,78 @@ export const loader: LoaderFunction = async ({ params }) => {
|
||||
// TODO: handle warning if check-in has not concluded
|
||||
export default function StartBracketTab() {
|
||||
const args = useLoaderData<UseTournamentRoundsArgs>();
|
||||
const [rounds, dispatch] = useTournamentRounds(args);
|
||||
const [{ bracket, showAlert, actionButtonsDisabled }, dispatch] =
|
||||
useTournamentRounds(args);
|
||||
|
||||
return (
|
||||
<div className="tournament__start__container">
|
||||
<ActionButtons dispatch={dispatch} />
|
||||
<ActionButtons
|
||||
dispatch={dispatch}
|
||||
disabled={actionButtonsDisabled}
|
||||
showAlert={showAlert}
|
||||
position="top"
|
||||
/>
|
||||
<div className="tournament__start__round-collections-container">
|
||||
<RoundsCollection
|
||||
side="winners"
|
||||
dispatch={dispatch}
|
||||
rounds={rounds.winners}
|
||||
rounds={bracket.winners}
|
||||
/>
|
||||
{args.initialState.losers.length > 0 && (
|
||||
<RoundsCollection
|
||||
side="losers"
|
||||
dispatch={dispatch}
|
||||
rounds={rounds.losers}
|
||||
rounds={bracket.losers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ActionButtons dispatch={dispatch} />
|
||||
<ActionButtons
|
||||
dispatch={dispatch}
|
||||
disabled={actionButtonsDisabled}
|
||||
showAlert={showAlert}
|
||||
position="bottom"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ActionButtons({
|
||||
dispatch,
|
||||
disabled,
|
||||
showAlert,
|
||||
position,
|
||||
}: {
|
||||
dispatch: React.Dispatch<UseTournamentRoundsAction>;
|
||||
disabled?: boolean;
|
||||
showAlert: boolean;
|
||||
position: "top" | "bottom";
|
||||
}) {
|
||||
return (
|
||||
<div className="tournament__start__buttons-container">
|
||||
<Button>Preview bracket</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => dispatch({ type: "REGENERATE_MAP_LIST" })}
|
||||
>
|
||||
Regenerate all maps
|
||||
</Button>
|
||||
</div>
|
||||
<>
|
||||
{position === "bottom" && showAlert && (
|
||||
<Alert type="warning">
|
||||
Save or cancel the round being edited to continue or regenerate maps
|
||||
</Alert>
|
||||
)}
|
||||
<div className="tournament__start__action-buttons__container">
|
||||
<Button>Preview bracket</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() =>
|
||||
disabled
|
||||
? dispatch({ type: "SHOW_ALERT" })
|
||||
: dispatch({ type: "REGENERATE_MAP_LIST" })
|
||||
}
|
||||
>
|
||||
Regenerate all maps
|
||||
</Button>
|
||||
</div>
|
||||
{position === "top" && showAlert && (
|
||||
<Alert type="warning">
|
||||
Save or cancel the round being edited to continue or regenerate maps
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,7 +143,7 @@ function RoundsCollection({
|
||||
}: {
|
||||
side: EliminationBracketSide;
|
||||
dispatch: React.Dispatch<UseTournamentRoundsAction>;
|
||||
rounds: UseTournamentRoundsState["winners"];
|
||||
rounds: UseTournamentRoundsState["bracket"]["winners"];
|
||||
}) {
|
||||
const { mapPool } = useLoaderData<UseTournamentRoundsArgs>();
|
||||
|
||||
@@ -205,7 +238,7 @@ function RoundsCollection({
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
<div className="tournament__start__round-card-buttons-container">
|
||||
<div className="tournament__start__round-card-buttons__container">
|
||||
{round.editing ? (
|
||||
<>
|
||||
<Button
|
||||
|
||||
@@ -11,12 +11,18 @@
|
||||
gap: var(--s-6);
|
||||
}
|
||||
|
||||
.tournament__start__buttons-container {
|
||||
.tournament__start__action-buttons__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--s-1-5);
|
||||
}
|
||||
|
||||
.tournament__start__action-buttons__info {
|
||||
color: var(--theme-error);
|
||||
font-size: var(--fonts-xs);
|
||||
}
|
||||
|
||||
.tournament__start__round-collections-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -77,7 +83,7 @@
|
||||
padding-inline: var(--s-1);
|
||||
}
|
||||
|
||||
.tournament__start__round-card-buttons-container {
|
||||
.tournament__start__round-card-buttons__container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--s-4);
|
||||
|
||||
Reference in New Issue
Block a user