fix: chuni: errors from null userbox values when items not in database
This commit is contained in:
parent
61892d004e
commit
140520fc5e
@ -58,14 +58,17 @@ export async function getUserData(user: { id: number }) {
|
||||
[`${name}.name as ${name}Name`, `${name}.iconPath as ${name}Icon`,
|
||||
`${name}.texturePath as ${name}Texture`] as const)
|
||||
])
|
||||
.where(({ and, eb, selectFrom }) => and([
|
||||
.where(({ and, eb, selectFrom, or }) => and([
|
||||
eb('p.user', '=', user.id),
|
||||
eb('p.version', '=',
|
||||
selectFrom('chuni_static_music')
|
||||
.select(({ fn }) => fn.max('version').as('latest'))
|
||||
),
|
||||
...avatarNames.map(name => eb(`${name}.version`, '=', selectFrom('chuni_static_music')
|
||||
.select(({ fn }) => fn.max('version').as('latest'))))
|
||||
...avatarNames.map(name => or([
|
||||
eb(`${name}.version`, '=', selectFrom('chuni_static_avatar')
|
||||
.select(({ fn }) => fn.max('version').as('latest'))),
|
||||
eb(`${name}.version`, 'is', null)
|
||||
]))
|
||||
]))
|
||||
.executeTakeFirst();
|
||||
|
||||
|
@ -43,7 +43,7 @@ const ITEM_KEYS: Record<keyof UserboxItems, keyof NonNullable<ChuniUserData>> =
|
||||
const AVATAR_KEYS = ['avatarWear', 'avatarHead', 'avatarFace', 'avatarSkin', 'avatarItem', 'avatarFront', 'avatarBack'] as const;
|
||||
|
||||
type RequiredUserbox = NonNullable<UserboxItems>;
|
||||
type EquippedItem = { [K in keyof RequiredUserbox]: RequiredUserbox[K][number] };
|
||||
type EquippedItem = { [K in keyof RequiredUserbox]: RequiredUserbox[K][number] | null };
|
||||
type SavedItem = { [K in keyof RequiredUserbox]: boolean } & { username: boolean };
|
||||
|
||||
export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
||||
@ -92,7 +92,7 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
||||
|
||||
const update: Partial<ProfileUpdate> = Object.fromEntries((Object.entries(equipped) as Entries<typeof equipped>)
|
||||
.filter(([k]) => items.includes(k as any))
|
||||
.map(([k, v]) => [ITEM_KEYS[k], v.id]));
|
||||
.map(([k, v]) => [ITEM_KEYS[k], v?.id]));
|
||||
|
||||
if ((items as string[]).includes('username'))
|
||||
update.userName = username;
|
||||
@ -129,13 +129,13 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
||||
selectedKeys={selectedLine} onSelectionChange={s => {
|
||||
if (typeof s === 'string' || !s.size) return;
|
||||
setSelectedLine(s as any);
|
||||
play(getAudioUrl(`chuni/system-voice/${selectingVoice?.cuePath ?? equipped.systemVoice.cuePath}_${[...s][0]}`))
|
||||
play(getAudioUrl(`chuni/system-voice/${selectingVoice?.cuePath ?? equipped.systemVoice?.cuePath}_${[...s][0]}`))
|
||||
}}>
|
||||
{CHUNI_VOICE_LINES.map(([line, id]) => <SelectItem key={id}>{line}</SelectItem>)}
|
||||
</Select>
|
||||
<Button isIconOnly color="primary" className="p-1.5 h-full" radius="none" size="lg" isDisabled={!playPreviews}
|
||||
onPress={() => playingVoice ? stop() :
|
||||
play(getAudioUrl(`chuni/system-voice/${selectingVoice?.cuePath ?? equipped.systemVoice.cuePath}_${[...selectedLine][0]}`))}>
|
||||
play(getAudioUrl(`chuni/system-voice/${selectingVoice?.cuePath ?? equipped.systemVoice?.cuePath}_${[...selectedLine][0]}`))}>
|
||||
{playingVoice ? <StopIcon /> : <PlayIcon />}
|
||||
</Button>
|
||||
</div>);
|
||||
@ -171,14 +171,14 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
||||
<ChuniNameplate profile={profile ? {
|
||||
...profile,
|
||||
userName: username,
|
||||
nameplateName: equipped.namePlate.name,
|
||||
nameplateImage: equipped.namePlate.imagePath,
|
||||
trophyName: equipped.trophy.name,
|
||||
trophyRareType: equipped.trophy.rareType
|
||||
nameplateName: equipped.namePlate?.name,
|
||||
nameplateImage: equipped.namePlate?.imagePath,
|
||||
trophyName: equipped.trophy?.name,
|
||||
trophyRareType: equipped.trophy?.rareType
|
||||
} : null} className="w-full" />
|
||||
</div>
|
||||
|
||||
<ChuniNameModal username={profile?.userName!} isOpen={editingUsername}
|
||||
<ChuniNameModal username={profile?.userName ?? ''} isOpen={editingUsername}
|
||||
onClose={() => setEditingUsername(false)}
|
||||
setUsername={u => {
|
||||
setSaved(s => ({ ...s, username: false }));
|
||||
@ -225,12 +225,12 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
||||
<div className="flex flex-col sm:flex-row h-full w-full items-center sm:pl-3 sm:pr-6 sm:pb-2">
|
||||
<div className="w-full max-w-96">
|
||||
<ChuniAvatar className="w-full sm:w-auto sm:h-96"
|
||||
wear={equipped.avatarWear.texturePath}
|
||||
head={equipped.avatarHead.texturePath}
|
||||
face={equipped.avatarFace.texturePath}
|
||||
skin={equipped.avatarSkin.texturePath}
|
||||
item={equipped.avatarItem.texturePath}
|
||||
back={equipped.avatarBack.texturePath}/>
|
||||
wear={equipped.avatarWear?.texturePath}
|
||||
head={equipped.avatarHead?.texturePath}
|
||||
face={equipped.avatarFace?.texturePath}
|
||||
skin={equipped.avatarSkin?.texturePath}
|
||||
item={equipped.avatarItem?.texturePath}
|
||||
back={equipped.avatarBack?.texturePath}/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 w-full px-2 sm:px-0 sm:flex flex-col gap-1.5 sm:ml-3 flex-grow">
|
||||
{(['avatarHead', 'avatarFace', 'avatarWear', 'avatarSkin', 'avatarItem', 'avatarBack'] as const).map(k => ((k !== 'avatarSkin' || userboxItems.avatarSkin.length > 1) && <SelectModalButton
|
||||
@ -267,8 +267,8 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
||||
<div className="flex w-full flex-col sm:flex-row items-center px-2 sm:px-4 sm:pb-4 h-full">
|
||||
<div className="flex flex-col">
|
||||
<Image className="w-80 max-w-full" width={320} height={204} priority
|
||||
alt={equipped.systemVoice.name ?? ''} src={getImageUrl(`chuni/system-voice-icon/${equipped.systemVoice.imagePath}`)} />
|
||||
<span className="text-center">{ equipped.systemVoice.name }</span>
|
||||
alt={equipped.systemVoice?.name ?? ''} src={getImageUrl(`chuni/system-voice-icon/${equipped.systemVoice?.imagePath}`)} />
|
||||
<span className="text-center">{ equipped.systemVoice?.name }</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-grow w-fullmt-3 sm:-mt-5 sm:w-auto gap-2 w-full">
|
||||
@ -324,8 +324,8 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
||||
|
||||
|
||||
<Image className="w-52 max-w-full -mt-2" width={208} height={208} priority
|
||||
alt={equipped.mapIcon.name ?? ''} src={getImageUrl(`chuni/map-icon/${equipped.mapIcon.imagePath}`)} />
|
||||
<span className="text-center mb-2">{ equipped.mapIcon.name }</span>
|
||||
alt={equipped.mapIcon?.name ?? ''} src={getImageUrl(`chuni/map-icon/${equipped.mapIcon?.imagePath}`)} />
|
||||
<span className="text-center mb-2">{ equipped.mapIcon?.name }</span>
|
||||
<div className="px-2 w-full flex justify-center">
|
||||
<SelectModalButton onSelected={i => i && equipItem('mapIcon', i)} selectedItem={equipped.mapIcon}
|
||||
displayMode="grid" modalSize="full" rowSize={210} colSize={175} items={userboxItems.mapIcon} gap={6}
|
||||
|
@ -2,12 +2,12 @@ import { getImageUrl } from '@/helpers/assets';
|
||||
import Image from 'next/image';
|
||||
|
||||
export type ChuniAvatarProps = {
|
||||
wear: string | null,
|
||||
head: string | null,
|
||||
face: string | null,
|
||||
skin: string | null,
|
||||
item: string | null,
|
||||
back: string | null,
|
||||
wear: string | null | undefined,
|
||||
head: string | null | undefined,
|
||||
face: string | null | undefined,
|
||||
skin: string | null | undefined,
|
||||
item: string | null | undefined,
|
||||
back: string | null | undefined,
|
||||
className?: string
|
||||
};
|
||||
|
||||
|
@ -15,7 +15,7 @@ export type Profile = PickNullable<ChuniUserData, (typeof CHUNI_NAMEPLATE_PROFIL
|
||||
|
||||
export type ChuniNameplateProps = {
|
||||
className?: string,
|
||||
profile: Profile,
|
||||
profile: Partial<Profile>,
|
||||
};
|
||||
|
||||
export const ChuniNameplate = ({ className, profile }: ChuniNameplateProps) => {
|
||||
@ -36,7 +36,7 @@ export const ChuniNameplate = ({ className, profile }: ChuniNameplateProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChuniTrophy rarity={profile.trophyRareType} name={profile.trophyName} />
|
||||
<ChuniTrophy rarity={profile?.trophyRareType ?? 0} name={profile?.trophyName ?? ''} />
|
||||
<div className="w-[99%] h-[52%] flex">
|
||||
<div className="w-full m-[0.25%]">
|
||||
<div className="h-full w-full bg-gray-400 px-[2%] @container-size flex flex-col text-black font-semibold text-nowrap overflow-hidden">
|
||||
@ -65,8 +65,8 @@ export const ChuniNameplate = ({ className, profile }: ChuniNameplateProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<Image className="ml-auto aspect-square h-full w-auto bg-gray-200 border-2 border-black" alt="Character" width={135} height={135} src={profile.characterId !== null ? getImageUrl(
|
||||
`chuni/character/CHU_UI_Character_${Math.floor(profile.characterId / 10).toString()
|
||||
.padStart(4, '0')}_${(profile.characterId % 10).toString().padStart(2, '0')}_02`) : ''}/>
|
||||
`chuni/character/CHU_UI_Character_${Math.floor(profile.characterId! / 10).toString()
|
||||
.padStart(4, '0')}_${(profile.characterId! % 10).toString().padStart(2, '0')}_02`) : ''}/>
|
||||
</div>
|
||||
</div>
|
||||
<Image width={576} height={228} priority
|
||||
|
Loading…
Reference in New Issue
Block a user