mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
SendouQ favorite weapons (#2286)
* Migrate code to new weapon list type * Add database migration script * Run formatter * Display favorite background image * Null latest weapon on empty array
This commit is contained in:
parent
5d3fa14edf
commit
1aa3425120
|
|
@ -778,6 +778,11 @@ export interface UserMapModePreferences {
|
|||
}>;
|
||||
}
|
||||
|
||||
export interface QWeaponPool {
|
||||
weaponSplId: MainWeaponId;
|
||||
isFavorite: number;
|
||||
}
|
||||
|
||||
export const BUILD_SORT_IDENTIFIERS = [
|
||||
"UPDATED_AT",
|
||||
"TOP_500",
|
||||
|
|
@ -836,7 +841,7 @@ export interface User {
|
|||
vc: Generated<"YES" | "NO" | "LISTEN_ONLY">;
|
||||
youtubeId: string | null;
|
||||
mapModePreferences: JSONColumnTypeNullable<UserMapModePreferences>;
|
||||
qWeaponPool: ColumnType<MainWeaponId[] | null, string | null, string | null>;
|
||||
qWeaponPool: JSONColumnTypeNullable<QWeaponPool[]>;
|
||||
plusSkippedForSeasonNth: number | null;
|
||||
noScreen: Generated<number>;
|
||||
buildSorting: JSONColumnTypeNullable<BuildSort[]>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
|
||||
import { db } from "~/db/sql";
|
||||
import type { ParsedMemento, Tables, UserSkillDifference } from "~/db/tables";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import type {
|
||||
ParsedMemento,
|
||||
QWeaponPool,
|
||||
Tables,
|
||||
UserSkillDifference,
|
||||
} from "~/db/tables";
|
||||
import { COMMON_USER_FIELDS, userChatNameColor } from "~/utils/kysely.server";
|
||||
|
||||
export function findById(id: number) {
|
||||
|
|
@ -57,7 +61,7 @@ export interface GroupForMatch {
|
|||
role: Tables["GroupMember"]["role"];
|
||||
customUrl: Tables["User"]["customUrl"];
|
||||
inGameName: Tables["User"]["inGameName"];
|
||||
weapons: Array<MainWeaponId>;
|
||||
weapons: Array<QWeaponPool>;
|
||||
chatNameColor: string | null;
|
||||
vc: Tables["User"]["vc"];
|
||||
languages: string[];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { db } from "~/db/sql";
|
||||
import type { Tables, UserMapModePreferences } from "~/db/tables";
|
||||
import { type MainWeaponId, modesShort } from "~/modules/in-game-lists";
|
||||
import type { QWeaponPool, Tables, UserMapModePreferences } from "~/db/tables";
|
||||
import { modesShort } from "~/modules/in-game-lists";
|
||||
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
|
||||
|
||||
export async function settingsByUserId(userId: number) {
|
||||
|
|
@ -74,7 +74,7 @@ export function updateVoiceChat(args: {
|
|||
|
||||
export function updateSendouQWeaponPool(args: {
|
||||
userId: number;
|
||||
weaponPool: MainWeaponId[];
|
||||
weaponPool: QWeaponPool[];
|
||||
}) {
|
||||
return db
|
||||
.updateTable("User")
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import {
|
|||
id,
|
||||
modeShort,
|
||||
noDuplicates,
|
||||
qWeapon,
|
||||
safeJSONParse,
|
||||
stageId,
|
||||
weaponSplId,
|
||||
} from "~/utils/zod";
|
||||
import {
|
||||
AMOUNT_OF_MAPS_IN_POOL_PER_MODE,
|
||||
|
|
@ -67,7 +67,7 @@ export const settingsActionSchema = z.union([
|
|||
_action: _action("UPDATE_SENDOUQ_WEAPON_POOL"),
|
||||
weaponPool: z.preprocess(
|
||||
safeJSONParse,
|
||||
z.array(weaponSplId).max(SENDOUQ_WEAPON_POOL_MAX_SIZE),
|
||||
z.array(qWeapon).max(SENDOUQ_WEAPON_POOL_MAX_SIZE),
|
||||
),
|
||||
}),
|
||||
z.object({
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { MapIcon } from "~/components/icons/Map";
|
|||
import { MicrophoneFilledIcon } from "~/components/icons/MicrophoneFilled";
|
||||
import { PuzzleIcon } from "~/components/icons/Puzzle";
|
||||
import { SpeakerFilledIcon } from "~/components/icons/SpeakerFilled";
|
||||
import { StarIcon } from "~/components/icons/Star";
|
||||
import { StarFilledIcon } from "~/components/icons/StarFilled";
|
||||
import { TrashIcon } from "~/components/icons/Trash";
|
||||
import { UsersIcon } from "~/components/icons/Users";
|
||||
import type { Preference, Tables, UserMapModePreferences } from "~/db/tables";
|
||||
|
|
@ -383,7 +385,7 @@ function WeaponPool() {
|
|||
const [weapons, setWeapons] = React.useState(data.settings.qWeaponPool ?? []);
|
||||
const fetcher = useFetcher();
|
||||
|
||||
const latestWeapon = weapons[weapons.length - 1];
|
||||
const latestWeapon = weapons[weapons.length - 1]?.weaponSplId ?? null;
|
||||
|
||||
return (
|
||||
<details>
|
||||
|
|
@ -408,12 +410,15 @@ function WeaponPool() {
|
|||
if (!weapon) return;
|
||||
setWeapons([
|
||||
...weapons,
|
||||
Number(weapon.value) as MainWeaponId,
|
||||
{
|
||||
weaponSplId: Number(weapon.value) as MainWeaponId,
|
||||
isFavorite: 0,
|
||||
},
|
||||
]);
|
||||
}}
|
||||
// empty on selection
|
||||
key={latestWeapon ?? "empty"}
|
||||
weaponIdsToOmit={new Set(weapons)}
|
||||
weaponIdsToOmit={new Set(weapons.map((w) => w.weaponSplId))}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -426,23 +431,45 @@ function WeaponPool() {
|
|||
<div className="stack horizontal sm justify-center">
|
||||
{weapons.map((weapon) => {
|
||||
return (
|
||||
<div key={weapon} className="stack xs">
|
||||
<div key={weapon.weaponSplId} className="stack xs">
|
||||
<div>
|
||||
<WeaponImage
|
||||
weaponSplId={weapon}
|
||||
variant="badge"
|
||||
weaponSplId={weapon.weaponSplId}
|
||||
variant={weapon.isFavorite ? "badge-5-star" : "badge"}
|
||||
width={38}
|
||||
height={38}
|
||||
/>
|
||||
</div>
|
||||
<div className="stack sm horizontal items-center justify-center">
|
||||
<Button
|
||||
icon={weapon.isFavorite ? <StarFilledIcon /> : <StarIcon />}
|
||||
variant="minimal"
|
||||
aria-label="Favorite weapon"
|
||||
onClick={() =>
|
||||
setWeapons(
|
||||
weapons.map((w) =>
|
||||
w.weaponSplId === weapon.weaponSplId
|
||||
? {
|
||||
...weapon,
|
||||
isFavorite: weapon.isFavorite === 1 ? 0 : 1,
|
||||
}
|
||||
: w,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon={<TrashIcon />}
|
||||
variant="minimal-destructive"
|
||||
aria-label="Delete weapon"
|
||||
onClick={() =>
|
||||
setWeapons(weapons.filter((w) => w !== weapon))
|
||||
setWeapons(
|
||||
weapons.filter(
|
||||
(w) => w.weaponSplId !== weapon.weaponSplId,
|
||||
),
|
||||
)
|
||||
}
|
||||
testId={`delete-weapon-${weapon.weaponSplId}`}
|
||||
size="tiny"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -374,9 +374,9 @@ function GroupMember({
|
|||
{member.weapons?.map((weapon) => {
|
||||
return (
|
||||
<WeaponImage
|
||||
key={weapon}
|
||||
weaponSplId={weapon}
|
||||
variant="badge"
|
||||
key={weapon.weaponSplId}
|
||||
weaponSplId={weapon.weaponSplId}
|
||||
variant={weapon.isFavorite ? "badge-5-star" : "badge"}
|
||||
size={26}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ParsedMemento, Tables } from "~/db/tables";
|
||||
import type { MainWeaponId, ModeShort } from "~/modules/in-game-lists";
|
||||
import type { ParsedMemento, QWeaponPool, Tables } from "~/db/tables";
|
||||
import type { ModeShort } from "~/modules/in-game-lists";
|
||||
import type { TieredSkill } from "../mmr/tiered.server";
|
||||
import type { GroupForMatch } from "../sendouq-match/QMatchRepository.server";
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ export type LookingGroup = {
|
|||
plusTier?: Tables["PlusTier"]["tier"];
|
||||
role: Tables["GroupMember"]["role"];
|
||||
note?: Tables["GroupMember"]["note"];
|
||||
weapons?: MainWeaponId[];
|
||||
weapons?: QWeaponPool[];
|
||||
skill?: TieredSkill | "CALCULATING";
|
||||
vc?: Tables["User"]["vc"];
|
||||
inGameName?: Tables["User"]["inGameName"];
|
||||
|
|
|
|||
|
|
@ -100,6 +100,11 @@ export const weaponSplId = z.preprocess(
|
|||
numericEnum(mainWeaponIds),
|
||||
);
|
||||
|
||||
export const qWeapon = z.object({
|
||||
weaponSplId,
|
||||
isFavorite: z.union([z.literal(0), z.literal(1)]),
|
||||
});
|
||||
|
||||
export const modeShort = z.enum(["TW", "SZ", "TC", "RM", "CB"]);
|
||||
|
||||
export const stageId = z.preprocess(actualNumber, numericEnum(stageIds));
|
||||
|
|
@ -151,7 +156,10 @@ export const safeStringSchema = ({ min, max }: { min?: number; max: number }) =>
|
|||
export const safeNullableStringSchema = ({
|
||||
min,
|
||||
max,
|
||||
}: { min?: number; max: number }) =>
|
||||
}: {
|
||||
min?: number;
|
||||
max: number;
|
||||
}) =>
|
||||
z.preprocess(
|
||||
actuallyNonEmptyStringOrNull,
|
||||
z
|
||||
|
|
|
|||
23
migrations/086-add-weapon-pool-favorites.js
Normal file
23
migrations/086-add-weapon-pool-favorites.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export function up(db) {
|
||||
db.transaction(() => {
|
||||
const users = db
|
||||
.prepare(/* sql */ "SELECT id, qWeaponPool FROM User")
|
||||
.all();
|
||||
|
||||
const updateStatement = db.prepare(
|
||||
/* sql */ "UPDATE User SET qWeaponPool = ? WHERE id = ?",
|
||||
);
|
||||
|
||||
for (const user of users) {
|
||||
if (!user.qWeaponPool) continue;
|
||||
|
||||
const pool = JSON.parse(user.qWeaponPool);
|
||||
const newPool = pool.map((id) => ({
|
||||
weaponSplId: id,
|
||||
isFavorite: 0,
|
||||
}));
|
||||
|
||||
updateStatement.run(JSON.stringify(newPool), user.id);
|
||||
}
|
||||
})();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user