Maps with serializing to URL

This commit is contained in:
Kalle
2022-10-05 21:05:59 +03:00
parent bba380840c
commit 34e134acaf
27 changed files with 142 additions and 22 deletions

View File

@@ -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>
))}

View File

@@ -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%);
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/img/stages/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

BIN
public/img/stages/1.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/img/stages/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

BIN
public/img/stages/10.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
public/img/stages/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

BIN
public/img/stages/11.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/img/stages/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

BIN
public/img/stages/2.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/img/stages/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

BIN
public/img/stages/3.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/img/stages/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

BIN
public/img/stages/4.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/img/stages/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

BIN
public/img/stages/5.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/img/stages/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

BIN
public/img/stages/6.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
public/img/stages/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
public/img/stages/7.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
public/img/stages/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

BIN
public/img/stages/8.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
public/img/stages/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

BIN
public/img/stages/9.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/img/stages/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB