mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-27 05:36:28 -05:00
Remove action related code from maps page
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import type { ModeShort } from "../in-game-lists";
|
||||
import type { StageId } from "../in-game-lists";
|
||||
|
||||
// xxx: rename - same name as DB table
|
||||
export type MapPool = Record<ModeShort, StageId[]>;
|
||||
|
||||
@@ -233,9 +233,11 @@ function Description() {
|
||||
<Avatar user={data.event} size="xs" />
|
||||
{discordFullName(data.event)}
|
||||
</div>
|
||||
<div data-cy="event-description" className="whitespace-pre-wrap">
|
||||
{data.event.description}
|
||||
</div>
|
||||
{data.event.description && (
|
||||
<div data-cy="event-description" className="whitespace-pre-wrap">
|
||||
{data.event.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@@ -1,54 +1,42 @@
|
||||
import type {
|
||||
ActionFunction,
|
||||
LinksFunction,
|
||||
LoaderArgs,
|
||||
} from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import type { LinksFunction, LoaderArgs } from "@remix-run/node";
|
||||
import type { ShouldReloadFunction } from "@remix-run/react";
|
||||
import { Link } from "@remix-run/react";
|
||||
import { useLoaderData, useSearchParams } from "@remix-run/react";
|
||||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Button } from "~/components/Button";
|
||||
import { Image } from "~/components/Image";
|
||||
import { Label } from "~/components/Label";
|
||||
import { Main } from "~/components/Main";
|
||||
import { Toggle } from "~/components/Toggle";
|
||||
import { ADMIN_DISCORD_ID } from "~/constants";
|
||||
import { db } from "~/db";
|
||||
import { useUser } from "~/modules/auth";
|
||||
import {
|
||||
modesShort,
|
||||
modes,
|
||||
stageIds,
|
||||
type ModeShort,
|
||||
type ModeWithStage,
|
||||
type StageId,
|
||||
} from "~/modules/in-game-lists";
|
||||
import { modes, stageIds } from "~/modules/in-game-lists";
|
||||
import type { MapPool } from "~/modules/map-pool-serializer/types";
|
||||
import styles from "~/styles/maps.css";
|
||||
import { mapsPage, modeImageUrl, stageImageUrl } from "~/utils/urls";
|
||||
import clsx from "clsx";
|
||||
import type { ShouldReloadFunction } from "@remix-run/react";
|
||||
import { Form, useLoaderData, useSearchParams } from "@remix-run/react";
|
||||
import {
|
||||
mapPoolToSerializedString,
|
||||
serializedStringToMapPool,
|
||||
} from "~/modules/map-pool-serializer";
|
||||
import { getUser, requireUser, useUser } from "~/modules/auth";
|
||||
import { ADMIN_DISCORD_ID, MAPS } from "~/constants";
|
||||
import { Button } from "~/components/Button";
|
||||
import { Input } from "~/components/Input";
|
||||
import { Label } from "~/components/Label";
|
||||
import { DownloadIcon } from "~/components/icons/Download";
|
||||
import { Toggle } from "~/components/Toggle";
|
||||
import {
|
||||
generateMapList,
|
||||
mapPoolToNonEmptyModes,
|
||||
modesOrder,
|
||||
} from "~/modules/map-list-generator";
|
||||
import * as React from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { z } from "zod";
|
||||
import { parseRequestFormData } from "~/utils/remix";
|
||||
import { db } from "~/db";
|
||||
import { discordFullName } from "~/utils/strings";
|
||||
import { UploadIcon } from "~/components/icons/Upload";
|
||||
import {
|
||||
mapPoolToSerializedString,
|
||||
serializedStringToMapPool,
|
||||
} from "~/modules/map-pool-serializer";
|
||||
import type { MapPool } from "~/modules/map-pool-serializer/types";
|
||||
import styles from "~/styles/maps.css";
|
||||
import { calendarEventPage, modeImageUrl, stageImageUrl } from "~/utils/urls";
|
||||
|
||||
const AMOUNT_OF_MAPS_IN_MAP_LIST = stageIds.length * 2;
|
||||
|
||||
export const unstable_shouldReload: ShouldReloadFunction = ({ url }) => {
|
||||
return Boolean(url.searchParams.get("code"));
|
||||
};
|
||||
export const unstable_shouldReload: ShouldReloadFunction = () => false;
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
@@ -58,43 +46,24 @@ export const handle = {
|
||||
i18n: "game-misc",
|
||||
};
|
||||
|
||||
// xxx: check: limit how many map pools can be submitted maybe
|
||||
// xxx: next -> define user flow after submitting a map pool
|
||||
// xxx: code can't have spaces or special characters also convert upper case to lower case
|
||||
const mapsActionSchema = z.object({
|
||||
code: z.string().min(MAPS.CODE_MIN_LENGTH).max(MAPS.CODE_MAX_LENGTH),
|
||||
pool: z.string(),
|
||||
});
|
||||
|
||||
export const action: ActionFunction = async ({ request }) => {
|
||||
const user = await requireUser(request);
|
||||
const data = await parseRequestFormData({
|
||||
request,
|
||||
schema: mapsActionSchema,
|
||||
});
|
||||
|
||||
const mapPool = serializedStringToMapPool(data.pool);
|
||||
const maps = Object.entries(mapPool).flatMap(([mode, stages]) =>
|
||||
stages.flatMap((stageId) => ({ mode: mode as ModeShort, stageId }))
|
||||
);
|
||||
|
||||
db.maps.addMapPool({
|
||||
ownerId: user.id,
|
||||
code: data.code,
|
||||
maps,
|
||||
});
|
||||
|
||||
return redirect(mapsPage(data.code));
|
||||
};
|
||||
|
||||
export const loader = async ({ request }: LoaderArgs) => {
|
||||
const user = await getUser(request);
|
||||
export const loader = ({ request }: LoaderArgs) => {
|
||||
const url = new URL(request.url);
|
||||
const code = url.searchParams.get("code");
|
||||
const calendarEventId = url.searchParams.get("eventId");
|
||||
|
||||
const event = calendarEventId
|
||||
? db.calendarEvents.findById(Number(calendarEventId))
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
mapPool: code ? db.maps.findMapPoolByCode(code) : null,
|
||||
ownCodes: user ? db.maps.codesByUserId(user.id) : null,
|
||||
calendarEvent: event
|
||||
? {
|
||||
id: event.eventId,
|
||||
name: event.name,
|
||||
}
|
||||
: undefined,
|
||||
mapPool: event
|
||||
? db.calendarEvents.findMapPoolByEventId(event.eventId)
|
||||
: null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -107,7 +76,9 @@ const DEFAULT_MAP_POOL = {
|
||||
};
|
||||
|
||||
export default function MapListPage() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const user = useUser();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { mapPool, handleMapPoolChange } = useSearchParamMapPool();
|
||||
|
||||
if (
|
||||
@@ -119,7 +90,14 @@ export default function MapListPage() {
|
||||
|
||||
return (
|
||||
<Main className="maps__container stack lg">
|
||||
<MapPoolLoaderSaver mapPool={mapPool} />
|
||||
{data.calendarEvent && !searchParams.has("pool") && (
|
||||
<div className="maps__pool-info">
|
||||
Map pool:{" "}
|
||||
<Link to={calendarEventPage(data.calendarEvent.id)}>
|
||||
{data.calendarEvent.name}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<MapPoolSelector
|
||||
mapPool={mapPool}
|
||||
handleMapPoolChange={handleMapPoolChange}
|
||||
@@ -134,30 +112,14 @@ function useSearchParamMapPool() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const mapPool = (() => {
|
||||
if (data?.mapPool) {
|
||||
return {
|
||||
TW: data.mapPool.maps
|
||||
.filter((m) => m.mode === "TW")
|
||||
.map((m) => m.stageId),
|
||||
SZ: data.mapPool.maps
|
||||
.filter((m) => m.mode === "SZ")
|
||||
.map((m) => m.stageId),
|
||||
TC: data.mapPool.maps
|
||||
.filter((m) => m.mode === "TC")
|
||||
.map((m) => m.stageId),
|
||||
CB: data.mapPool.maps
|
||||
.filter((m) => m.mode === "CB")
|
||||
.map((m) => m.stageId),
|
||||
RM: data.mapPool.maps
|
||||
.filter((m) => m.mode === "RM")
|
||||
.map((m) => m.stageId),
|
||||
};
|
||||
}
|
||||
|
||||
if (searchParams.has("pool")) {
|
||||
return serializedStringToMapPool(searchParams.get("pool")!);
|
||||
}
|
||||
|
||||
if (data?.mapPool) {
|
||||
return data.mapPool;
|
||||
}
|
||||
|
||||
return DEFAULT_MAP_POOL;
|
||||
})();
|
||||
|
||||
@@ -199,12 +161,8 @@ function MapPoolSelector({
|
||||
mapPool: MapPool;
|
||||
handleMapPoolChange: (args: { mode: ModeShort; stageId: StageId }) => void;
|
||||
}) {
|
||||
const user = useUser();
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const { t } = useTranslation(["game-misc"]);
|
||||
|
||||
const editMode = !data.mapPool || data.mapPool?.owner.id === user?.id;
|
||||
|
||||
return (
|
||||
<div className="stack md">
|
||||
{stageIds.map((stageId) => (
|
||||
@@ -227,12 +185,10 @@ function MapPoolSelector({
|
||||
key={mode.short}
|
||||
className={clsx("maps__mode-button", "outline-theme", {
|
||||
selected,
|
||||
hidden: !editMode && !selected,
|
||||
})}
|
||||
onClick={() =>
|
||||
handleMapPoolChange({ mode: mode.short, stageId })
|
||||
}
|
||||
disabled={!editMode}
|
||||
type="button"
|
||||
>
|
||||
<Image
|
||||
@@ -255,80 +211,6 @@ function MapPoolSelector({
|
||||
);
|
||||
}
|
||||
|
||||
// xxx: show "log in to save map pools" if not logged in
|
||||
function MapPoolLoaderSaver({ mapPool }: { mapPool: MapPool }) {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
if (data.mapPool) {
|
||||
return (
|
||||
<div className="maps__pool_info">
|
||||
<div>
|
||||
Code: <code>{data.mapPool.code}</code> - Made by:{" "}
|
||||
{discordFullName(data.mapPool.owner)}
|
||||
</div>
|
||||
<div className="stack horizontal sm">
|
||||
<Button tiny>Change pool</Button>
|
||||
<Button tiny>Save 3 changes</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const hasChanges = (() => {
|
||||
for (const mode of modesShort) {
|
||||
if (mapPool[mode].length !== DEFAULT_MAP_POOL[mode].length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const stageId of mapPool[mode]) {
|
||||
if (!(DEFAULT_MAP_POOL[mode] as StageId[]).includes(stageId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})();
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="maps__pool-loader-saver"
|
||||
method={hasChanges ? "post" : "get"}
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="pool"
|
||||
value={mapPoolToSerializedString(mapPool)}
|
||||
/>
|
||||
<div>
|
||||
<Label>Code</Label>
|
||||
{/* <Input
|
||||
name="code"
|
||||
minLength={MAPS.CODE_MIN_LENGTH}
|
||||
maxLength={MAPS.CODE_MAX_LENGTH}
|
||||
/> */}
|
||||
<Input name="code" list="ownCodes" />
|
||||
{data.ownCodes && (
|
||||
<datalist id="ownCodes">
|
||||
{data.ownCodes.map((code) => (
|
||||
<option key={code} value={code} />
|
||||
))}
|
||||
</datalist>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
icon={hasChanges ? <UploadIcon /> : <DownloadIcon />}
|
||||
variant="outlined"
|
||||
type="submit"
|
||||
>
|
||||
{hasChanges ? "Save map pool" : "Load map pool"}
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
// xxx: next maybe own map pool being viewed is always editable but other is view only?
|
||||
|
||||
// xxx: crashes if only one map in mode
|
||||
function MapListCreator({ mapPool }: { mapPool: MapPool }) {
|
||||
const { t } = useTranslation(["game-misc"]);
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.maps__pool-loader-saver {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: var(--s-4);
|
||||
.maps__pool-info {
|
||||
font-size: var(--fonts-xxs);
|
||||
font-weight: var(--semi-bold);
|
||||
}
|
||||
|
||||
.maps__stage-row {
|
||||
@@ -72,14 +70,6 @@
|
||||
gap: var(--s-4);
|
||||
}
|
||||
|
||||
.maps__pool_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: var(--fonts-xs);
|
||||
font-weight: var(--semi-bold);
|
||||
gap: var(--s-2);
|
||||
}
|
||||
|
||||
.maps__toggle-container {
|
||||
--label-margin: 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user