Tier list maker: allow filtering down stage+modes by mode

This commit is contained in:
Kalle 2025-11-26 16:53:26 +02:00
parent da59b2e79e
commit d1d0e68b4b
3 changed files with 58 additions and 4 deletions

View File

@ -7,8 +7,12 @@ import { arrayMove } from "@dnd-kit/sortable";
import { useSearchParams } from "@remix-run/react";
import JSONCrush from "jsoncrush";
import * as React from "react";
import { useSearchParamState } from "~/hooks/useSearchParamState";
import { modesShort } from "~/modules/in-game-lists/modes";
import { z } from "zod/v4";
import {
useSearchParamState,
useSearchParamStateEncoder,
} from "~/hooks/useSearchParamState";
import { modesShort, rankedModesShort } from "~/modules/in-game-lists/modes";
import { stageIds } from "~/modules/in-game-lists/stage-ids";
import {
mainWeaponIds,
@ -17,6 +21,7 @@ import {
weaponIdToType,
} from "~/modules/in-game-lists/weapon-ids";
import { assertUnreachable } from "~/utils/types";
import { modeShort, safeJSONParse } from "~/utils/zod";
import { DEFAULT_TIERS } from "../tier-list-maker-constants";
import {
type TierListItem,
@ -71,6 +76,17 @@ export function useTierList() {
revive: (value) => value,
});
const [selectedModes, setSelectedModes] = useSearchParamStateEncoder({
name: "modes",
defaultValue: rankedModesShort,
revive: (value) =>
z
.preprocess(safeJSONParse, z.array(modeShort))
.catch(() => rankedModesShort)
.parse(value),
encode: JSON.stringify,
});
const parseItemFromId = (id: string): TierListItem | null => {
const [type, idStr, nth] = String(id).split(":");
if (!type || !idStr) return null;
@ -319,7 +335,9 @@ export function useTierList() {
const combinations: string[] = [];
for (const stageId of stageIds) {
for (const mode of modesShort) {
combinations.push(`${stageId}-${mode}`);
if (selectedModes.includes(mode)) {
combinations.push(`${stageId}-${mode}`);
}
}
}
return combinations;
@ -429,6 +447,8 @@ export function useTierList() {
setShowTierHeaders,
title,
setTitle,
selectedModes,
setSelectedModes,
};
}

View File

@ -25,6 +25,12 @@
gap: var(--s-4);
}
.modeFilters {
column-gap: var(--s-8);
row-gap: var(--s-2);
flex-wrap: wrap;
}
.titleInput {
width: 100%;
padding: 0 var(--s-2) var(--s-2) var(--s-2);

View File

@ -24,6 +24,7 @@ import {
SendouTabPanel,
SendouTabs,
} from "~/components/elements/Tabs";
import { ModeImage } from "~/components/Image";
import { DownloadIcon } from "~/components/icons/Download";
import { PlusIcon } from "~/components/icons/Plus";
import { RefreshIcon } from "~/components/icons/Refresh";
@ -31,6 +32,7 @@ import { Main } from "~/components/Main";
import { Placeholder } from "~/components/Placeholder";
import { useUser } from "~/features/auth/core/user";
import { useIsMounted } from "~/hooks/useIsMounted";
import { modesShort } from "~/modules/in-game-lists/modes";
import { metaTags } from "~/utils/remix";
import type { SendouRouteHandle } from "~/utils/remix.server";
import { navIconUrl, TIER_LIST_MAKER_URL } from "~/utils/urls";
@ -106,6 +108,8 @@ function TierListMakerContent() {
setTitle,
screenshotMode,
setScreenshotMode,
selectedModes,
setSelectedModes,
} = useTierListState();
const sensors = useSensors(
@ -269,7 +273,31 @@ function TierListMakerContent() {
</SendouTabPanel>
<SendouTabPanel id="stage-mode">
<ItemPool />
<div className="stack md">
<ItemPool />
<div className={clsx(styles.filters, styles.modeFilters)}>
{modesShort.map((mode) => {
const isSelected = selectedModes.includes(mode);
return (
<SendouSwitch
key={mode}
isSelected={isSelected}
onChange={(selected) => {
if (selected) {
setSelectedModes([...selectedModes, mode]);
} else {
setSelectedModes(
selectedModes.filter((m) => m !== mode),
);
}
}}
>
<ModeImage mode={mode} size={32} />
</SendouSwitch>
);
})}
</div>
</div>
</SendouTabPanel>
</SendouTabs>