mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Add SendouQ weapon report recently reported quick add
This commit is contained in:
parent
c9752bfc87
commit
ea178d7d6a
|
|
@ -44,6 +44,8 @@ interface WeaponSelectProps<
|
|||
disabledWeaponIds?: Array<MainWeaponId>; // TODO: implement for `AnyWeapon` if needed
|
||||
testId?: string;
|
||||
isRequired?: boolean;
|
||||
/** If set, selection of weapons that user sees when search input is empty allowing for quick select for e.g. previous selections */
|
||||
quickSelectWeaponsIds?: Array<MainWeaponId>;
|
||||
}
|
||||
|
||||
// TODO: fix selected value disappears when filtered out. This is because `items` is filtered in a controlled manner and the selected key might not be included in the filtered items.
|
||||
|
|
@ -60,10 +62,13 @@ export function WeaponSelect<
|
|||
includeSubSpecial,
|
||||
testId = "weapon-select",
|
||||
isRequired,
|
||||
quickSelectWeaponsIds,
|
||||
}: WeaponSelectProps<Clearable, IncludeSubSpecial>) {
|
||||
const { t } = useTranslation(["common"]);
|
||||
const { items, filterValue, setFilterValue } =
|
||||
useFilteredWeaponItems(includeSubSpecial);
|
||||
const { items, filterValue, setFilterValue } = useFilteredWeaponItems({
|
||||
includeSubSpecial,
|
||||
quickSelectWeaponsIds,
|
||||
});
|
||||
|
||||
const keyify = (value?: MainWeaponId | AnyWeapon | null) => {
|
||||
if (typeof value === "number") return `MAIN_${value}`;
|
||||
|
|
@ -111,11 +116,13 @@ export function WeaponSelect<
|
|||
<SendouSelectItemSection
|
||||
heading={name}
|
||||
headingImgPath={
|
||||
name === "subs"
|
||||
? subWeaponImageUrl(SPLAT_BOMB_ID)
|
||||
: name === "specials"
|
||||
? specialWeaponImageUrl(TRIZOOKA_ID)
|
||||
: weaponCategoryUrl(name)
|
||||
key === "quick-select"
|
||||
? undefined
|
||||
: name === "subs"
|
||||
? subWeaponImageUrl(SPLAT_BOMB_ID)
|
||||
: name === "specials"
|
||||
? specialWeaponImageUrl(TRIZOOKA_ID)
|
||||
: weaponCategoryUrl(name)
|
||||
}
|
||||
className={idx === 0 ? "pt-0-5-forced" : undefined}
|
||||
key={key}
|
||||
|
|
@ -169,32 +176,72 @@ export function WeaponSelect<
|
|||
);
|
||||
}
|
||||
|
||||
function useFilteredWeaponItems(includeSubSpecial: boolean | undefined) {
|
||||
function useFilteredWeaponItems({
|
||||
includeSubSpecial,
|
||||
quickSelectWeaponsIds,
|
||||
}: {
|
||||
includeSubSpecial: boolean | undefined;
|
||||
quickSelectWeaponsIds?: Array<MainWeaponId>;
|
||||
}) {
|
||||
const items = useAllWeaponCategories(includeSubSpecial);
|
||||
const [filterValue, setFilterValue] = React.useState("");
|
||||
const { t } = useTranslation(["common"]);
|
||||
|
||||
const filtered = !filterValue
|
||||
? items
|
||||
: items
|
||||
.map((category) => {
|
||||
const filteredItems = category.items.filter((item) =>
|
||||
filterWeapon({
|
||||
weapon: item.weapon,
|
||||
weaponName: item.name,
|
||||
searchTerm: filterValue,
|
||||
const showQuickSelectWeapons =
|
||||
filterValue === "" && quickSelectWeaponsIds?.length;
|
||||
|
||||
const filteredItems = () => {
|
||||
if (showQuickSelectWeapons) {
|
||||
return [
|
||||
{
|
||||
idx: 0,
|
||||
key: "quick-select" as const,
|
||||
name: t("common:forms.weaponSearch.quickSelect"),
|
||||
items: items
|
||||
.flatMap((c) =>
|
||||
c.items
|
||||
.map((item) => (item.weapon.type === "MAIN" ? item : null))
|
||||
.filter((val) => val !== null),
|
||||
)
|
||||
.filter((item) =>
|
||||
quickSelectWeaponsIds.includes(item.weapon.id as MainWeaponId),
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const aIdx = quickSelectWeaponsIds.indexOf(
|
||||
a.weapon.id as MainWeaponId,
|
||||
);
|
||||
const bIdx = quickSelectWeaponsIds.indexOf(
|
||||
b.weapon.id as MainWeaponId,
|
||||
);
|
||||
return aIdx - bIdx;
|
||||
}),
|
||||
);
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
...category,
|
||||
items: filteredItems,
|
||||
};
|
||||
})
|
||||
.filter((category) => category.items.length > 0)
|
||||
.map((category, idx) => ({ ...category, idx }));
|
||||
return !filterValue
|
||||
? items
|
||||
: items
|
||||
.map((category) => {
|
||||
const filteredItems = category.items.filter((item) =>
|
||||
filterWeapon({
|
||||
weapon: item.weapon,
|
||||
weaponName: item.name,
|
||||
searchTerm: filterValue,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...category,
|
||||
items: filteredItems,
|
||||
};
|
||||
})
|
||||
.filter((category) => category.items.length > 0)
|
||||
.map((category, idx) => ({ ...category, idx }));
|
||||
};
|
||||
|
||||
return {
|
||||
items: filtered,
|
||||
items: filteredItems(),
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { Chat, type ChatProps, useChat } from "~/features/chat/components/Chat";
|
|||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import { GroupCard } from "~/features/sendouq/components/GroupCard";
|
||||
import { FULL_GROUP_SIZE } from "~/features/sendouq/q-constants";
|
||||
import { useRecentlyReportedWeapons } from "~/features/sendouq/q-hooks";
|
||||
import { AddPrivateNoteDialog } from "~/features/sendouq-match/components/AddPrivateNoteDialog";
|
||||
import type { ReportedWeaponForMerging } from "~/features/sendouq-match/core/reported-weapons.server";
|
||||
import { resolveRoomPass } from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
|
|
@ -410,6 +411,8 @@ function ReportWeaponsForm() {
|
|||
const [reportingMode, setReportingMode] = React.useState<
|
||||
"ALL" | "MYSELF" | "MY_TEAM"
|
||||
>("MYSELF");
|
||||
const { recentlyReportedWeapons, addRecentlyReportedWeapon } =
|
||||
useRecentlyReportedWeapons();
|
||||
|
||||
const playedMaps = data.match.mapList.filter((m) => m.winnerGroupId);
|
||||
const winners = playedMaps.map((m) =>
|
||||
|
|
@ -569,6 +572,7 @@ function ReportWeaponsForm() {
|
|||
<div className="stack horizontal sm items-center">
|
||||
<WeaponSelect
|
||||
value={weaponSplId ?? undefined}
|
||||
quickSelectWeaponsIds={recentlyReportedWeapons}
|
||||
onChange={(weaponSplId) => {
|
||||
setWeaponsUsage((val) => {
|
||||
const result = val.filter(
|
||||
|
|
@ -585,6 +589,8 @@ function ReportWeaponsForm() {
|
|||
userId: member.id,
|
||||
});
|
||||
|
||||
addRecentlyReportedWeapon(weaponSplId);
|
||||
|
||||
return result;
|
||||
});
|
||||
}}
|
||||
|
|
@ -929,6 +935,8 @@ function MapList({
|
|||
const [ownWeaponsUsage, setOwnWeaponsUsage] = React.useState<
|
||||
ReportedWeaponForMerging[]
|
||||
>([]);
|
||||
const { recentlyReportedWeapons, addRecentlyReportedWeapon } =
|
||||
useRecentlyReportedWeapons();
|
||||
|
||||
const previouslyReportedWinners = isResubmission
|
||||
? data.match.mapList
|
||||
|
|
@ -974,6 +982,8 @@ function MapList({
|
|||
setWinners={setWinners}
|
||||
weapons={data.reportedWeapons?.[i]}
|
||||
showReportedOwnWeapon={!ownWeaponReported}
|
||||
recentlyReportedWeapons={recentlyReportedWeapons}
|
||||
addRecentlyReportedWeapon={addRecentlyReportedWeapon}
|
||||
onOwnWeaponSelected={(newReportedWeapon) => {
|
||||
if (!newReportedWeapon) return;
|
||||
|
||||
|
|
@ -1031,6 +1041,8 @@ function MapListMap({
|
|||
weapons,
|
||||
onOwnWeaponSelected,
|
||||
showReportedOwnWeapon,
|
||||
recentlyReportedWeapons,
|
||||
addRecentlyReportedWeapon,
|
||||
}: {
|
||||
i: number;
|
||||
map: Unpacked<SerializeFrom<typeof loader>["match"]["mapList"]>;
|
||||
|
|
@ -1040,6 +1052,8 @@ function MapListMap({
|
|||
weapons?: (MainWeaponId | null)[] | null;
|
||||
onOwnWeaponSelected?: (weapon: ReportedWeaponForMerging | null) => void;
|
||||
showReportedOwnWeapon: boolean;
|
||||
recentlyReportedWeapons?: MainWeaponId[];
|
||||
addRecentlyReportedWeapon?: (weapon: MainWeaponId) => void;
|
||||
}) {
|
||||
const user = useUser();
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
|
@ -1243,10 +1257,15 @@ function MapListMap({
|
|||
</label>
|
||||
<WeaponSelect
|
||||
clearable
|
||||
quickSelectWeaponsIds={recentlyReportedWeapons}
|
||||
onChange={(weaponSplId) => {
|
||||
const userId = user!.id;
|
||||
const groupMatchMapId = map.id;
|
||||
|
||||
if (typeof weaponSplId === "number") {
|
||||
addRecentlyReportedWeapon?.(weaponSplId);
|
||||
}
|
||||
|
||||
onOwnWeaponSelected(
|
||||
typeof weaponSplId === "number"
|
||||
? {
|
||||
|
|
|
|||
54
app/features/sendouq/q-hooks.ts
Normal file
54
app/features/sendouq/q-hooks.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import * as React from "react";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists/types";
|
||||
|
||||
const LOCAL_STORAGE_KEY = "sq__recently-reported-weapons";
|
||||
const MAX_REPORTED_WEAPONS = 7;
|
||||
|
||||
/**
|
||||
* This hook provides access to the list of recently reported weapons,
|
||||
* which is persisted in local storage, and a function to add a new weapon
|
||||
* to the list. The list is automatically loaded from local storage when
|
||||
* the hook is first used.
|
||||
*
|
||||
* If a weapon is added that already exists in the list, it will be moved to the front of the list.
|
||||
* If the list exceeds the maximum number of reported weapons, the oldest weapon will be removed.
|
||||
*/
|
||||
export function useRecentlyReportedWeapons() {
|
||||
const [recentlyReportedWeapons, setReportedWeapons] = React.useState<
|
||||
MainWeaponId[]
|
||||
>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setReportedWeapons(getReportedWeaponsFromLocalStorage());
|
||||
}, []);
|
||||
|
||||
const addRecentlyReportedWeapon = (weapon: MainWeaponId) => {
|
||||
const newList = addReportedWeaponToLocalStorage(weapon);
|
||||
setReportedWeapons(newList);
|
||||
};
|
||||
|
||||
return { recentlyReportedWeapons, addRecentlyReportedWeapon };
|
||||
}
|
||||
|
||||
const getReportedWeaponsFromLocalStorage = (): MainWeaponId[] => {
|
||||
const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||
if (!stored) return [];
|
||||
return JSON.parse(stored);
|
||||
};
|
||||
|
||||
/** Adds weapon to list of recently reported weapons to local storage returning the current list */
|
||||
const addReportedWeaponToLocalStorage = (weapon: MainWeaponId) => {
|
||||
const stored = getReportedWeaponsFromLocalStorage();
|
||||
|
||||
const otherWeapons = stored.filter((storedWeapon) => storedWeapon !== weapon);
|
||||
|
||||
if (otherWeapons.length >= MAX_REPORTED_WEAPONS) {
|
||||
otherWeapons.pop();
|
||||
}
|
||||
|
||||
const newList = [weapon, ...otherWeapons];
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newList));
|
||||
|
||||
return newList;
|
||||
};
|
||||
|
|
@ -157,6 +157,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Særregler",
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Spezielle Regeln",
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
"forms.userSearch.noResults": "No users matching your search found",
|
||||
"forms.weaponSearch.placeholder": "Select a weapon",
|
||||
"forms.weaponSearch.search.placeholder": "Search weapons...",
|
||||
"forms.weaponSearch.quickSelect": "Recent",
|
||||
"forms.gearSearch.placeholder": "Select a gear",
|
||||
"forms.gearSearch.search.placeholder": "Search gear...",
|
||||
"tag.name.SPECIAL": "Special rules",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Reglas especiales",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Reglas especiales",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Règles spéciales",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "Aucun utilisateur trouvé",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Règles spéciales",
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "חוקים מיוחדים",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Regole speciali",
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "特別ルール",
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "특별 규칙",
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Speciale spelregels",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Zasady specjalne",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Regras especiais",
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "Особые правила",
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@
|
|||
"forms.userSearch.noResults": "",
|
||||
"forms.weaponSearch.placeholder": "",
|
||||
"forms.weaponSearch.search.placeholder": "",
|
||||
"forms.weaponSearch.quickSelect": "",
|
||||
"forms.gearSearch.placeholder": "",
|
||||
"forms.gearSearch.search.placeholder": "",
|
||||
"tag.name.SPECIAL": "特殊规则",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user