mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-25 15:56:19 -05:00
Submit map pool initial
This commit is contained in:
parent
20a5beb119
commit
e00c7cf531
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
4
app/db/models/maps/createMapPool.sql
Normal file
4
app/db/models/maps/createMapPool.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
insert into
|
||||
"MapPool" ("code", "ownerId")
|
||||
values
|
||||
(@code, @ownerId) returning *
|
||||
4
app/db/models/maps/createMapPoolMap.sql
Normal file
4
app/db/models/maps/createMapPoolMap.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
insert into
|
||||
"MapPoolMap" ("mapPoolId", "stageId", "mode")
|
||||
values
|
||||
(@mapPoolId, @stageId, @mode)
|
||||
28
app/db/models/maps/queries.server.ts
Normal file
28
app/db/models/maps/queries.server.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
43
migrations/009-maps.js
Normal 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();
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user