From bdba03a5d8d65d5c146ebab0e34ec931faabe1a2 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 11 Oct 2022 20:14:32 +0300 Subject: [PATCH 1/9] Datalist on maplist page initial --- app/components/Input.tsx | 5 +- app/db/models/maps/codesByUserId.sql | 6 +++ app/db/models/maps/queries.server.ts | 8 ++++ app/routes/maps.tsx | 69 +++++++++++++++++++++------- app/styles/common.css | 4 ++ app/styles/maps.css | 3 ++ migrations/009-maps.js | 1 + 7 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 app/db/models/maps/codesByUserId.sql 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/db/models/maps/codesByUserId.sql b/app/db/models/maps/codesByUserId.sql new file mode 100644 index 000000000..1289d38a2 --- /dev/null +++ b/app/db/models/maps/codesByUserId.sql @@ -0,0 +1,6 @@ +select + "code" +from + "MapPool" +where + "ownerId" = @userId; diff --git a/app/db/models/maps/queries.server.ts b/app/db/models/maps/queries.server.ts index e1201ce75..60efcee6e 100644 --- a/app/db/models/maps/queries.server.ts +++ b/app/db/models/maps/queries.server.ts @@ -3,10 +3,12 @@ import type { MapPool, MapPoolMap, User } from "~/db/types"; import createMapPoolSql from "./createMapPool.sql"; import createMapPoolMapSql from "./createMapPoolMap.sql"; import findMapPoolByCodeSql from "./findMapPoolByCode.sql"; +import codesByUserIdSql from "./codesByUserId.sql"; const createMapPoolStm = sql.prepare(createMapPoolSql); const createMapPoolMapStm = sql.prepare(createMapPoolMapSql); const findMapPoolByCodeStm = sql.prepare(findMapPoolByCodeSql); +const codesByUserIdStm = sql.prepare(codesByUserIdSql); export const addMapPool = sql.transaction( ({ @@ -53,3 +55,9 @@ export function findMapPoolByCode(code: MapPool["code"]) { maps: Array>; }; } + +export function codesByUserId(userId: User["id"]) { + return (codesByUserIdStm.all({ userId }) as Array>).map( + ({ code }) => code + ); +} diff --git a/app/routes/maps.tsx b/app/routes/maps.tsx index bf7d51552..44d480607 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -18,12 +18,13 @@ 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 { requireUser, useUser } from "~/modules/auth"; +import { getUser, requireUser, useUser } from "~/modules/auth"; import { ADMIN_DISCORD_ID, MAPS } from "~/constants"; import { Button } from "~/components/Button"; import { Input } from "~/components/Input"; @@ -45,6 +46,10 @@ import { UploadIcon } from "~/components/icons/Upload"; const AMOUNT_OF_MAPS_IN_MAP_LIST = stageIds.length * 2; +export const unstable_shouldReload: ShouldReloadFunction = ({ url }) => { + return Boolean(url.searchParams.get("code")); +}; + export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: styles }]; }; @@ -53,6 +58,7 @@ 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({ @@ -81,13 +87,15 @@ export const action: ActionFunction = async ({ request }) => { return redirect(mapsPage(data.code)); }; -export const loader = ({ request }: LoaderArgs) => { +export const loader = async ({ request }: LoaderArgs) => { + const user = await getUser(request); const url = new URL(request.url); const code = url.searchParams.get("code"); - if (!code) return null; - - return db.maps.findMapPoolByCode(code) ?? null; + return { + mapPool: code ? db.maps.findMapPoolByCode(code) : null, + ownCodes: user ? db.maps.codesByUserId(user.id) : null, + }; }; const DEFAULT_MAP_POOL = { @@ -126,13 +134,23 @@ function useSearchParamMapPool() { const [searchParams, setSearchParams] = useSearchParams(); const mapPool = (() => { - if (data?.maps) { + if (data?.mapPool) { 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), + 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), }; } @@ -181,10 +199,11 @@ function MapPoolSelector({ mapPool: MapPool; handleMapPoolChange: (args: { mode: ModeShort; stageId: StageId }) => void; }) { + const user = useUser(); const data = useLoaderData(); const { t } = useTranslation(["game-misc"]); - const editMode = !data; + const editMode = !data.mapPool || data.mapPool?.owner.id === user?.id; return (
@@ -240,10 +259,17 @@ function MapPoolSelector({ function MapPoolLoaderSaver({ mapPool }: { mapPool: MapPool }) { const data = useLoaderData(); - if (data) { + if (data.mapPool) { return (
- Code: {data.code} - Made by: {discordFullName(data.owner)} +
+ Code: {data.mapPool.code} - Made by:{" "} + {discordFullName(data.mapPool.owner)} +
+
+ + +
); } @@ -276,11 +302,19 @@ function MapPoolLoaderSaver({ mapPool }: { mapPool: MapPool }) { />
- + /> */} + + {data.ownCodes && ( + + {data.ownCodes.map((code) => ( + + )}
@@ -497,3 +525,111 @@ function BadgesAdder() {
); } + +// xxx: maybe better mobile layout? +const DEFAULT_MAP_POOL = { + SZ: [], + TC: [], + CB: [], + RM: [], + TW: [], +}; +function MapPoolSelector() { + 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 && ( +
+ {stageIds.map((stageId) => ( +
+ +
+
{t(`game-misc:STAGE_${stageId}`)}
+
+ {modes.map((mode) => { + const selected = ( + mapPool[mode.short] as StageId[] + ).includes(stageId); + + return ( + + ); + })} +
+
+
+ ))} +
+ )} +
+
+ ); +} 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/utils/urls.ts b/app/utils/urls.ts index e029f31e8..8e7e651ca 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, @@ -64,8 +64,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..9b320f675 --- /dev/null +++ b/migrations/010-fix-maps.js @@ -0,0 +1,65 @@ +// xxx: add unique constraint to code +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/public/locales/en/calendar.json b/public/locales/en/calendar.json index 2c5456634..ee0fd6c37 100644 --- a/public/locales/en/calendar.json +++ b/public/locales/en/calendar.json @@ -17,6 +17,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}}", From 0f2a5ee72844e2732da59125b28f97f6409f5eac Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 13 Oct 2022 20:36:02 +0300 Subject: [PATCH 3/9] Remove action related code from maps page --- app/modules/map-pool-serializer/types.ts | 1 - app/routes/calendar/$id/index.tsx | 8 +- app/routes/maps.tsx | 222 ++++++----------------- app/styles/maps.css | 16 +- 4 files changed, 60 insertions(+), 187 deletions(-) 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..2d7b14fcb 100644 --- a/app/routes/calendar/$id/index.tsx +++ b/app/routes/calendar/$id/index.tsx @@ -233,9 +233,11 @@ function Description() { {discordFullName(data.event)} -
- {data.event.description} -
+ {data.event.description && ( +
+ {data.event.description} +
+ )} ); diff --git a/app/routes/maps.tsx b/app/routes/maps.tsx index 44d480607..5caa3221c 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -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(); const user = useUser(); + const [searchParams] = useSearchParams(); const { mapPool, handleMapPoolChange } = useSearchParamMapPool(); if ( @@ -119,7 +90,14 @@ export default function MapListPage() { return (
- + {data.calendarEvent && !searchParams.has("pool") && ( +
+ Map pool:{" "} + + {data.calendarEvent.name} + +
+ )} { - 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(); const { t } = useTranslation(["game-misc"]); - const editMode = !data.mapPool || data.mapPool?.owner.id === user?.id; - return (
{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" > (); - - if (data.mapPool) { - return ( -
-
- Code: {data.mapPool.code} - Made by:{" "} - {discordFullName(data.mapPool.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 ( - - -
- - {/* */} - - {data.ownCodes && ( - - {data.ownCodes.map((code) => ( - - )} -
- - - ); -} - -// 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"]); diff --git a/app/styles/maps.css b/app/styles/maps.css index 35b6af8da..dc3d6a089 100644 --- a/app/styles/maps.css +++ b/app/styles/maps.css @@ -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; From 8470bf228e9ec9f23f240c074c6b74c196a86c2b Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 13 Oct 2022 21:39:40 +0300 Subject: [PATCH 4/9] Map pool on tournament page --- app/components/layout/nav-items.json | 1 + app/routes/calendar/$id/index.tsx | 40 +++++- .../calendar/components/MapPoolSelector.tsx | 97 +++++++++++++ app/routes/calendar/new.tsx | 133 ++++++------------ app/styles/calendar-event.css | 18 +++ public/img/layout/maps.avif | Bin 0 -> 4231 bytes public/img/layout/maps.png | Bin 0 -> 18682 bytes public/locales/en/calendar.json | 1 + public/locales/en/common.json | 1 + 9 files changed, 198 insertions(+), 93 deletions(-) create mode 100644 app/routes/calendar/components/MapPoolSelector.tsx create mode 100644 public/img/layout/maps.avif create mode 100644 public/img/layout/maps.png 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/routes/calendar/$id/index.tsx b/app/routes/calendar/$id/index.tsx index 2d7b14fcb..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(); diff --git a/app/routes/calendar/components/MapPoolSelector.tsx b/app/routes/calendar/components/MapPoolSelector.tsx new file mode 100644 index 000000000..b47ae737c --- /dev/null +++ b/app/routes/calendar/components/MapPoolSelector.tsx @@ -0,0 +1,97 @@ +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"; + +// xxx: maybe better mobile layout? +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 396254612..7334520d2 100644 --- a/app/routes/calendar/new.tsx +++ b/app/routes/calendar/new.tsx @@ -1,32 +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 calendarNewStyles from "~/styles/calendar-new.css"; -import mapsStyles from "~/styles/maps.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, @@ -37,29 +57,8 @@ import { safeJSONParse, toArray, } from "~/utils/zod"; -import { - badRequestIfFalsy, - parseRequestFormData, - validate, -} from "~/utils/remix"; -import { - databaseTimestampToDate, - dateToDatabaseTimestamp, -} from "~/utils/dates"; -import { calendarEventPage, modeImageUrl, stageImageUrl } from "~/utils/urls"; -import { makeTitle } from "~/utils/strings"; -import { i18next } from "~/modules/i18n"; -import { canEditCalendarEvent } from "~/permissions"; -import { DateInput } from "~/components/DateInput"; -import type { ModeShort, StageId } from "~/modules/in-game-lists"; -import { modes, stageIds } from "~/modules/in-game-lists"; -import { Image } from "~/components/Image"; -import { - type MapPool, - serializedStringToMapPool, -} from "~/modules/map-pool-serializer"; -import { mapPoolToSerializedString } from "~/modules/map-pool-serializer"; -import { Toggle } from "~/components/Toggle"; +import { MapPoolSelector } from "./components/MapPoolSelector"; +import { Tags } from "./components/Tags"; const MIN_DATE = new Date(Date.UTC(2015, 4, 28)); @@ -235,7 +234,7 @@ export default function CalendarNewEventPage() { - + @@ -534,7 +533,7 @@ const DEFAULT_MAP_POOL = { RM: [], TW: [], }; -function MapPoolSelector() { +function MapPoolSection() { const { t } = useTranslation(["game-misc", "calendar"]); const data = useLoaderData(); @@ -578,56 +577,10 @@ function MapPoolSelector() {
{includeMapPool && ( -
- {stageIds.map((stageId) => ( -
- -
-
{t(`game-misc:STAGE_${stageId}`)}
-
- {modes.map((mode) => { - const selected = ( - mapPool[mode.short] as StageId[] - ).includes(stageId); - - return ( - - ); - })} -
-
-
- ))} -
+ )}
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/public/img/layout/maps.avif b/public/img/layout/maps.avif new file mode 100644 index 0000000000000000000000000000000000000000..28e0f6fb24b2c3cd6501fe75ca6f4bc6faa68e11 GIT binary patch literal 4231 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 literal 0 HcmV?d00001 diff --git a/public/img/layout/maps.png b/public/img/layout/maps.png new file mode 100644 index 0000000000000000000000000000000000000000..f04def871a7932d9199beb85d5e7d8b8f34f14b2 GIT binary patch literal 18682 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 E0?&7^UH||9 literal 0 HcmV?d00001 diff --git a/public/locales/en/calendar.json b/public/locales/en/calendar.json index ee0fd6c37..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", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index d89e4d4e6..33cd2a53d 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", From 6b49d06f575da882e2a0c2e790a618fff321bd9e Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 13 Oct 2022 21:45:36 +0300 Subject: [PATCH 5/9] Maps preparation for prod --- app/routes/index.tsx | 7 +++++++ app/routes/maps.tsx | 8 -------- public/locales/en/front.json | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) 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 5caa3221c..42e1f65d9 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -77,17 +77,9 @@ const DEFAULT_MAP_POOL = { export default function MapListPage() { const data = useLoaderData(); - const user = useUser(); 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") && ( 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}}" From b473e64e12a9d88c096bb6ed04d2449bdff50dce Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 13 Oct 2022 21:56:15 +0300 Subject: [PATCH 6/9] MapPoolSelector a bit better mobile layout --- app/routes/calendar/components/MapPoolSelector.tsx | 1 - app/routes/calendar/new.tsx | 1 - app/routes/maps.tsx | 2 -- app/styles/maps.css | 9 ++++++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/routes/calendar/components/MapPoolSelector.tsx b/app/routes/calendar/components/MapPoolSelector.tsx index b47ae737c..62215babf 100644 --- a/app/routes/calendar/components/MapPoolSelector.tsx +++ b/app/routes/calendar/components/MapPoolSelector.tsx @@ -6,7 +6,6 @@ import { modes, stageIds } from "~/modules/in-game-lists"; import { type MapPool } from "~/modules/map-pool-serializer"; import { modeImageUrl, stageImageUrl } from "~/utils/urls"; -// xxx: maybe better mobile layout? export function MapPoolSelector({ mapPool, handleMapPoolChange, diff --git a/app/routes/calendar/new.tsx b/app/routes/calendar/new.tsx index 7334520d2..dbc2e7f21 100644 --- a/app/routes/calendar/new.tsx +++ b/app/routes/calendar/new.tsx @@ -525,7 +525,6 @@ function BadgesAdder() { ); } -// xxx: maybe better mobile layout? const DEFAULT_MAP_POOL = { SZ: [], TC: [], diff --git a/app/routes/maps.tsx b/app/routes/maps.tsx index 42e1f65d9..266e01ee6 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -11,9 +11,7 @@ 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 { modes, stageIds, diff --git a/app/styles/maps.css b/app/styles/maps.css index dc3d6a089..76847149a 100644 --- a/app/styles/maps.css +++ b/app/styles/maps.css @@ -16,6 +16,7 @@ .maps__stage-name-row { display: flex; + flex-direction: column; flex-grow: 1; align-items: center; justify-content: space-between; @@ -23,11 +24,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; From 31d233025abfece662fe443f3777f268c2dd81be Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 13 Oct 2022 22:26:28 +0300 Subject: [PATCH 7/9] Copy to clipboard button --- app/routes/maps.tsx | 38 +++- app/styles/maps.css | 1 + package-lock.json | 467 ++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 4 files changed, 484 insertions(+), 23 deletions(-) diff --git a/app/routes/maps.tsx b/app/routes/maps.tsx index 266e01ee6..fedcf6135 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -5,6 +5,7 @@ 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"; @@ -206,6 +207,7 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) { const { t } = useTranslation(["game-misc"]); const [mapList, setMapList] = React.useState(); const [szEveryOther, setSzEveryOther] = React.useState(false); + const [, copyToClipboard] = useCopyToClipboard(); const handleCreateMaplist = () => { const [list] = generateMapList( @@ -230,14 +232,34 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) { {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/maps.css b/app/styles/maps.css index 76847149a..e1c3506c0 100644 --- a/app/styles/maps.css +++ b/app/styles/maps.css @@ -89,4 +89,5 @@ .maps__map-list { font-size: var(--fonts-xs); font-weight: var(--semi-bold); + margin-block-start: var(--s-4); } 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", From f81ac674e1f9b585b82fcbeb89680e653e4279a5 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 13 Oct 2022 22:38:46 +0300 Subject: [PATCH 8/9] Link to iplabs --- app/routes/maps.tsx | 15 ++++++++++++++- app/styles/maps.css | 5 +++++ app/utils/urls.ts | 3 +++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/routes/maps.tsx b/app/routes/maps.tsx index fedcf6135..e418b0643 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -31,7 +31,12 @@ import { } 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"; +import { + calendarEventPage, + ipLabsMaps, + modeImageUrl, + stageImageUrl, +} from "~/utils/urls"; const AMOUNT_OF_MAPS_IN_MAP_LIST = stageIds.length * 2; @@ -93,6 +98,14 @@ export default function MapListPage() { mapPool={mapPool} handleMapPoolChange={handleMapPoolChange} /> + + Create tournament map list (maps.iplabs.ink) +
); diff --git a/app/styles/maps.css b/app/styles/maps.css index e1c3506c0..648e5b435 100644 --- a/app/styles/maps.css +++ b/app/styles/maps.css @@ -7,6 +7,11 @@ font-weight: var(--semi-bold); } +.maps__tournament-map-list-link { + font-size: var(--fonts-xxs); + text-align: center; +} + .maps__stage-row { display: flex; width: 100%; diff --git a/app/utils/urls.ts b/app/utils/urls.ts index 8e7e651ca..c9ead88eb 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -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"; From ae3f706f2483c0f3ccc82807056c02e114070f7c Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 13 Oct 2022 23:21:55 +0300 Subject: [PATCH 9/9] Maps page translated --- app/routes/maps.tsx | 15 +- migrations/009-maps.js | 1 - migrations/010-fix-maps.js | 1 - public/locales/en/common.json | 6 + translation-progress.md | 421 +++++++++++++++++++++++++++++----- 5 files changed, 380 insertions(+), 64 deletions(-) diff --git a/app/routes/maps.tsx b/app/routes/maps.tsx index e418b0643..bb9b8c929 100644 --- a/app/routes/maps.tsx +++ b/app/routes/maps.tsx @@ -80,6 +80,7 @@ const DEFAULT_MAP_POOL = { }; export default function MapListPage() { + const { t } = useTranslation(["common"]); const data = useLoaderData(); const [searchParams] = useSearchParams(); const { mapPool, handleMapPoolChange } = useSearchParamMapPool(); @@ -88,7 +89,7 @@ export default function MapListPage() {
{data.calendarEvent && !searchParams.has("pool") && (
- Map pool:{" "} + {t("common:maps.mapPool")}:{" "} {data.calendarEvent.name} @@ -104,7 +105,7 @@ export default function MapListPage() { rel="noreferrer" className="maps__tournament-map-list-link" > - Create tournament map list (maps.iplabs.ink) + {t("common:maps.tournamentMaplist")}
@@ -217,7 +218,7 @@ function MapPoolSelector({ // xxx: crashes if only one map in 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(); @@ -240,10 +241,12 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) { return (
- +
- + {mapList && ( <>
    @@ -270,7 +273,7 @@ function MapListCreator({ mapPool }: { mapPool: MapPool }) { ) } > - Copy to clipboard + {t("common:actions.copyToClipboard")} )} diff --git a/migrations/009-maps.js b/migrations/009-maps.js index cb165d8a9..3d918a5d2 100644 --- a/migrations/009-maps.js +++ b/migrations/009-maps.js @@ -1,4 +1,3 @@ -// xxx: add unique constraint to code module.exports.up = function (db) { db.prepare( ` diff --git a/migrations/010-fix-maps.js b/migrations/010-fix-maps.js index 9b320f675..c1f284661 100644 --- a/migrations/010-fix-maps.js +++ b/migrations/010-fix-maps.js @@ -1,4 +1,3 @@ -// xxx: add unique constraint to code module.exports.up = function (db) { for (const table of ["MapPool", "MapPoolMap"]) { db.prepare(`drop table "${table}"`).run(); diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 33cd2a53d..72115dee2 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -28,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/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