Maps with serializing to URL
@@ -2,9 +2,17 @@ import type { LinksFunction } from "@remix-run/node";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Image } from "~/components/Image";
|
||||
import { Main } from "~/components/Main";
|
||||
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/types";
|
||||
import styles from "~/styles/maps.css";
|
||||
import { modeImageUrl } from "~/utils/urls";
|
||||
import { modeImageUrl, stageImageUrl } from "~/utils/urls";
|
||||
import clsx from "clsx";
|
||||
import { useSearchParams } from "@remix-run/react";
|
||||
import {
|
||||
mapPoolToSerializedString,
|
||||
serializedStringToMapPool,
|
||||
} from "~/modules/map-pool-serializer";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
@@ -14,39 +22,114 @@ export const handle = {
|
||||
i18n: "game-misc",
|
||||
};
|
||||
|
||||
const DEFAULT_MAP_POOL = {
|
||||
SZ: [...stageIds],
|
||||
TC: [...stageIds],
|
||||
CB: [...stageIds],
|
||||
RM: [...stageIds],
|
||||
TW: [],
|
||||
};
|
||||
|
||||
export default function MapListPage() {
|
||||
const { mapPool, handleMapPoolChange } = useSearchParamMapPool();
|
||||
|
||||
return (
|
||||
<Main halfWidth>
|
||||
<MapPoolSelector />
|
||||
<Main className="maps__container">
|
||||
<MapPoolSelector
|
||||
mapPool={mapPool}
|
||||
handleMapPoolChange={handleMapPoolChange}
|
||||
/>
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
||||
function MapPoolSelector() {
|
||||
function useSearchParamMapPool() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const mapPool = searchParams.has("pool")
|
||||
? serializedStringToMapPool(searchParams.get("pool")!)
|
||||
: DEFAULT_MAP_POOL;
|
||||
|
||||
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],
|
||||
};
|
||||
|
||||
setSearchParams(
|
||||
{
|
||||
pool: mapPoolToSerializedString(newMapPool),
|
||||
},
|
||||
{ replace: true, state: { scroll: false } }
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
mapPool,
|
||||
handleMapPoolChange,
|
||||
};
|
||||
}
|
||||
|
||||
function MapPoolSelector({
|
||||
mapPool,
|
||||
handleMapPoolChange,
|
||||
}: {
|
||||
mapPool: MapPool;
|
||||
handleMapPoolChange: (args: { mode: ModeShort; stageId: StageId }) => void;
|
||||
}) {
|
||||
const { t } = useTranslation(["game-misc"]);
|
||||
|
||||
return (
|
||||
<div className="stack md">
|
||||
{stageIds.map((stageId) => (
|
||||
<div key={stageId} className="maps__stage-row">
|
||||
<div>{t(`game-misc:STAGE_${stageId}`)}</div>
|
||||
<div className="stack horizontal sm">
|
||||
{modes
|
||||
.filter((mode) => mode.short !== "TW")
|
||||
.map((mode) => (
|
||||
<button
|
||||
key={mode.short}
|
||||
className="maps__mode-button outline-theme"
|
||||
type="button"
|
||||
>
|
||||
<Image
|
||||
alt={mode.long}
|
||||
path={modeImageUrl(mode.short)}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
<Image
|
||||
className="maps__stage-image"
|
||||
alt=""
|
||||
path={stageImageUrl(stageId)}
|
||||
width={80}
|
||||
height={45}
|
||||
/>
|
||||
<div className="maps__stage-name-row">
|
||||
<div>{t(`game-misc:STAGE_${stageId}`)}</div>
|
||||
<div className="maps__mode-buttons-container">
|
||||
{modes.map((mode) => {
|
||||
const selected = mapPool[mode.short].includes(stageId);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={mode.short}
|
||||
className={clsx("maps__mode-button", "outline-theme", {
|
||||
selected,
|
||||
})}
|
||||
onClick={() =>
|
||||
handleMapPoolChange({ mode: mode.short, stageId })
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<Image
|
||||
className={clsx("maps__mode", {
|
||||
selected,
|
||||
})}
|
||||
alt={mode.long}
|
||||
path={modeImageUrl(mode.short)}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
.maps__container {
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.maps__stage-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: var(--s-3);
|
||||
}
|
||||
|
||||
.maps__stage-name-row {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--bg-lighter);
|
||||
@@ -11,6 +23,13 @@
|
||||
padding-inline: var(--s-3);
|
||||
}
|
||||
|
||||
.maps__mode-buttons-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: var(--s-1-5);
|
||||
}
|
||||
|
||||
.maps__mode-button {
|
||||
padding: 0;
|
||||
padding: var(--s-1-5);
|
||||
@@ -20,3 +39,19 @@
|
||||
color: var(--theme);
|
||||
outline: initial;
|
||||
}
|
||||
|
||||
.maps__mode-button.selected {
|
||||
background-color: var(--theme-very-transparent);
|
||||
}
|
||||
|
||||
.maps__stage-image {
|
||||
border-radius: var(--rounded);
|
||||
}
|
||||
|
||||
.maps__mode {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.maps__mode.selected {
|
||||
filter: grayscale(0%);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
AbilityWithUnknown,
|
||||
MainWeaponId,
|
||||
SpecialWeaponId,
|
||||
StageId,
|
||||
SubWeaponId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import type navItems from "~/components/layout/nav-items.json";
|
||||
@@ -102,6 +103,7 @@ export const specialWeaponImageUrl = (specialWeaponSplId: SpecialWeaponId) =>
|
||||
export const abilityImageUrl = (ability: AbilityWithUnknown) =>
|
||||
`/img/abilities/${ability}`;
|
||||
export const modeImageUrl = (mode: ModeShort) => `/img/modes/${mode}`;
|
||||
export const stageImageUrl = (stageId: StageId) => `/img/stages/${stageId}`;
|
||||
|
||||
export function resolveBaseUrl(url: string) {
|
||||
return new URL(url).host;
|
||||
|
||||
BIN
public/img/stages/0.avif
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/img/stages/0.png
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
public/img/stages/1.avif
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/img/stages/1.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
public/img/stages/10.avif
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/img/stages/10.png
Normal file
|
After Width: | Height: | Size: 339 KiB |
BIN
public/img/stages/11.avif
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/img/stages/11.png
Normal file
|
After Width: | Height: | Size: 304 KiB |
BIN
public/img/stages/2.avif
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
public/img/stages/2.png
Normal file
|
After Width: | Height: | Size: 329 KiB |
BIN
public/img/stages/3.avif
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/img/stages/3.png
Normal file
|
After Width: | Height: | Size: 291 KiB |
BIN
public/img/stages/4.avif
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/img/stages/4.png
Normal file
|
After Width: | Height: | Size: 326 KiB |
BIN
public/img/stages/5.avif
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/img/stages/5.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
public/img/stages/6.avif
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/img/stages/6.png
Normal file
|
After Width: | Height: | Size: 318 KiB |
BIN
public/img/stages/7.avif
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/img/stages/7.png
Normal file
|
After Width: | Height: | Size: 360 KiB |
BIN
public/img/stages/8.avif
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/img/stages/8.png
Normal file
|
After Width: | Height: | Size: 301 KiB |
BIN
public/img/stages/9.avif
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/img/stages/9.png
Normal file
|
After Width: | Height: | Size: 302 KiB |