Submit map pool initial

This commit is contained in:
Kalle 2022-10-09 12:27:09 +03:00
parent 20a5beb119
commit e00c7cf531
8 changed files with 177 additions and 13 deletions

View File

@ -41,6 +41,11 @@ export const BUILD = {
MAX_COUNT: 250,
} as const;
export const MAPS = {
CODE_MIN_LENGTH: 2,
CODE_MAX_LENGTH: 32,
};
export const BUILDS_PAGE_BATCH_SIZE = 24;
export const BUILDS_PAGE_MAX_BUILDS = 240;

View File

@ -4,6 +4,7 @@ import * as plusVotes from "./models/plusVotes/queries.server";
import * as badges from "./models/badges/queries.server";
import * as calendarEvents from "./models/calendar/queries.server";
import * as builds from "./models/builds/queries.server";
import * as maps from "./models/maps/queries.server";
export const db = {
users,
@ -12,4 +13,5 @@ export const db = {
badges,
calendarEvents,
builds,
maps,
};

View File

@ -0,0 +1,4 @@
insert into
"MapPool" ("code", "ownerId")
values
(@code, @ownerId) returning *

View File

@ -0,0 +1,4 @@
insert into
"MapPoolMap" ("mapPoolId", "stageId", "mode")
values
(@mapPoolId, @stageId, @mode)

View File

@ -0,0 +1,28 @@
import { sql } from "~/db/sql";
import type { MapPool, MapPoolMap } from "~/db/types";
import createMapPoolSql from "./createMapPool.sql";
import createMapPoolMapSql from "./createMapPoolMap.sql";
const createMapPoolStm = sql.prepare(createMapPoolSql);
const createMapPoolMapStm = sql.prepare(createMapPoolMapSql);
export const addMapPool = sql.transaction(
({
ownerId,
code,
maps,
}: {
ownerId: MapPool["id"];
code: MapPool["code"];
maps: Array<Pick<MapPoolMap, "mode" | "stageId">>;
}) => {
const mapPool = createMapPoolStm.get({ ownerId, code }) as MapPool;
for (const args of maps) {
createMapPoolMapStm.run({
mapPoolId: mapPool.id,
...args,
});
}
}
);

View File

@ -1,4 +1,9 @@
import type { Ability, MainWeaponId } from "~/modules/in-game-lists";
import type {
Ability,
MainWeaponId,
ModeShort,
StageId,
} from "~/modules/in-game-lists";
import type allTags from "../routes/calendar/tags.json";
export interface User {
@ -95,6 +100,7 @@ export interface CalendarEvent {
discordUrl: string | null;
bracketUrl: string;
participantCount: number | null;
mapPoolId?: number;
}
export type CalendarEventTag = keyof typeof allTags;
@ -148,3 +154,15 @@ export interface BuildAbility {
ability: Ability;
slotIndex: 0 | 1 | 2 | 3;
}
export interface MapPool {
id: number;
code: string;
ownerId: number;
}
export interface MapPoolMap {
mapPoolId: number;
stageId: StageId;
mode: ModeShort;
}

View File

@ -1,11 +1,12 @@
import type { LinksFunction } from "@remix-run/node";
import type { ActionFunction, LinksFunction } from "@remix-run/node";
import { useTranslation } from "react-i18next";
import { Image } from "~/components/Image";
import { Main } from "~/components/Main";
import type {
ModeShort,
ModeWithStage,
StageId,
import {
modesShort,
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";
@ -17,8 +18,8 @@ import {
mapPoolToSerializedString,
serializedStringToMapPool,
} from "~/modules/map-pool-serializer";
import { useUser } from "~/modules/auth";
import { ADMIN_DISCORD_ID } from "~/constants";
import { 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";
@ -31,6 +32,9 @@ import {
} 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";
const AMOUNT_OF_MAPS_IN_MAP_LIST = stageIds.length * 2;
@ -42,6 +46,33 @@ export const handle = {
i18n: "game-misc",
};
// xxx: next -> define user flow after submitting a map pool
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 null;
};
const DEFAULT_MAP_POOL = {
SZ: [...stageIds],
TC: [...stageIds],
@ -55,7 +86,7 @@ export default function MapListPage() {
return (
<Main className="maps__container stack lg">
<MapPoolLoaderSaver />
<MapPoolLoaderSaver mapPool={mapPool} />
<MapPoolSelector
mapPool={mapPool}
handleMapPoolChange={handleMapPoolChange}
@ -167,15 +198,44 @@ function MapPoolSelector({
);
}
function MapPoolLoaderSaver() {
// xxx: show "log in to save map pools" if not logged in
function MapPoolLoaderSaver({ mapPool }: { mapPool: MapPool }) {
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">
<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" />
<Input
name="code"
minLength={MAPS.CODE_MIN_LENGTH}
maxLength={MAPS.CODE_MAX_LENGTH}
/>
</div>
<Button icon={<DownloadIcon />} variant="outlined" type="submit">
Load map pool
{hasChanges ? "Save map pool" : "Load map pool"}
</Button>
</Form>
);

43
migrations/009-maps.js Normal file
View File

@ -0,0 +1,43 @@
module.exports.up = function (db) {
db.prepare(
`
create table "MapPool" (
"id" integer primary key,
"code" text not null,
"ownerId" integer not null,
foreign key ("ownerId") references "User"("id") on delete restrict
) strict
`
).run();
db.prepare(`create index map_pool_owner_id on "MapPool"("ownerId")`).run();
db.prepare(
`
create table "MapPoolMap" (
"mapPoolId" integer not null,
"stageId" integer not null,
"mode" text not null,
foreign key ("mapPoolId") references "MapPool"("id") on delete cascade
) strict
`
).run();
db.prepare(
`create index map_pool_map_map_pool_id on "MapPoolMap"("mapPoolId")`
).run();
db.prepare(
`alter table "CalendarEvent" add "mapPoolId" integer references "MapPool"("id") on delete set null`
).run();
db.prepare(
`create index calendar_event_map_pool_id on "CalendarEvent"("mapPoolId")`
).run();
};
module.exports.down = function (db) {
for (const table of ["MapPool", "MapPoolMap"]) {
db.prepare(`drop table "${table}"`).run();
}
db.prepare(`drop index calendar_event_map_pool_id`).run();
db.prepare(`alter table "CalendarEvent" drop column "mapPoolId"`).run();
};