diff --git a/app/components/Input.tsx b/app/components/Input.tsx index e364223b9..12f643dce 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -8,6 +8,7 @@ export function Input({ defaultValue, leftAddon, pattern, + list, "data-cy": dataCy, }: { name: string; @@ -17,6 +18,7 @@ export function Input({ defaultValue?: string; leftAddon?: string; pattern?: string; + list?: string; "data-cy"?: string; }) { return ( @@ -27,8 +29,9 @@ export function Input({ minLength={minLength} maxLength={maxLength} defaultValue={defaultValue} - data-cy={dataCy} pattern={pattern} + list={list} + data-cy={dataCy} /> ); diff --git a/app/components/layout/nav-items.json b/app/components/layout/nav-items.json index 3f7b5ba1f..a31567fa2 100644 --- a/app/components/layout/nav-items.json +++ b/app/components/layout/nav-items.json @@ -6,6 +6,7 @@ { "name": "builds", "url": "builds" }, { "name": "analyzer", "url": "analyzer" }, { "name": "calendar", "url": "calendar" }, + { "name": "maps", "url": "maps" }, { "name": "badges", "url": "badges" }, { "name": "plus", diff --git a/app/db/index.ts b/app/db/index.ts index 77fecd210..69ed79a04 100644 --- a/app/db/index.ts +++ b/app/db/index.ts @@ -4,7 +4,6 @@ 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, @@ -13,5 +12,4 @@ export const db = { badges, calendarEvents, builds, - maps, }; diff --git a/app/db/models/calendar/createMapPoolMap.sql b/app/db/models/calendar/createMapPoolMap.sql new file mode 100644 index 000000000..9f26e4f3a --- /dev/null +++ b/app/db/models/calendar/createMapPoolMap.sql @@ -0,0 +1,4 @@ +insert into + "MapPoolMap" ("calendarEventId", "stageId", "mode") +values + (@calendarEventId, @stageId, @mode) diff --git a/app/db/models/calendar/deleteMapPoolMaps.sql b/app/db/models/calendar/deleteMapPoolMaps.sql new file mode 100644 index 000000000..6c9de7b40 --- /dev/null +++ b/app/db/models/calendar/deleteMapPoolMaps.sql @@ -0,0 +1,4 @@ +delete from + "MapPoolMap" +where + "calendarEventId" = @calendarEventId diff --git a/app/db/models/calendar/findMapPoolByEventId.sql b/app/db/models/calendar/findMapPoolByEventId.sql new file mode 100644 index 000000000..137d77cd2 --- /dev/null +++ b/app/db/models/calendar/findMapPoolByEventId.sql @@ -0,0 +1,7 @@ +select + "stageId", + "mode" +from + "MapPoolMap" +where + "calendarEventId" = @calendarEventId diff --git a/app/db/models/calendar/queries.server.ts b/app/db/models/calendar/queries.server.ts index 90d367c24..5cfb87735 100644 --- a/app/db/models/calendar/queries.server.ts +++ b/app/db/models/calendar/queries.server.ts @@ -9,7 +9,9 @@ import type { CalendarEventBadge, CalendarEventResultTeam, CalendarEventResultPlayer, + MapPoolMap, } from "../../types"; +import { mapPoolListToMapPoolObject } from "~/modules/map-list-generator"; import createSql from "./create.sql"; import updateSql from "./update.sql"; @@ -31,6 +33,9 @@ import findBadgesByEventIdSql from "./findBadgesByEventId.sql"; import eventsToReportSql from "./eventsToReport.sql"; import recentWinnersSql from "./recentWinners.sql"; import upcomingEventsSql from "./upcomingEvents.sql"; +import createMapPoolMapSql from "./createMapPoolMap.sql"; +import deleteMapPoolMapsSql from "./deleteMapPoolMaps.sql"; +import findMapPoolByEventIdSql from "./findMapPoolByEventId.sql"; const createStm = sql.prepare(createSql); const updateStm = sql.prepare(updateSql); @@ -38,6 +43,9 @@ const createDateStm = sql.prepare(createDateSql); const deleteDatesByEventIdStm = sql.prepare(deleteDatesByEventIdSql); const createBadgeStm = sql.prepare(createBadgeSql); const deleteBadgesByEventIdStm = sql.prepare(deleteBadgesByEventIdSql); +const createMapPoolMapStm = sql.prepare(createMapPoolMapSql); +const deleteMapPoolMapsStm = sql.prepare(deleteMapPoolMapsSql); +const findMapPoolByEventIdStm = sql.prepare(findMapPoolByEventIdSql); export type CreateArgs = Pick< CalendarEvent, @@ -50,9 +58,15 @@ export type CreateArgs = Pick< > & { startTimes: Array; badges: Array; + mapPoolMaps?: Array>; }; export const create = sql.transaction( - ({ startTimes, badges, ...calendarEventArgs }: CreateArgs) => { + ({ + startTimes, + badges, + mapPoolMaps = [], + ...calendarEventArgs + }: CreateArgs) => { const createdEvent = createStm.get(calendarEventArgs) as CalendarEvent; for (const startTime of startTimes) { @@ -69,6 +83,13 @@ export const create = sql.transaction( }); } + for (const mapPoolArgs of mapPoolMaps) { + createMapPoolMapStm.run({ + calendarEventId: createdEvent.id, + ...mapPoolArgs, + }); + } + return createdEvent.id; } ); @@ -78,6 +99,7 @@ export const update = sql.transaction( startTimes, badges, eventId, + mapPoolMaps = [], ...calendarEventArgs }: Omit & { eventId: CalendarEvent["id"] }) => { updateStm.run({ ...calendarEventArgs, eventId }); @@ -97,6 +119,14 @@ export const update = sql.transaction( badgeId, }); } + + deleteMapPoolMapsStm.run({ calendarEventId: eventId }); + for (const mapPoolArgs of mapPoolMaps) { + createMapPoolMapStm.run({ + calendarEventId: eventId, + ...mapPoolArgs, + }); + } } ); @@ -410,6 +440,16 @@ export function findBadgesByEventId(eventId: CalendarEvent["id"]) { >; } +export function findMapPoolByEventId(calendarEventId: CalendarEvent["id"]) { + const rows = findMapPoolByEventIdStm.all({ calendarEventId }) as Array< + Pick + >; + + if (rows.length === 0) return; + + return mapPoolListToMapPoolObject(rows); +} + const eventsToReportStm = sql.prepare(eventsToReportSql); export function eventsToReport(authorId?: CalendarEvent["authorId"]) { if (!authorId) return []; diff --git a/app/db/models/maps/createMapPool.sql b/app/db/models/maps/createMapPool.sql deleted file mode 100644 index c3d26779e..000000000 --- a/app/db/models/maps/createMapPool.sql +++ /dev/null @@ -1,4 +0,0 @@ -insert into - "MapPool" ("code", "ownerId") -values - (@code, @ownerId) returning * diff --git a/app/db/models/maps/createMapPoolMap.sql b/app/db/models/maps/createMapPoolMap.sql deleted file mode 100644 index 595f8a9b3..000000000 --- a/app/db/models/maps/createMapPoolMap.sql +++ /dev/null @@ -1,4 +0,0 @@ -insert into - "MapPoolMap" ("mapPoolId", "stageId", "mode") -values - (@mapPoolId, @stageId, @mode) diff --git a/app/db/models/maps/findMapPoolByCode.sql b/app/db/models/maps/findMapPoolByCode.sql deleted file mode 100644 index 694915493..000000000 --- a/app/db/models/maps/findMapPoolByCode.sql +++ /dev/null @@ -1,22 +0,0 @@ -select - "MapPool"."id", - "MapPool"."code", - "MapPool"."ownerId", - "User"."discordName", - "User"."discordDiscriminator", - json_group_array( - json_object( - 'stageId', - "MapPoolMap"."stageId", - 'mode', - "MapPoolMap"."mode" - ) - ) as "maps" -from - "MapPool" - left join "MapPoolMap" on "MapPoolMap"."mapPoolId" = "MapPool"."id" - left join "User" on "User"."id" = "MapPool"."ownerId" -where - code = lower(@code) -group by - "MapPool"."id" diff --git a/app/db/models/maps/queries.server.ts b/app/db/models/maps/queries.server.ts deleted file mode 100644 index e1201ce75..000000000 --- a/app/db/models/maps/queries.server.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { sql } from "~/db/sql"; -import type { MapPool, MapPoolMap, User } from "~/db/types"; -import createMapPoolSql from "./createMapPool.sql"; -import createMapPoolMapSql from "./createMapPoolMap.sql"; -import findMapPoolByCodeSql from "./findMapPoolByCode.sql"; - -const createMapPoolStm = sql.prepare(createMapPoolSql); -const createMapPoolMapStm = sql.prepare(createMapPoolMapSql); -const findMapPoolByCodeStm = sql.prepare(findMapPoolByCodeSql); - -export const addMapPool = sql.transaction( - ({ - ownerId, - code, - maps, - }: { - ownerId: MapPool["id"]; - code: MapPool["code"]; - maps: Array>; - }) => { - const mapPool = createMapPoolStm.get({ ownerId, code }) as MapPool; - - for (const args of maps) { - createMapPoolMapStm.run({ - mapPoolId: mapPool.id, - ...args, - }); - } - } -); - -export function findMapPoolByCode(code: MapPool["code"]) { - const row = findMapPoolByCodeStm.get({ code }); - if (!row) return; - - return { - id: row.id, - code: row.code, - owner: { - id: row.ownerId, - discordName: row.discordName, - discordDiscriminator: row.discordDiscriminator, - }, - maps: JSON.parse(row.maps), - } as { - id: MapPool["id"]; - code: MapPool["code"]; - owner: { - id: User["id"]; - discordName: User["discordName"]; - discordDiscriminator: User["discordDiscriminator"]; - }; - maps: Array>; - }; -} diff --git a/app/db/types.ts b/app/db/types.ts index c79b6ad72..2c776213b 100644 --- a/app/db/types.ts +++ b/app/db/types.ts @@ -155,14 +155,8 @@ export interface BuildAbility { slotIndex: 0 | 1 | 2 | 3; } -export interface MapPool { - id: number; - code: string; - ownerId: number; -} - export interface MapPoolMap { - mapPoolId: number; + calendarEventId?: number; stageId: StageId; mode: ModeShort; } diff --git a/app/modules/map-list-generator/index.ts b/app/modules/map-list-generator/index.ts index 88b1f2cd9..92f4a34bc 100644 --- a/app/modules/map-list-generator/index.ts +++ b/app/modules/map-list-generator/index.ts @@ -1,3 +1,3 @@ export { generateMapList } from "./map-list"; export { modesOrder } from "./modes"; -export { mapPoolToNonEmptyModes } from "./utils"; +export { mapPoolToNonEmptyModes, mapPoolListToMapPoolObject } from "./utils"; diff --git a/app/modules/map-list-generator/utils.ts b/app/modules/map-list-generator/utils.ts index 0906c6e66..f804b87c0 100644 --- a/app/modules/map-list-generator/utils.ts +++ b/app/modules/map-list-generator/utils.ts @@ -1,3 +1,4 @@ +import type { MapPoolMap } from "~/db/types"; import type { ModeShort } from "../in-game-lists"; import type { MapPool } from "../map-pool-serializer"; @@ -12,3 +13,21 @@ export function mapPoolToNonEmptyModes(mapPool: MapPool) { return result; } + +export function mapPoolListToMapPoolObject( + mapPoolList: Array> +) { + const result: MapPool = { + TW: [], + SZ: [], + TC: [], + RM: [], + CB: [], + }; + + for (const { stageId, mode } of mapPoolList) { + result[mode].push(stageId); + } + + return result; +} diff --git a/app/modules/map-pool-serializer/types.ts b/app/modules/map-pool-serializer/types.ts index f8a7c1189..c3e416dc0 100644 --- a/app/modules/map-pool-serializer/types.ts +++ b/app/modules/map-pool-serializer/types.ts @@ -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; diff --git a/app/routes/calendar/$id/index.tsx b/app/routes/calendar/$id/index.tsx index d7ed795b9..5e17d4188 100644 --- a/app/routes/calendar/$id/index.tsx +++ b/app/routes/calendar/$id/index.tsx @@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next"; import { z } from "zod"; import { Avatar } from "~/components/Avatar"; import { LinkButton } from "~/components/Button"; +import { Image } from "~/components/Image"; import { Main } from "~/components/Main"; import { Section } from "~/components/Section"; import { db } from "~/db"; @@ -23,21 +24,28 @@ import { canEditCalendarEvent, canReportCalendarEventWinners, } from "~/permissions"; -import styles from "~/styles/calendar-event.css"; +import calendarStyles from "~/styles/calendar-event.css"; +import mapsStyles from "~/styles/maps.css"; import { databaseTimestampToDate } from "~/utils/dates"; import { notFoundIfFalsy } from "~/utils/remix"; import { discordFullName, makeTitle, placementString } from "~/utils/strings"; import { calendarEditPage, calendarReportWinnersPage, + mapsPage, + navIconUrl, resolveBaseUrl, userPage, } from "~/utils/urls"; import { actualNumber, id } from "~/utils/zod"; +import { MapPoolSelector } from "../components/MapPoolSelector"; import { Tags } from "../components/Tags"; export const links: LinksFunction = () => { - return [{ rel: "stylesheet", href: styles }]; + return [ + { rel: "stylesheet", href: calendarStyles }, + { rel: "stylesheet", href: mapsStyles }, + ]; }; export const meta: MetaFunction = (args) => { @@ -52,7 +60,7 @@ export const meta: MetaFunction = (args) => { }; export const handle = { - i18n: "calendar", + i18n: ["calendar", "game-misc"], }; export const loader = async ({ params, request }: LoaderArgs) => { @@ -67,6 +75,7 @@ export const loader = async ({ params, request }: LoaderArgs) => { badgePrizes: db.calendarEvents.findBadgesByEventId(parsedParams.id), title: makeTitle([event.name, t("pages.calendar")]), results: db.calendarEvents.findResultsByEventId(parsedParams.id), + mapPool: db.calendarEvents.findMapPoolByEventId(parsedParams.id), }); }; @@ -159,6 +168,7 @@ export default function CalendarEventPage() { + ); @@ -222,6 +232,30 @@ function Results() { ); } +function MapPoolInfo() { + const { t } = useTranslation(["calendar"]); + const data = useLoaderData(); + + if (!data.mapPool) return null; + + return ( +
+
+ + + + {t("calendar:createMapList")} + +
+
+ ); +} + function Description() { const { t } = useTranslation(); const data = useLoaderData(); @@ -233,9 +267,11 @@ function Description() { {discordFullName(data.event)} -
- {data.event.description} -
+ {data.event.description && ( +
+ {data.event.description} +
+ )} ); diff --git a/app/routes/calendar/components/MapPoolSelector.tsx b/app/routes/calendar/components/MapPoolSelector.tsx new file mode 100644 index 000000000..62215babf --- /dev/null +++ b/app/routes/calendar/components/MapPoolSelector.tsx @@ -0,0 +1,96 @@ +import clsx from "clsx"; +import { useTranslation } from "react-i18next"; +import { Image } from "~/components/Image"; +import type { ModeShort, StageId } from "~/modules/in-game-lists"; +import { modes, stageIds } from "~/modules/in-game-lists"; +import { type MapPool } from "~/modules/map-pool-serializer"; +import { modeImageUrl, stageImageUrl } from "~/utils/urls"; + +export function MapPoolSelector({ + mapPool, + handleMapPoolChange, +}: { + mapPool: MapPool; + handleMapPoolChange?: ({ + mode, + stageId, + }: { + mode: ModeShort; + stageId: StageId; + }) => void; +}) { + const { t } = useTranslation(["game-misc", "calendar"]); + + const isPresentational = !handleMapPoolChange; + + const stageRowIsVisible = (stageId: StageId) => { + if (!isPresentational) return true; + + return modes.some((mode) => mapPool[mode.short].includes(stageId)); + }; + + return ( +
+ {stageIds.filter(stageRowIsVisible).map((stageId) => ( +
+ +
+
{t(`game-misc:STAGE_${stageId}`)}
+
+ {modes.map((mode) => { + const selected = (mapPool[mode.short] as StageId[]).includes( + stageId + ); + + if (isPresentational && !selected) return null; + if (isPresentational && selected) { + return ( + {mode.long} + ); + } + + return ( + + ); + })} +
+
+
+ ))} +
+ ); +} diff --git a/app/routes/calendar/new.tsx b/app/routes/calendar/new.tsx index 592ccb7a1..dbc2e7f21 100644 --- a/app/routes/calendar/new.tsx +++ b/app/routes/calendar/new.tsx @@ -1,31 +1,52 @@ -import { Form, useLoaderData } from "@remix-run/react"; -import { Label } from "~/components/Label"; -import { Main } from "~/components/Main"; -import * as React from "react"; -import type { Badge as BadgeType, CalendarEventTag } from "~/db/types"; -import { CALENDAR_EVENT } from "~/constants"; -import { Button } from "~/components/Button"; import type { SerializeFrom } from "@remix-run/node"; import { json, redirect, - type MetaFunction, type ActionFunction, type LinksFunction, type LoaderArgs, + type MetaFunction, } from "@remix-run/node"; -import styles from "~/styles/calendar-new.css"; +import { Form, useLoaderData } from "@remix-run/react"; import clsx from "clsx"; +import * as React from "react"; import { useTranslation } from "react-i18next"; +import { z } from "zod"; +import { Badge } from "~/components/Badge"; +import { Button } from "~/components/Button"; +import { DateInput } from "~/components/DateInput"; +import { FormMessage } from "~/components/FormMessage"; import { TrashIcon } from "~/components/icons/Trash"; import { Input } from "~/components/Input"; -import { FormMessage } from "~/components/FormMessage"; -import { useIsMounted } from "~/hooks/useIsMounted"; -import { Tags } from "./components/Tags"; +import { Label } from "~/components/Label"; +import { Main } from "~/components/Main"; +import { Toggle } from "~/components/Toggle"; +import { CALENDAR_EVENT } from "~/constants"; import { db } from "~/db"; +import type { Badge as BadgeType, CalendarEventTag } from "~/db/types"; +import { useIsMounted } from "~/hooks/useIsMounted"; import { requireUser } from "~/modules/auth"; -import { Badge } from "~/components/Badge"; -import { z } from "zod"; +import { i18next } from "~/modules/i18n"; +import type { ModeShort, StageId } from "~/modules/in-game-lists"; +import { + mapPoolToSerializedString, + serializedStringToMapPool, + type MapPool, +} from "~/modules/map-pool-serializer"; +import { canEditCalendarEvent } from "~/permissions"; +import calendarNewStyles from "~/styles/calendar-new.css"; +import mapsStyles from "~/styles/maps.css"; +import { + databaseTimestampToDate, + dateToDatabaseTimestamp, +} from "~/utils/dates"; +import { + badRequestIfFalsy, + parseRequestFormData, + validate, +} from "~/utils/remix"; +import { makeTitle } from "~/utils/strings"; +import { calendarEventPage } from "~/utils/urls"; import { actualNumber, date, @@ -36,20 +57,8 @@ import { safeJSONParse, toArray, } from "~/utils/zod"; -import { - badRequestIfFalsy, - parseRequestFormData, - validate, -} from "~/utils/remix"; -import { - databaseTimestampToDate, - dateToDatabaseTimestamp, -} from "~/utils/dates"; -import { calendarEventPage } from "~/utils/urls"; -import { makeTitle } from "~/utils/strings"; -import { i18next } from "~/modules/i18n"; -import { canEditCalendarEvent } from "~/permissions"; -import { DateInput } from "~/components/DateInput"; +import { MapPoolSelector } from "./components/MapPoolSelector"; +import { Tags } from "./components/Tags"; const MIN_DATE = new Date(Date.UTC(2015, 4, 28)); @@ -57,7 +66,10 @@ const MAX_DATE = new Date(); MAX_DATE.setFullYear(MAX_DATE.getFullYear() + 1); export const links: LinksFunction = () => { - return [{ rel: "stylesheet", href: styles }]; + return [ + { rel: "stylesheet", href: calendarNewStyles }, + { rel: "stylesheet", href: mapsStyles }, + ]; }; export const meta: MetaFunction = (args) => { @@ -108,6 +120,7 @@ const newCalendarEventActionSchema = z.object({ processMany(safeJSONParse, removeDuplicates), z.array(id).nullable() ), + pool: z.string().optional(), }); export const action: ActionFunction = async ({ request }) => { @@ -134,6 +147,16 @@ export const action: ActionFunction = async ({ request }) => { : data.tags, badges: data.badges ?? [], }; + + const deserializedMaps = (() => { + if (!data.pool) return; + + const mapPool = serializedStringToMapPool(data.pool); + return Object.entries(mapPool).flatMap(([mode, stages]) => + stages.flatMap((stageId) => ({ mode: mode as ModeShort, stageId })) + ); + })(); + if (data.eventToEditId) { const eventToEdit = badRequestIfFalsy( db.calendarEvents.findById(data.eventToEditId) @@ -142,6 +165,7 @@ export const action: ActionFunction = async ({ request }) => { db.calendarEvents.update({ eventId: data.eventToEditId, + mapPoolMaps: deserializedMaps, ...commonArgs, }); @@ -149,6 +173,7 @@ export const action: ActionFunction = async ({ request }) => { } else { const createdEventId = db.calendarEvents.create({ authorId: user.id, + mapPoolMaps: deserializedMaps, ...commonArgs, }); @@ -181,6 +206,7 @@ export const loader = async ({ request }: LoaderArgs) => { // "BADGE" tag is special and can't be edited like other tags tags: eventToEdit.tags.filter((tag) => tag !== "BADGE"), badges: db.calendarEvents.findBadgesByEventId(eventId), + mapPool: db.calendarEvents.findMapPoolByEventId(eventId), } : undefined, title: makeTitle([canEditEvent ? "Edit" : "New", t("pages.calendar")]), @@ -192,7 +218,7 @@ export default function CalendarNewEventPage() { const { eventToEdit } = useLoaderData(); return ( -
+
{eventToEdit && ( + @@ -497,3 +524,64 @@ function BadgesAdder() { ); } + +const DEFAULT_MAP_POOL = { + SZ: [], + TC: [], + CB: [], + RM: [], + TW: [], +}; +function MapPoolSection() { + const { t } = useTranslation(["game-misc", "calendar"]); + + const data = useLoaderData(); + const [mapPool, setMapPool] = React.useState( + data.eventToEdit?.mapPool ?? DEFAULT_MAP_POOL + ); + const [includeMapPool, setIncludeMapPool] = React.useState( + Boolean(data.eventToEdit?.mapPool) + ); + + const handleMapPoolChange = ({ + mode, + stageId, + }: { + mode: ModeShort; + stageId: StageId; + }) => { + const newMapPool = mapPool[mode].includes(stageId) + ? { + ...mapPool, + [mode]: mapPool[mode].filter((id) => id !== stageId), + } + : { + ...mapPool, + [mode]: [...mapPool[mode], stageId], + }; + + setMapPool(newMapPool); + }; + + return ( +
+ {includeMapPool && ( + + )} + +
+ + {includeMapPool && ( + + )} +
+
+ ); +} diff --git a/app/routes/index.tsx b/app/routes/index.tsx index 2269387b7..02d3a7cd9 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -20,6 +20,7 @@ import { BUILDS_PAGE, calendarEventPage, CALENDAR_PAGE, + mapsPage, navIconUrl, plusSuggestionPage, userPage, @@ -85,6 +86,12 @@ export default function Index() { description={t("front:badges.description")} to={BADGES_PAGE} /> +
diff --git a/app/routes/maps.tsx b/app/routes/maps.tsx index bf7d51552..bb9b8c929 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -1,50 +1,47 @@ -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 { useCopyToClipboard } from "react-use"; +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 { db } from "~/db"; 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 { Form, useLoaderData, useSearchParams } from "@remix-run/react"; -import { - mapPoolToSerializedString, - serializedStringToMapPool, -} from "~/modules/map-pool-serializer"; -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"; -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, + ipLabsMaps, + modeImageUrl, + stageImageUrl, +} from "~/utils/urls"; const AMOUNT_OF_MAPS_IN_MAP_LIST = stageIds.length * 2; +export const unstable_shouldReload: ShouldReloadFunction = () => false; + export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: styles }]; }; @@ -53,41 +50,25 @@ export const handle = { i18n: "game-misc", }; -// 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 = ({ request }: LoaderArgs) => { const url = new URL(request.url); - const code = url.searchParams.get("code"); + const calendarEventId = url.searchParams.get("eventId"); - if (!code) return null; + const event = calendarEventId + ? db.calendarEvents.findById(Number(calendarEventId)) + : undefined; - return db.maps.findMapPoolByCode(code) ?? null; + return { + calendarEvent: event + ? { + id: event.eventId, + name: event.name, + } + : undefined, + mapPool: event + ? db.calendarEvents.findMapPoolByEventId(event.eventId) + : null, + }; }; const DEFAULT_MAP_POOL = { @@ -99,23 +80,33 @@ const DEFAULT_MAP_POOL = { }; export default function MapListPage() { - const user = useUser(); + const { t } = useTranslation(["common"]); + const data = useLoaderData(); + const [searchParams] = useSearchParams(); const { mapPool, handleMapPoolChange } = useSearchParamMapPool(); - if ( - process.env.NODE_ENV !== "development" && - user?.discordId !== ADMIN_DISCORD_ID - ) { - return
Coming soon :)
; - } - return (
- + {data.calendarEvent && !searchParams.has("pool") && ( +
+ {t("common:maps.mapPool")}:{" "} + + {data.calendarEvent.name} + +
+ )} + + {t("common:maps.tournamentMaplist")} +
); @@ -126,20 +117,14 @@ function useSearchParamMapPool() { const [searchParams, setSearchParams] = useSearchParams(); const mapPool = (() => { - if (data?.maps) { - return { - TW: data.maps.filter((m) => m.mode === "TW").map((m) => m.stageId), - SZ: data.maps.filter((m) => m.mode === "SZ").map((m) => m.stageId), - TC: data.maps.filter((m) => m.mode === "TC").map((m) => m.stageId), - CB: data.maps.filter((m) => m.mode === "CB").map((m) => m.stageId), - RM: data.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; })(); @@ -181,11 +166,8 @@ function MapPoolSelector({ mapPool: MapPool; handleMapPoolChange: (args: { mode: ModeShort; stageId: StageId }) => void; }) { - const data = useLoaderData(); const { t } = useTranslation(["game-misc"]); - const editMode = !data; - return (
{stageIds.map((stageId) => ( @@ -208,12 +190,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" > (); - - if (data) { - return ( -
- Code: {data.code} - Made by: {discordFullName(data.owner)} -
- ); - } - - 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 ( - - -
- - -
- - - ); -} - // xxx: crashes if only one map in mode -// xxx: presentational mode function MapListCreator({ mapPool }: { mapPool: MapPool }) { - const { t } = useTranslation(["game-misc"]); + const { t } = useTranslation(["game-misc", "common"]); const [mapList, setMapList] = React.useState(); const [szEveryOther, setSzEveryOther] = React.useState(false); + const [, copyToClipboard] = useCopyToClipboard(); const handleCreateMaplist = () => { const [list] = generateMapList( @@ -318,19 +241,41 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) { return (
- +
- + {mapList && ( -
    - {mapList.map(({ mode, stageId }, i) => ( -
  1. - {t(`game-misc:MODE_SHORT_${mode}`)}{" "} - {t(`game-misc:STAGE_${stageId}`)} -
  2. - ))} -
+ <> +
    + {mapList.map(({ mode, stageId }, i) => ( +
  1. + {t(`game-misc:MODE_SHORT_${mode}`)}{" "} + {t(`game-misc:STAGE_${stageId}`)} +
  2. + ))} +
+ + )}
); diff --git a/app/styles/calendar-event.css b/app/styles/calendar-event.css index eb6d0c311..b56e58163 100644 --- a/app/styles/calendar-event.css +++ b/app/styles/calendar-event.css @@ -17,6 +17,24 @@ height: 1.25rem; } +.event__map-pool-section { + display: flex; + max-width: 32rem; + flex-direction: column; + padding: var(--s-2); + margin: 0 auto; + gap: var(--s-4); +} + +.event__create-map-list-link { + display: flex; + width: max-content; + align-items: center; + margin: 0 auto; + font-size: var(--fonts-xs); + gap: var(--s-1-5); +} + .event__author { display: flex; align-items: center; diff --git a/app/styles/calendar-new.css b/app/styles/calendar-new.css index fb4d9ad6b..7a6387569 100644 --- a/app/styles/calendar-new.css +++ b/app/styles/calendar-new.css @@ -1,3 +1,7 @@ +.calendar-new__container { + max-width: 32rem; +} + .calendar-new__select { max-width: 16rem; } diff --git a/app/styles/common.css b/app/styles/common.css index 1f82e217c..2d1cbf3be 100644 --- a/app/styles/common.css +++ b/app/styles/common.css @@ -205,6 +205,10 @@ input[type="checkbox"] { height: 16px; } +[list]::-webkit-calendar-picker-indicator { + display: none !important; +} + label { display: block; font-size: var(--fonts-xs); diff --git a/app/styles/maps.css b/app/styles/maps.css index 065dd7051..648e5b435 100644 --- a/app/styles/maps.css +++ b/app/styles/maps.css @@ -2,11 +2,14 @@ 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__tournament-map-list-link { + font-size: var(--fonts-xxs); + text-align: center; } .maps__stage-row { @@ -18,6 +21,7 @@ .maps__stage-name-row { display: flex; + flex-direction: column; flex-grow: 1; align-items: center; justify-content: space-between; @@ -25,11 +29,17 @@ border-radius: var(--rounded); font-size: var(--fonts-xs); font-weight: var(--semi-bold); - gap: var(--s-0-5); + gap: var(--s-2); padding-block: var(--s-1-5); padding-inline: var(--s-3); } +@media screen and (min-width: 640px) { + .maps__stage-name-row { + flex-direction: row; + } +} + .maps__mode-buttons-container { display: flex; flex-wrap: wrap; @@ -72,11 +82,6 @@ gap: var(--s-4); } -.maps__pool_info { - font-size: var(--fonts-xs); - font-weight: var(--semi-bold); -} - .maps__toggle-container { --label-margin: 0; @@ -89,4 +94,5 @@ .maps__map-list { font-size: var(--fonts-xs); font-weight: var(--semi-bold); + margin-block-start: var(--s-4); } diff --git a/app/utils/urls.ts b/app/utils/urls.ts index e029f31e8..c9ead88eb 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -1,5 +1,5 @@ import slugify from "slugify"; -import type { Badge, GearType, MapPool, User } from "~/db/types"; +import type { Badge, GearType, MapPoolMap, User } from "~/db/types"; import type { ModeShort, weaponCategories } from "~/modules/in-game-lists"; import type { Ability, @@ -23,6 +23,9 @@ export const GITHUB_CONTRIBUTORS_URL = export const BORZOIC_TWITTER = "https://twitter.com/borzoic_"; export const LEAN_TWITTER = "https://twitter.com/LeanYoshi"; export const UBERU_TWITTER = "https://twitter.com/uberu5"; +export const ipLabsMaps = (pool: string) => + `https://maps.iplabs.ink/?3&pool=${pool}`; + export const LOG_IN_URL = "/auth"; export const LOG_OUT_URL = "/auth/logout"; export const ADMIN_PAGE = "/admin"; @@ -64,8 +67,8 @@ export const calendarEditPage = (eventId?: number) => `/calendar/new${eventId ? `?eventId=${eventId}` : ""}`; export const calendarReportWinnersPage = (eventId: number) => `/calendar/${eventId}/report-winners`; -export const mapsPage = (code?: MapPool["code"]) => - `/maps${code ? `?code=${code}` : ""}`; +export const mapsPage = (eventId?: MapPoolMap["calendarEventId"]) => + `/maps${eventId ? `?eventId=${eventId}` : ""}`; export const articlePage = (slug: string) => `/a/${slug}`; export const analyzerPage = (args?: { weaponId: MainWeaponId; diff --git a/migrations/010-fix-maps.js b/migrations/010-fix-maps.js new file mode 100644 index 000000000..c1f284661 --- /dev/null +++ b/migrations/010-fix-maps.js @@ -0,0 +1,64 @@ +module.exports.up = 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(); + + // --- + + db.prepare( + ` + create table "MapPoolMap" ( + "calendarEventId" integer, + "stageId" integer not null, + "mode" text not null, + foreign key ("calendarEventId") references "CalendarEvent"("id") on delete cascade, + unique("calendarEventId", "stageId", "mode") on conflict rollback + ) strict + ` + ).run(); + db.prepare( + `create index map_pool_map_calendar_event_id on "MapPoolMap"("calendarEventId")` + ).run(); +}; + +module.exports.down = function (db) { + db.prepare(`drop table "MapPoolMap"`).run(); + + // --- + + 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(); +}; diff --git a/package-lock.json b/package-lock.json index 43637d4bd..836301043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "react-flip-toolkit": "^7.0.16", "react-i18next": "^11.18.6", "react-popper": "^2.3.0", + "react-use": "^17.4.0", "remix-auth": "^3.3.0", "remix-auth-oauth2": "^1.3.0", "remix-i18next": "^4.1.1", @@ -2923,6 +2924,11 @@ "i18next": "^21.0.1" } }, + "node_modules/@types/js-cookie": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz", + "integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==" + }, "node_modules/@types/json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", @@ -3386,6 +3392,11 @@ "resolved": "https://registry.npmjs.org/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz", "integrity": "sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==" }, + "node_modules/@xobotyi/scrollbar-width": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", + "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==" + }, "node_modules/@yarnpkg/esbuild-plugin-pnp": { "version": "3.0.0-rc.11", "resolved": "https://registry.npmjs.org/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.11.tgz", @@ -5025,6 +5036,14 @@ "node": ">=0.10.0" } }, + "node_modules/copy-to-clipboard": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/core-js-compat": { "version": "3.23.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.1.tgz", @@ -5140,6 +5159,35 @@ "node": ">=12.22" } }, + "node_modules/css-in-js-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", + "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", + "dependencies": { + "hyphenate-style-name": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5155,8 +5203,7 @@ "node_modules/csstype": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "node_modules/cypress": { "version": "10.8.0", @@ -5793,6 +5840,14 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/es-abstract": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.0.tgz", @@ -7638,6 +7693,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fast-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", + "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -7647,6 +7707,11 @@ "node": ">= 4.9.1" } }, + "node_modules/fastest-stable-stringify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", + "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==" + }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -8602,6 +8667,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, "node_modules/i18next": { "version": "21.9.2", "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.2.tgz", @@ -8759,6 +8829,14 @@ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", "dev": true }, + "node_modules/inline-style-prefixer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.1.tgz", + "integrity": "sha512-AsqazZ8KcRzJ9YPN1wMH2aNM7lkWQ8tSPrW5uDk1ziYwiAPWSZnUsC7lfZq+BDqLqz0B4Pho5wscWcJzVvRzDQ==", + "dependencies": { + "css-in-js-utils": "^2.0.0" + } + }, "node_modules/inquirer": { "version": "8.2.4", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", @@ -9344,7 +9422,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9355,6 +9432,11 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "node_modules/js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, "node_modules/js-sdsl": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", @@ -10262,6 +10344,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -11212,6 +11299,25 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/nano-css": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.3.5.tgz", + "integrity": "sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg==", + "dependencies": { + "css-tree": "^1.1.2", + "csstype": "^3.0.6", + "fastest-stable-stringify": "^2.0.2", + "inline-style-prefixer": "^6.0.0", + "rtl-css-js": "^1.14.0", + "sourcemap-codec": "^1.4.8", + "stacktrace-js": "^2.0.2", + "stylis": "^4.0.6" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -12443,6 +12549,40 @@ "react-dom": ">=16.8" } }, + "node_modules/react-universal-interface": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", + "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", + "peerDependencies": { + "react": "*", + "tslib": "*" + } + }, + "node_modules/react-use": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.4.0.tgz", + "integrity": "sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q==", + "dependencies": { + "@types/js-cookie": "^2.2.6", + "@xobotyi/scrollbar-width": "^1.9.5", + "copy-to-clipboard": "^3.3.1", + "fast-deep-equal": "^3.1.3", + "fast-shallow-equal": "^1.0.0", + "js-cookie": "^2.2.1", + "nano-css": "^5.3.1", + "react-universal-interface": "^0.6.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.1.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^3.0.1", + "ts-easing": "^0.2.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -12908,6 +13048,11 @@ "node": ">=0.10.5" } }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -13039,6 +13184,14 @@ "estree-walker": "^0.6.1" } }, + "node_modules/rtl-css-js": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.0.tgz", + "integrity": "sha512-Oc7PnzwIEU4M0K1J4h/7qUUaljXhQ0kCObRsZjxs2HjkpKsnoTMvSmvJ4sqgJZd0zBoEfAyTdnK/jMIYvrjySQ==", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -13133,6 +13286,17 @@ "loose-envify": "^1.1.0" } }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -13222,6 +13386,14 @@ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" }, + "node_modules/set-harmonic-interval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", + "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==", + "engines": { + "node": ">=6.9" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -13645,8 +13817,7 @@ "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "node_modules/space-separated-tokens": { "version": "2.0.1", @@ -13744,6 +13915,46 @@ "node": ">= 8" } }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -14290,6 +14501,11 @@ "node": ">=10" } }, + "node_modules/stylis": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.2.tgz", + "integrity": "sha512-Nn2CCrG2ZaFziDxaZPN43CXqn+j7tcdjPFCkRBkFue8QYXC2HdEwnw5TCBo4yQZ2WxKYeSi0fdoOrtEqgDrXbA==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -14500,6 +14716,14 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "engines": { + "node": ">=10" + } + }, "node_modules/throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -14599,6 +14823,11 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -14659,6 +14888,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-easing": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", + "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==" + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -14737,8 +14971,7 @@ "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/tsm": { "version": "2.2.2", @@ -17783,6 +18016,11 @@ "i18next": "^21.0.1" } }, + "@types/js-cookie": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz", + "integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==" + }, "@types/json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", @@ -18126,6 +18364,11 @@ "resolved": "https://registry.npmjs.org/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz", "integrity": "sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==" }, + "@xobotyi/scrollbar-width": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", + "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==" + }, "@yarnpkg/esbuild-plugin-pnp": { "version": "3.0.0-rc.11", "resolved": "https://registry.npmjs.org/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.11.tgz", @@ -19346,6 +19589,14 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "copy-to-clipboard": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js-compat": { "version": "3.23.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.1.tgz", @@ -19433,6 +19684,31 @@ "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", "dev": true }, + "css-in-js-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", + "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", + "requires": { + "hyphenate-style-name": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -19442,8 +19718,7 @@ "csstype": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "cypress": { "version": "10.8.0", @@ -19931,6 +20206,14 @@ "is-arrayish": "^0.2.1" } }, + "error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "requires": { + "stackframe": "^1.3.4" + } + }, "es-abstract": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.0.tgz", @@ -21221,12 +21504,22 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", + "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" + }, "fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true }, + "fastest-stable-stringify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", + "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==" + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -21961,6 +22254,11 @@ } } }, + "hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, "i18next": { "version": "21.9.2", "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.2.tgz", @@ -22069,6 +22367,14 @@ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", "dev": true }, + "inline-style-prefixer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.1.tgz", + "integrity": "sha512-AsqazZ8KcRzJ9YPN1wMH2aNM7lkWQ8tSPrW5uDk1ziYwiAPWSZnUsC7lfZq+BDqLqz0B4Pho5wscWcJzVvRzDQ==", + "requires": { + "css-in-js-utils": "^2.0.0" + } + }, "inquirer": { "version": "8.2.4", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", @@ -22478,8 +22784,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isstream": { "version": "0.1.2", @@ -22487,6 +22792,11 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, "js-sdsl": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", @@ -23202,6 +23512,11 @@ "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -23807,6 +24122,21 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nano-css": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.3.5.tgz", + "integrity": "sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg==", + "requires": { + "css-tree": "^1.1.2", + "csstype": "^3.0.6", + "fastest-stable-stringify": "^2.0.2", + "inline-style-prefixer": "^6.0.0", + "rtl-css-js": "^1.14.0", + "sourcemap-codec": "^1.4.8", + "stacktrace-js": "^2.0.2", + "stylis": "^4.0.6" + } + }, "nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -24707,6 +25037,33 @@ "react-router": "6.3.0" } }, + "react-universal-interface": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", + "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", + "requires": {} + }, + "react-use": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.4.0.tgz", + "integrity": "sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q==", + "requires": { + "@types/js-cookie": "^2.2.6", + "@xobotyi/scrollbar-width": "^1.9.5", + "copy-to-clipboard": "^3.3.1", + "fast-deep-equal": "^3.1.3", + "fast-shallow-equal": "^1.0.0", + "js-cookie": "^2.2.1", + "nano-css": "^5.3.1", + "react-universal-interface": "^0.6.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.1.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^3.0.1", + "ts-easing": "^0.2.0", + "tslib": "^2.1.0" + } + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -25068,6 +25425,11 @@ "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", "dev": true }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -25172,6 +25534,14 @@ "estree-walker": "^0.6.1" } }, + "rtl-css-js": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.0.tgz", + "integrity": "sha512-Oc7PnzwIEU4M0K1J4h/7qUUaljXhQ0kCObRsZjxs2HjkpKsnoTMvSmvJ4sqgJZd0zBoEfAyTdnK/jMIYvrjySQ==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -25232,6 +25602,11 @@ "loose-envify": "^1.1.0" } }, + "screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==" + }, "section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -25307,6 +25682,11 @@ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" }, + "set-harmonic-interval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", + "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -25627,8 +26007,7 @@ "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "space-separated-tokens": { "version": "2.0.1", @@ -25708,6 +26087,45 @@ "minipass": "^3.1.1" } }, + "stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "requires": { + "stackframe": "^1.3.4" + } + }, + "stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, + "stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "requires": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==" + } + } + }, + "stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "requires": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -26123,6 +26541,11 @@ "postcss-sorting": "^7.0.1" } }, + "stylis": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.2.tgz", + "integrity": "sha512-Nn2CCrG2ZaFziDxaZPN43CXqn+j7tcdjPFCkRBkFue8QYXC2HdEwnw5TCBo4yQZ2WxKYeSi0fdoOrtEqgDrXbA==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -26301,6 +26724,11 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==" + }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -26384,6 +26812,11 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -26428,6 +26861,11 @@ "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", "dev": true }, + "ts-easing": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", + "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==" + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -26479,8 +26917,7 @@ "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "tsm": { "version": "2.2.2", diff --git a/package.json b/package.json index 2fcc18f0b..ae82fc4b1 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "react-flip-toolkit": "^7.0.16", "react-i18next": "^11.18.6", "react-popper": "^2.3.0", + "react-use": "^17.4.0", "remix-auth": "^3.3.0", "remix-auth-oauth2": "^1.3.0", "remix-i18next": "^4.1.1", diff --git a/public/img/layout/maps.avif b/public/img/layout/maps.avif new file mode 100644 index 000000000..28e0f6fb2 Binary files /dev/null and b/public/img/layout/maps.avif differ diff --git a/public/img/layout/maps.png b/public/img/layout/maps.png new file mode 100644 index 000000000..f04def871 Binary files /dev/null and b/public/img/layout/maps.png differ diff --git a/public/locales/en/calendar.json b/public/locales/en/calendar.json index 2c5456634..ea894544c 100644 --- a/public/locales/en/calendar.json +++ b/public/locales/en/calendar.json @@ -8,6 +8,7 @@ "participatedCount": "{{count}} teams participated", "members": "Members", "results": "Results", + "createMapList": "Create map list", "forms.dates": "Dates", "forms.bracketUrl": "Bracket URL", @@ -17,6 +18,7 @@ "forms.tags.info": "\"Badge prizes\" tag is added automatically if applicable", "forms.badges": "Badge prizes", "forms.badges.placeholder": "Choose a badge prize", + "forms.mapPool": "Map pool", "forms.participantCount": "Participant count", "forms.reportResultsHeader": "Reporting results for {{eventName}}", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index d89e4d4e6..72115dee2 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -8,6 +8,7 @@ "pages.faq": "FAQ", "pages.builds": "Builds", "pages.analyzer": "Build Analyzer", + "pages.maps": "Map Lists", "header.profile": "Profile", "header.logout": "Log out", @@ -27,6 +28,12 @@ "actions.remove": "Remove", "actions.delete": "Delete", "actions.loadMore": "Load more", + "actions.copyToClipboard": "Copy to clipboard", + + "maps.createMapList": "Create map list", + "maps.halfSz": "50% SZ", + "maps.mapPool": "Map pool", + "maps.tournamentMaplist": "Create tournament map list (maps.iplabs.ink)", "results": "Results", diff --git a/public/locales/en/front.json b/public/locales/en/front.json index 2eb9e8bf8..9f403cce6 100644 --- a/public/locales/en/front.json +++ b/public/locales/en/front.json @@ -6,6 +6,7 @@ "plus.description": "View Plus Server voting history and more", "badges.description": "List of all the badges you can earn for your profile", "analyzer.description": "Find out what your builds actually do", + "maps.description": "Turn pool of maps into a list to play on", "recentWinners": "Recent winners", "upcomingEvents": "Upcoming events", "articleBy": "by {{author}}" diff --git a/translation-progress.md b/translation-progress.md index d83e062b7..141e9ab33 100644 --- a/translation-progress.md +++ b/translation-progress.md @@ -23,18 +23,32 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**48/49** +**48/55**
Missing - pages.s2 +- pages.maps +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist
@@ -46,13 +60,31 @@ **6/6** -### 🟢 front.json +### 🟡 front.json -**10/10** +**10/11** -### 🟢 game-misc.json +
+Missing -**12/12** +- maps.description + +
+ +### 🟡 game-misc.json + +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json @@ -78,7 +110,85 @@ --- -## /de (🟢 Done) +## /de (🟡 In progress) + +### 🟢 analyzer.json + +**94/94** + +### 🟢 badges.json + +**7/7** + +### 🟢 builds.json + +**11/11** + +### 🟡 calendar.json + +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
+ +### 🟡 common.json + +**49/55** + +
+Missing + +- pages.maps +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist + +
+ +### 🟢 contributions.json + +**6/6** + +### 🟢 faq.json + +**6/6** + +### 🟡 front.json + +**10/11** + +
+Missing + +- maps.description + +
+ +### 🟡 game-misc.json + +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
+ +### 🟢 user.json + +**19/19** --- @@ -105,18 +215,32 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**48/49** +**48/55**
Missing - pages.s2 +- pages.maps +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist
@@ -128,13 +252,31 @@ **6/6** -### 🟢 front.json +### 🟡 front.json -**10/10** +**10/11** -### 🟢 game-misc.json +
+Missing -**12/12** +- maps.description + +
+ +### 🟡 game-misc.json + +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json @@ -183,20 +325,34 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**46/49** +**46/55**
Missing - pages.s2 - pages.analyzer +- pages.maps - actions.loadMore +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist
@@ -218,19 +374,31 @@ ### 🟡 front.json -**8/10** +**8/11**
Missing - buildsGoTo - analyzer.description +- maps.description
-### 🟢 game-misc.json +### 🟡 game-misc.json -**12/12** +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json @@ -272,11 +440,11 @@ ### 🔴 calendar.json -**0/44** +**0/46** ### 🔴 common.json -**0/49** +**0/55** ### 🔴 contributions.json @@ -288,11 +456,22 @@ ### 🔴 front.json -**0/10** +**0/11** -### 🟢 game-misc.json +### 🟡 game-misc.json -**12/12** +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🔴 user.json @@ -402,20 +581,34 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**46/49** +**46/55**
Missing - pages.s2 - pages.analyzer +- pages.maps - actions.loadMore +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist
@@ -436,19 +629,31 @@ ### 🟡 front.json -**8/10** +**8/11**
Missing - buildsGoTo - analyzer.description +- maps.description
-### 🟢 game-misc.json +### 🟡 game-misc.json -**12/12** +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json @@ -488,20 +693,34 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**35/49** +**35/55**
Missing - pages.s2 - pages.analyzer +- pages.maps - actions.loadMore +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist - weapon.category.SHOOTERS - weapon.category.BLASTERS - weapon.category.ROLLERS @@ -533,19 +752,31 @@ ### 🟡 front.json -**8/10** +**8/11**
Missing - buildsGoTo - analyzer.description +- maps.description
-### 🟢 game-misc.json +### 🟡 game-misc.json -**12/12** +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json @@ -615,19 +846,33 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**36/49** +**36/55**
Missing - pages.s2 +- pages.maps - actions.loadMore +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist - weapon.category.SHOOTERS - weapon.category.BLASTERS - weapon.category.ROLLERS @@ -659,19 +904,31 @@ ### 🟡 front.json -**8/10** +**8/11**
Missing - buildsGoTo - analyzer.description +- maps.description
-### 🟢 game-misc.json +### 🟡 game-misc.json -**12/12** +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json @@ -711,20 +968,34 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**35/49** +**35/55**
Missing - pages.s2 - pages.analyzer +- pages.maps - actions.loadMore +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist - weapon.category.SHOOTERS - weapon.category.BLASTERS - weapon.category.ROLLERS @@ -756,19 +1027,31 @@ ### 🟡 front.json -**8/10** +**8/11**
Missing - buildsGoTo - analyzer.description +- maps.description
-### 🟢 game-misc.json +### 🟡 game-misc.json -**12/12** +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json @@ -808,20 +1091,34 @@ **11/11** -### 🟢 calendar.json +### 🟡 calendar.json -**44/44** +**44/46** + +
+Missing + +- createMapList +- forms.mapPool + +
### 🟡 common.json -**35/49** +**35/55**
Missing - pages.s2 - pages.analyzer +- pages.maps - actions.loadMore +- actions.copyToClipboard +- maps.createMapList +- maps.halfSz +- maps.mapPool +- maps.tournamentMaplist - weapon.category.SHOOTERS - weapon.category.BLASTERS - weapon.category.ROLLERS @@ -853,19 +1150,31 @@ ### 🟡 front.json -**8/10** +**8/11**
Missing - buildsGoTo - analyzer.description +- maps.description
-### 🟢 game-misc.json +### 🟡 game-misc.json -**12/12** +**12/17** + +
+Missing + +- MODE_SHORT_TW +- MODE_SHORT_SZ +- MODE_SHORT_TC +- MODE_SHORT_RM +- MODE_SHORT_CB + +
### 🟡 user.json