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}.name as ${name}Name`, `${name}.iconPath as ${name}Icon`,
|
||||||
`${name}.texturePath as ${name}Texture`] as const)
|
`${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.user', '=', user.id),
|
||||||
eb('p.version', '=',
|
eb('p.version', '=',
|
||||||
selectFrom('chuni_static_music')
|
selectFrom('chuni_static_music')
|
||||||
.select(({ fn }) => fn.max('version').as('latest'))
|
.select(({ fn }) => fn.max('version').as('latest'))
|
||||||
),
|
),
|
||||||
...avatarNames.map(name => eb(`${name}.version`, '=', selectFrom('chuni_static_music')
|
...avatarNames.map(name => or([
|
||||||
.select(({ fn }) => fn.max('version').as('latest'))))
|
eb(`${name}.version`, '=', selectFrom('chuni_static_avatar')
|
||||||
|
.select(({ fn }) => fn.max('version').as('latest'))),
|
||||||
|
eb(`${name}.version`, 'is', null)
|
||||||
|
]))
|
||||||
]))
|
]))
|
||||||
.executeTakeFirst();
|
.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;
|
const AVATAR_KEYS = ['avatarWear', 'avatarHead', 'avatarFace', 'avatarSkin', 'avatarItem', 'avatarFront', 'avatarBack'] as const;
|
||||||
|
|
||||||
type RequiredUserbox = NonNullable<UserboxItems>;
|
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 };
|
type SavedItem = { [K in keyof RequiredUserbox]: boolean } & { username: boolean };
|
||||||
|
|
||||||
export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
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>)
|
const update: Partial<ProfileUpdate> = Object.fromEntries((Object.entries(equipped) as Entries<typeof equipped>)
|
||||||
.filter(([k]) => items.includes(k as any))
|
.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'))
|
if ((items as string[]).includes('username'))
|
||||||
update.userName = username;
|
update.userName = username;
|
||||||
@ -129,13 +129,13 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
|||||||
selectedKeys={selectedLine} onSelectionChange={s => {
|
selectedKeys={selectedLine} onSelectionChange={s => {
|
||||||
if (typeof s === 'string' || !s.size) return;
|
if (typeof s === 'string' || !s.size) return;
|
||||||
setSelectedLine(s as any);
|
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>)}
|
{CHUNI_VOICE_LINES.map(([line, id]) => <SelectItem key={id}>{line}</SelectItem>)}
|
||||||
</Select>
|
</Select>
|
||||||
<Button isIconOnly color="primary" className="p-1.5 h-full" radius="none" size="lg" isDisabled={!playPreviews}
|
<Button isIconOnly color="primary" className="p-1.5 h-full" radius="none" size="lg" isDisabled={!playPreviews}
|
||||||
onPress={() => playingVoice ? stop() :
|
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 />}
|
{playingVoice ? <StopIcon /> : <PlayIcon />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>);
|
</div>);
|
||||||
@ -171,14 +171,14 @@ export const ChuniUserbox = ({ profile, userboxItems }: ChuniUserboxProps) => {
|
|||||||
<ChuniNameplate profile={profile ? {
|
<ChuniNameplate profile={profile ? {
|
||||||
...profile,
|
...profile,
|
||||||
userName: username,
|
userName: username,
|
||||||
nameplateName: equipped.namePlate.name,
|
nameplateName: equipped.namePlate?.name,
|
||||||
nameplateImage: equipped.namePlate.imagePath,
|
nameplateImage: equipped.namePlate?.imagePath,
|
||||||
trophyName: equipped.trophy.name,
|
trophyName: equipped.trophy?.name,
|
||||||
trophyRareType: equipped.trophy.rareType
|
trophyRareType: equipped.trophy?.rareType
|
||||||
} : null} className="w-full" />
|
} : null} className="w-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ChuniNameModal username={profile?.userName!} isOpen={editingUsername}
|
<ChuniNameModal username={profile?.userName ?? ''} isOpen={editingUsername}
|
||||||
onClose={() => setEditingUsername(false)}
|
onClose={() => setEditingUsername(false)}
|
||||||
setUsername={u => {
|
setUsername={u => {
|
||||||
setSaved(s => ({ ...s, username: false }));
|
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="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">
|
<div className="w-full max-w-96">
|
||||||
<ChuniAvatar className="w-full sm:w-auto sm:h-96"
|
<ChuniAvatar className="w-full sm:w-auto sm:h-96"
|
||||||
wear={equipped.avatarWear.texturePath}
|
wear={equipped.avatarWear?.texturePath}
|
||||||
head={equipped.avatarHead.texturePath}
|
head={equipped.avatarHead?.texturePath}
|
||||||
face={equipped.avatarFace.texturePath}
|
face={equipped.avatarFace?.texturePath}
|
||||||
skin={equipped.avatarSkin.texturePath}
|
skin={equipped.avatarSkin?.texturePath}
|
||||||
item={equipped.avatarItem.texturePath}
|
item={equipped.avatarItem?.texturePath}
|
||||||
back={equipped.avatarBack.texturePath}/>
|
back={equipped.avatarBack?.texturePath}/>
|
||||||
</div>
|
</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">
|
<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
|
{(['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 w-full flex-col sm:flex-row items-center px-2 sm:px-4 sm:pb-4 h-full">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Image className="w-80 max-w-full" width={320} height={204} priority
|
<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}`)} />
|
alt={equipped.systemVoice?.name ?? ''} src={getImageUrl(`chuni/system-voice-icon/${equipped.systemVoice?.imagePath}`)} />
|
||||||
<span className="text-center">{ equipped.systemVoice.name }</span>
|
<span className="text-center">{ equipped.systemVoice?.name }</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col flex-grow w-fullmt-3 sm:-mt-5 sm:w-auto gap-2 w-full">
|
<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
|
<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}`)} />
|
alt={equipped.mapIcon?.name ?? ''} src={getImageUrl(`chuni/map-icon/${equipped.mapIcon?.imagePath}`)} />
|
||||||
<span className="text-center mb-2">{ equipped.mapIcon.name }</span>
|
<span className="text-center mb-2">{ equipped.mapIcon?.name }</span>
|
||||||
<div className="px-2 w-full flex justify-center">
|
<div className="px-2 w-full flex justify-center">
|
||||||
<SelectModalButton onSelected={i => i && equipItem('mapIcon', i)} selectedItem={equipped.mapIcon}
|
<SelectModalButton onSelected={i => i && equipItem('mapIcon', i)} selectedItem={equipped.mapIcon}
|
||||||
displayMode="grid" modalSize="full" rowSize={210} colSize={175} items={userboxItems.mapIcon} gap={6}
|
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';
|
import Image from 'next/image';
|
||||||
|
|
||||||
export type ChuniAvatarProps = {
|
export type ChuniAvatarProps = {
|
||||||
wear: string | null,
|
wear: string | null | undefined,
|
||||||
head: string | null,
|
head: string | null | undefined,
|
||||||
face: string | null,
|
face: string | null | undefined,
|
||||||
skin: string | null,
|
skin: string | null | undefined,
|
||||||
item: string | null,
|
item: string | null | undefined,
|
||||||
back: string | null,
|
back: string | null | undefined,
|
||||||
className?: string
|
className?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export type Profile = PickNullable<ChuniUserData, (typeof CHUNI_NAMEPLATE_PROFIL
|
|||||||
|
|
||||||
export type ChuniNameplateProps = {
|
export type ChuniNameplateProps = {
|
||||||
className?: string,
|
className?: string,
|
||||||
profile: Profile,
|
profile: Partial<Profile>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChuniNameplate = ({ className, profile }: ChuniNameplateProps) => {
|
export const ChuniNameplate = ({ className, profile }: ChuniNameplateProps) => {
|
||||||
@ -36,7 +36,7 @@ export const ChuniNameplate = ({ className, profile }: ChuniNameplateProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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-[99%] h-[52%] flex">
|
||||||
<div className="w-full m-[0.25%]">
|
<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">
|
<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>
|
||||||
</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(
|
<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()
|
`chuni/character/CHU_UI_Character_${Math.floor(profile.characterId! / 10).toString()
|
||||||
.padStart(4, '0')}_${(profile.characterId % 10).toString().padStart(2, '0')}_02`) : ''}/>
|
.padStart(4, '0')}_${(profile.characterId! % 10).toString().padStart(2, '0')}_02`) : ''}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Image width={576} height={228} priority
|
<Image width={576} height={228} priority
|
||||||
|
Loading…
Reference in New Issue
Block a user