Remove action related code from maps page

This commit is contained in:
Kalle
2022-10-13 20:36:02 +03:00
parent 4108637a30
commit 0f2a5ee728
4 changed files with 60 additions and 187 deletions

View File

@@ -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[]>;

View File

@@ -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>
);

View File

@@ -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"]);

View File

@@ -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;