From a5a7215e09937217a2a70fcf3c52cc002fe7a4ef Mon Sep 17 00:00:00 2001 From: Life Date: Mon, 30 Mar 2026 17:55:11 +0200 Subject: [PATCH 1/8] Change pendingActionRef to hold action and itemId Refactor pendingActionRef to store action and itemId as an object instead of a string. --- src/hooks/furni-editor/useFurniEditor.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/hooks/furni-editor/useFurniEditor.ts b/src/hooks/furni-editor/useFurniEditor.ts index 936eb4f..cd95ec4 100644 --- a/src/hooks/furni-editor/useFurniEditor.ts +++ b/src/hooks/furni-editor/useFurniEditor.ts @@ -60,7 +60,7 @@ export const useFurniEditor = () => const [ catalogItems, setCatalogItems ] = useState([]); const [ interactions, setInteractions ] = useState([]); const [ furniDataEntry, setFurniDataEntry ] = useState | null>(null); - const pendingActionRef = useRef(null); + const pendingActionRef = useRef<{ action: string; itemId: number } | null>(null); const { simpleAlert = null } = useNotification(); const clearError = useCallback(() => setError(null), []); @@ -161,7 +161,9 @@ export const useFurniEditor = () => useMessageEvent(FurniEditorResultEvent, (event: FurniEditorResultEvent) => { const parser = event.getParser(); - const action = pendingActionRef.current; + const pending = pendingActionRef.current; + const action = pending?.action ?? null; + const actionItemId = pending?.itemId ?? null; pendingActionRef.current = null; setLoading(false); @@ -182,10 +184,10 @@ export const useFurniEditor = () => if(action === 'update') { - // Auto-reload detail after update - if(selectedItem) + // Auto-reload detail after update using the ID from the original request + if(actionItemId) { - SendMessageComposer(new FurniEditorDetailComposer(selectedItem.id)); + SendMessageComposer(new FurniEditorDetailComposer(actionItemId)); } if(simpleAlert) @@ -231,7 +233,7 @@ export const useFurniEditor = () => { setLoading(true); setError(null); - pendingActionRef.current = 'update'; + pendingActionRef.current = { action: 'update', itemId: id }; SendMessageComposer(new FurniEditorUpdateComposer(id, JSON.stringify(fields))); }, []); @@ -239,7 +241,7 @@ export const useFurniEditor = () => { setLoading(true); setError(null); - pendingActionRef.current = 'delete'; + pendingActionRef.current = { action: 'delete', itemId: id }; SendMessageComposer(new FurniEditorDeleteComposer(id)); }, []); From 6609c0325f7f4988096aa94152273db8edff280b Mon Sep 17 00:00:00 2001 From: duckietm Date: Tue, 31 Mar 2026 11:41:21 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=86=99=20Fix=20Youtube=20TV's?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/furniture/useFurnitureYoutubeWidget.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts index 7f91c1e..0069063 100644 --- a/src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts +++ b/src/hooks/rooms/widgets/furniture/useFurnitureYoutubeWidget.ts @@ -1,5 +1,5 @@ import { ControlYoutubeDisplayPlaybackMessageComposer, GetRoomEngine, GetSessionDataManager, GetYoutubeDisplayStatusMessageComposer, RoomEngineTriggerWidgetEvent, RoomId, SecurityLevel, SetYoutubeDisplayPlaylistMessageComposer, YoutubeControlVideoMessageEvent, YoutubeDisplayPlaylist, YoutubeDisplayPlaylistsEvent, YoutubeDisplayVideoMessageEvent } from '@nitrots/nitro-renderer'; -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { IsOwnerOfFurniture, SendMessageComposer, YoutubeVideoPlaybackStateEnum } from '../../../../api'; import { useMessageEvent, useNitroEvent } from '../../../events'; import { useFurniRemovedEvent } from '../../engine'; @@ -13,6 +13,7 @@ const useFurnitureYoutubeWidgetState = () => { const [ objectId, setObjectId ] = useState(-1); const [ category, setCategory ] = useState(-1); + const objectIdRef = useRef(-1); const [ videoId, setVideoId ] = useState(null); const [ videoStart, setVideoStart ] = useState(null); const [ videoEnd, setVideoEnd ] = useState(null); @@ -23,6 +24,7 @@ const useFurnitureYoutubeWidgetState = () => const onClose = () => { + objectIdRef.current = -1; setObjectId(-1); setCategory(-1); setVideoId(null); @@ -64,6 +66,7 @@ const useFurnitureYoutubeWidgetState = () => if(!roomObject) return; + objectIdRef.current = event.objectId; setObjectId(event.objectId); setCategory(event.category); setHasControl(GetSessionDataManager().hasSecurity(SecurityLevel.EMPLOYEE) || IsOwnerOfFurniture(roomObject)); @@ -74,8 +77,9 @@ const useFurnitureYoutubeWidgetState = () => useMessageEvent(YoutubeDisplayVideoMessageEvent, event => { const parser = event.getParser(); + const currentObjectId = objectIdRef.current; - if((objectId === -1) || (objectId !== parser.furniId)) return; + if((currentObjectId === -1) || (currentObjectId !== parser.furniId)) return; setVideoId(parser.videoId); setVideoStart(parser.startAtSeconds); @@ -86,8 +90,9 @@ const useFurnitureYoutubeWidgetState = () => useMessageEvent(YoutubeDisplayPlaylistsEvent, event => { const parser = event.getParser(); + const currentObjectId = objectIdRef.current; - if((objectId === -1) || (objectId !== parser.furniId)) return; + if((currentObjectId === -1) || (currentObjectId !== parser.furniId)) return; setPlaylists(parser.playlists); setSelectedVideo(parser.selectedPlaylistId); @@ -100,8 +105,9 @@ const useFurnitureYoutubeWidgetState = () => useMessageEvent(YoutubeControlVideoMessageEvent, event => { const parser = event.getParser(); + const currentObjectId = objectIdRef.current; - if((objectId === -1) || (objectId !== parser.furniId)) return; + if((currentObjectId === -1) || (currentObjectId !== parser.furniId)) return; switch(parser.commandId) { From 57f6960735889bb2d24ae36da9dd285dc5d7b88a Mon Sep 17 00:00:00 2001 From: duckietm Date: Tue, 31 Mar 2026 14:55:29 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=86=99=20Fix=20the=20black=20screen?= =?UTF-8?q?=20in=20some=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../furniture/FurnitureYoutubeDisplayView.tsx | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx index 0d8dd5e..f2375ca 100644 --- a/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx +++ b/src/components/room/widgets/furniture/FurnitureYoutubeDisplayView.tsx @@ -17,42 +17,48 @@ export const FurnitureYoutubeDisplayView: FC<{}> = FurnitureYoutubeDisplayViewPr const onStateChange = (event: { target: YouTubePlayer; data: number }) => { - setPlayer(event.target); - - if(objectId === -1) return; - - switch(event.target.getPlayerState()) + try { - case -1: - case 1: - if(currentVideoState === 2) - { - //event.target.pauseVideo(); - } + setPlayer(event.target); - if(currentVideoState !== 1) play(); - return; - case 2: - if(currentVideoState !== 2) pause(); + if(objectId === -1) return; + + switch(event.target.getPlayerState()) + { + case -1: + case 1: + if(currentVideoState !== 1) play(); + return; + case 2: + if(currentVideoState !== 2) pause(); + } } + catch(err) {} }; useEffect(() => { if((currentVideoState === null) || !player) return; - if((currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PLAYING)) + try { - player.playVideo(); + if((currentVideoState === YoutubeVideoPlaybackStateEnum.PLAYING) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PLAYING)) + { + player.playVideo(); - return; + return; + } + + if((currentVideoState === YoutubeVideoPlaybackStateEnum.PAUSED) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PAUSED)) + { + player.pauseVideo(); + + return; + } } - - if((currentVideoState === YoutubeVideoPlaybackStateEnum.PAUSED) && (player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PAUSED)) + catch(err) { - player.pauseVideo(); - - return; + setPlayer(null); } }, [ currentVideoState, player ]); From ef008804d7c8512b1926094d37fe8a72bd056e2f Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 1 Apr 2026 09:11:15 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=86=99=20Added=20fix=20when=20slow=20?= =?UTF-8?q?download=20speeds=20clothes=20have=20problems?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/layout/LayoutAvatarImageView.tsx | 64 ++++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/src/common/layout/LayoutAvatarImageView.tsx b/src/common/layout/LayoutAvatarImageView.tsx index 2b3b156..89779bb 100644 --- a/src/common/layout/LayoutAvatarImageView.tsx +++ b/src/common/layout/LayoutAvatarImageView.tsx @@ -1,6 +1,7 @@ -import { AvatarScaleType, AvatarSetType, GetAvatarRenderManager } from '@nitrots/nitro-renderer'; +import { AvatarScaleType, AvatarSetType, GetAvatarRenderManager, NitroEventType } from '@nitrots/nitro-renderer'; import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react'; import { Base, BaseProps } from '../Base'; +import { useNitroEvent } from '../../hooks/events'; const AVATAR_IMAGE_CACHE: Map = new Map(); @@ -18,7 +19,14 @@ export const LayoutAvatarImageView: FC = props => const { figure = '', gender = '', headOnly = false, direction = 0, scale = 1, classNames = [], style = {}, ...rest } = props; const [ avatarUrl, setAvatarUrl ] = useState(null); const [ isReady, setIsReady ] = useState(false); + const [ updateId, setUpdateId ] = useState(0); const isDisposed = useRef(false); + const isPlaceholderRef = useRef(false); + + useNitroEvent(NitroEventType.AVATAR_ASSET_LOADED, () => + { + if(isPlaceholderRef.current) setUpdateId(prev => prev + 1); + }); const getClassNames = useMemo(() => { @@ -55,37 +63,49 @@ export const LayoutAvatarImageView: FC = props => if(AVATAR_IMAGE_CACHE.has(figureKey)) { + isPlaceholderRef.current = false; setAvatarUrl(AVATAR_IMAGE_CACHE.get(figureKey)); } else { - const resetFigure = (_figure: string) => - { - if(isDisposed.current) return; - - const avatarImage = GetAvatarRenderManager().createAvatarImage(_figure, AvatarScaleType.LARGE, gender, { resetFigure: (figure: string) => resetFigure(figure), dispose: null, disposed: false }); - - let setType = AvatarSetType.FULL; - - if(headOnly) setType = AvatarSetType.HEAD; - - avatarImage.setDirection(setType, direction); - - const imageUrl = avatarImage.processAsImageUrl(setType); - - if(imageUrl && !isDisposed.current) + const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, gender, { + resetFigure: (figure: string) => { - if(!avatarImage.isPlaceholder()) AVATAR_IMAGE_CACHE.set(figureKey, imageUrl); + if(isDisposed.current) return; - setAvatarUrl(imageUrl); + isPlaceholderRef.current = false; + setUpdateId(prev => prev + 1); + }, + dispose: null, + disposed: false + }); + + let setType = AvatarSetType.FULL; + + if(headOnly) setType = AvatarSetType.HEAD; + + avatarImage.setDirection(setType, direction); + + const imageUrl = avatarImage.processAsImageUrl(setType); + + if(imageUrl && !isDisposed.current) + { + if(!avatarImage.isPlaceholder()) + { + AVATAR_IMAGE_CACHE.set(figureKey, imageUrl); + isPlaceholderRef.current = false; + } + else + { + isPlaceholderRef.current = true; } - avatarImage.dispose(); - }; + setAvatarUrl(imageUrl); + } - resetFigure(figure); + avatarImage.dispose(); } - }, [ figure, gender, direction, headOnly, isReady ]); + }, [ figure, gender, direction, headOnly, isReady, updateId ]); useEffect(() => { From aa10cb54dbbf2a4b3f4505f6a064b8e8d695b718 Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 1 Apr 2026 10:23:00 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=86=99=20Small=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../avatar/AvatarEditorThumbnailsHelper.ts | 43 +++++++++++-------- .../AvatarEditorFigureSetItemView.tsx | 10 ++++- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/api/avatar/AvatarEditorThumbnailsHelper.ts b/src/api/avatar/AvatarEditorThumbnailsHelper.ts index bffc418..f40483d 100644 --- a/src/api/avatar/AvatarEditorThumbnailsHelper.ts +++ b/src/api/avatar/AvatarEditorThumbnailsHelper.ts @@ -150,34 +150,39 @@ export class AvatarEditorThumbnailsHelper if(cached) return cached; - return new Promise(async (resolve, reject) => + const avatarImage = GetAvatarRenderManager().createAvatarImage(figureString, AvatarScaleType.LARGE, null, null); + + if(avatarImage.isPlaceholder()) { - const resetFigure = async (figure: string) => - { - const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, { resetFigure, dispose: null, disposed: false }); + avatarImage.dispose(); - if(avatarImage.isPlaceholder()) return; + return null; + } - const texture = avatarImage.processAsTexture(AvatarSetType.HEAD, false); - const sprite = new NitroSprite(texture); + const texture = avatarImage.processAsTexture(AvatarSetType.HEAD, false); - if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; + if(!texture) + { + avatarImage.dispose(); - const imageUrl = await TextureUtils.generateImageUrl({ - target: sprite, - frame: new NitroRectangle(0, 0, texture.width, texture.height) - }); + return null; + } - sprite.destroy(); - avatarImage.dispose(); + const sprite = new NitroSprite(texture); - AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); + if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; - resolve(imageUrl); - }; - - resetFigure(figureString); + const imageUrl = await TextureUtils.generateImageUrl({ + target: sprite, + frame: new NitroRectangle(0, 0, texture.width, texture.height) }); + + sprite.destroy(); + avatarImage.dispose(); + + if(imageUrl) AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); + + return imageUrl; } private static sortByDrawOrder(a: IFigurePart, b: IFigurePart): number diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx index 789dc74..588005e 100644 --- a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx @@ -1,7 +1,9 @@ +import { NitroEventType } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; import { AvatarEditorThumbnailsHelper, GetClubMemberLevel, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../api'; import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common'; import { useAvatarEditor } from '../../../hooks'; +import { useNitroEvent } from '../../../hooks/events'; import { InfiniteGrid } from '../../../layout'; import { AvatarEditorIcon } from '../AvatarEditorIcon'; @@ -14,6 +16,7 @@ export const AvatarEditorFigureSetItemView: FC<{ { const { setType = null, partItem = null, isSelected = false, width = '100%', ...rest } = props; const [ assetUrl, setAssetUrl ] = useState(''); + const [ retryId, setRetryId ] = useState(0); const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor(); const clubLevel = partItem.partSet?.clubLevel ?? 0; @@ -21,6 +24,11 @@ export const AvatarEditorFigureSetItemView: FC<{ const isLocked = isHC && (GetClubMemberLevel() < clubLevel); const isSellableNotOwned = partItem.isSellableNotOwned ?? false; + useNitroEvent(NitroEventType.AVATAR_ASSET_LOADED, () => + { + if(!assetUrl || !assetUrl.length) setRetryId(prev => prev + 1); + }); + useEffect(() => { setAssetUrl(''); @@ -54,7 +62,7 @@ export const AvatarEditorFigureSetItemView: FC<{ }; loadImage(); - }, [ setType, partItem, selectedColorParts, getFigureStringWithFace, isSellableNotOwned ]); + }, [ setType, partItem, selectedColorParts, getFigureStringWithFace, isSellableNotOwned, retryId ]); if(!partItem) return null; From 49e1ac6307b13a0a22f017571e60fa985dd1d19e Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 1 Apr 2026 10:48:11 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=86=99=20Fix=20user=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user-profile/UserContainerView.tsx | 56 ++++++++++++++++--- .../user-profile/UserProfileView.tsx | 26 ++++----- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/components/user-profile/UserContainerView.tsx b/src/components/user-profile/UserContainerView.tsx index a6b3a00..88d3150 100644 --- a/src/components/user-profile/UserContainerView.tsx +++ b/src/components/user-profile/UserContainerView.tsx @@ -8,8 +8,11 @@ export const UserContainerView: FC<{ }> = props => { const { userProfile = null } = props; + const [ requestSent, setRequestSent ] = useState(userProfile.requestSent); + const isOwnProfile = (userProfile.id === GetSessionDataManager().userId); + const canSendFriendRequest = !requestSent && (!isOwnProfile && !userProfile.isMyFriend && !userProfile.requestSent); const infostandBackgroundClass = `background-${userProfile.backgroundId ?? 'default'}`; @@ -35,36 +38,75 @@ export const UserContainerView: FC<{
+

{ userProfile.username }

{ userProfile.motto }

+
-

-

-

- { LocalizeText('extendedprofile.friends.count') } { userProfile.friendsCount } -

+

+ +

+ +

+

{ LocalizeText('extendedprofile.achievementscore') } { userProfile.achievementPoints }

+
{ userProfile.isOnline && } + { !userProfile.isOnline && } +
{ canSendFriendRequest && - { LocalizeText('extendedprofile.addasafriend') } } + + { LocalizeText('extendedprofile.addasafriend') } + } + { !canSendFriendRequest && <> + { isOwnProfile &&

{ LocalizeText('extendedprofile.me') }

} + { userProfile.isMyFriend &&

{ LocalizeText('extendedprofile.friend') }

} + { (requestSent || userProfile.requestSent) &&

{ LocalizeText('extendedprofile.friendrequestsent') }

} } @@ -73,4 +115,4 @@ export const UserContainerView: FC<{
); -}; +}; \ No newline at end of file diff --git a/src/components/user-profile/UserProfileView.tsx b/src/components/user-profile/UserProfileView.tsx index b294346..67bb6b4 100644 --- a/src/components/user-profile/UserProfileView.tsx +++ b/src/components/user-profile/UserProfileView.tsx @@ -9,7 +9,7 @@ import { FriendsContainerView } from './FriendsContainerView'; import { GroupsContainerView } from './GroupsContainerView'; import { UserContainerView } from './UserContainerView'; -type ProfileTab = 'badge' | 'amici' | 'stanze' | 'gruppi'; +type ProfileTab = 'badge' | 'friends' | 'rooms' | 'groups'; export const UserProfileView: FC<{}> = props => { @@ -39,7 +39,7 @@ export const UserProfileView: FC<{}> = props => { setActiveTab(tab); - if(tab === 'stanze' && !userRooms && userProfile) + if(tab === 'rooms' && !userRooms && userProfile) { SendMessageComposer(new NavigatorSearchComposer('hotel_view', `owner:${ userProfile.username }`)); } @@ -99,7 +99,7 @@ export const UserProfileView: FC<{}> = props => useMessageEvent(NavigatorSearchEvent, event => { - if(!userProfile || activeTab !== 'stanze') return; + if(!userProfile || activeTab !== 'rooms') return; const parser = event.getParser(); const result = parser.result; @@ -145,16 +145,16 @@ export const UserProfileView: FC<{}> = props =>
onTabClick('badge') }> - { LocalizeText('extendedprofile.tab.badge') } + { LocalizeText('levelinfo.category.badge') } - onTabClick('amici') }> - { LocalizeText('extendedprofile.tab.friends') } + onTabClick('friends') }> + { LocalizeText('navigator.tab.3') } - onTabClick('stanze') }> - { LocalizeText('extendedprofile.tab.rooms') } + onTabClick('rooms') }> + { LocalizeText('navigator.tab.2') } - onTabClick('gruppi') }> - { LocalizeText('extendedprofile.tab.groups') } + onTabClick('groups') }> + { LocalizeText('navigator.searchcode.title.groups') }
@@ -172,7 +172,7 @@ export const UserProfileView: FC<{}> = props => }
) } - { activeTab === 'amici' && ( + { activeTab === 'friends' && (
{ userRelationships ? ( @@ -183,7 +183,7 @@ export const UserProfileView: FC<{}> = props => ) }
) } - { activeTab === 'stanze' && ( + { activeTab === 'rooms' && (
{ !userRooms && ( @@ -206,7 +206,7 @@ export const UserProfileView: FC<{}> = props => )) }
) } - { activeTab === 'gruppi' && ( + { activeTab === 'groups' && (
From bfb56f0a4ea597935ad26c6e66fd9293b863965f Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 1 Apr 2026 11:48:43 +0200 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=86=99=20Small=20test=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/layout/LayoutAvatarImageView.tsx | 23 ++++++++----------- .../AvatarEditorFigureSetItemView.tsx | 6 ++++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/common/layout/LayoutAvatarImageView.tsx b/src/common/layout/LayoutAvatarImageView.tsx index 89779bb..fdde1ac 100644 --- a/src/common/layout/LayoutAvatarImageView.tsx +++ b/src/common/layout/LayoutAvatarImageView.tsx @@ -21,11 +21,15 @@ export const LayoutAvatarImageView: FC = props => const [ isReady, setIsReady ] = useState(false); const [ updateId, setUpdateId ] = useState(0); const isDisposed = useRef(false); - const isPlaceholderRef = useRef(false); + const figureKeyRef = useRef(null); useNitroEvent(NitroEventType.AVATAR_ASSET_LOADED, () => { - if(isPlaceholderRef.current) setUpdateId(prev => prev + 1); + if(figureKeyRef.current) + { + AVATAR_IMAGE_CACHE.delete(figureKeyRef.current); + setUpdateId(prev => prev + 1); + } }); const getClassNames = useMemo(() => @@ -61,9 +65,10 @@ export const LayoutAvatarImageView: FC = props => const figureKey = [ figure, gender, direction, headOnly ].join('-'); + figureKeyRef.current = figureKey; + if(AVATAR_IMAGE_CACHE.has(figureKey)) { - isPlaceholderRef.current = false; setAvatarUrl(AVATAR_IMAGE_CACHE.get(figureKey)); } else @@ -73,7 +78,7 @@ export const LayoutAvatarImageView: FC = props => { if(isDisposed.current) return; - isPlaceholderRef.current = false; + AVATAR_IMAGE_CACHE.delete(figureKey); setUpdateId(prev => prev + 1); }, dispose: null, @@ -90,15 +95,7 @@ export const LayoutAvatarImageView: FC = props => if(imageUrl && !isDisposed.current) { - if(!avatarImage.isPlaceholder()) - { - AVATAR_IMAGE_CACHE.set(figureKey, imageUrl); - isPlaceholderRef.current = false; - } - else - { - isPlaceholderRef.current = true; - } + if(!avatarImage.isPlaceholder()) AVATAR_IMAGE_CACHE.set(figureKey, imageUrl); setAvatarUrl(imageUrl); } diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx index 588005e..7b5d65c 100644 --- a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx @@ -26,7 +26,11 @@ export const AvatarEditorFigureSetItemView: FC<{ useNitroEvent(NitroEventType.AVATAR_ASSET_LOADED, () => { - if(!assetUrl || !assetUrl.length) setRetryId(prev => prev + 1); + if(!assetUrl || !assetUrl.length) + { + AvatarEditorThumbnailsHelper.clearCache(); + setRetryId(prev => prev + 1); + } }); useEffect(() => From 6ad9a0c7b95905c4ac72d902fef3296fcf8ee3fd Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 1 Apr 2026 17:12:09 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=86=99=20Fix=20the=20catalog=20search?= =?UTF-8?q?=20in=20some=20cases=20it=20will=20give=20a=20black=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../avatar/AvatarEditorThumbnailsHelper.ts | 43 ++++++++----------- src/api/catalog/CatalogUtilities.ts | 6 ++- src/api/catalog/FurnitureOffer.ts | 4 +- .../page/common/CatalogGridOfferView.tsx | 8 +++- .../views/page/common/CatalogSearchView.tsx | 6 ++- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/api/avatar/AvatarEditorThumbnailsHelper.ts b/src/api/avatar/AvatarEditorThumbnailsHelper.ts index f40483d..bffc418 100644 --- a/src/api/avatar/AvatarEditorThumbnailsHelper.ts +++ b/src/api/avatar/AvatarEditorThumbnailsHelper.ts @@ -150,39 +150,34 @@ export class AvatarEditorThumbnailsHelper if(cached) return cached; - const avatarImage = GetAvatarRenderManager().createAvatarImage(figureString, AvatarScaleType.LARGE, null, null); - - if(avatarImage.isPlaceholder()) + return new Promise(async (resolve, reject) => { - avatarImage.dispose(); + const resetFigure = async (figure: string) => + { + const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, { resetFigure, dispose: null, disposed: false }); - return null; - } + if(avatarImage.isPlaceholder()) return; - const texture = avatarImage.processAsTexture(AvatarSetType.HEAD, false); + const texture = avatarImage.processAsTexture(AvatarSetType.HEAD, false); + const sprite = new NitroSprite(texture); - if(!texture) - { - avatarImage.dispose(); + if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; - return null; - } + const imageUrl = await TextureUtils.generateImageUrl({ + target: sprite, + frame: new NitroRectangle(0, 0, texture.width, texture.height) + }); - const sprite = new NitroSprite(texture); + sprite.destroy(); + avatarImage.dispose(); - if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; + AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); - const imageUrl = await TextureUtils.generateImageUrl({ - target: sprite, - frame: new NitroRectangle(0, 0, texture.width, texture.height) + resolve(imageUrl); + }; + + resetFigure(figureString); }); - - sprite.destroy(); - avatarImage.dispose(); - - if(imageUrl) AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); - - return imageUrl; } private static sortByDrawOrder(a: IFigurePart, b: IFigurePart): number diff --git a/src/api/catalog/CatalogUtilities.ts b/src/api/catalog/CatalogUtilities.ts index c2e1d5e..9cb8866 100644 --- a/src/api/catalog/CatalogUtilities.ts +++ b/src/api/catalog/CatalogUtilities.ts @@ -13,6 +13,8 @@ export const GetSubscriptionProductIcon = (id: number) => export const GetOfferNodes = (offerNodes: Map, offerId: number) => { + if(!offerNodes) return []; + const nodes = offerNodes.get(offerId); const allowedNodes: ICatalogNode[] = []; @@ -20,7 +22,7 @@ export const GetOfferNodes = (offerNodes: Map, offerId: { for(const node of nodes) { - if(!node.isVisible) continue; + if(!node || !node.isVisible) continue; allowedNodes.push(node); } @@ -31,6 +33,8 @@ export const GetOfferNodes = (offerNodes: Map, offerId: export const FilterCatalogNode = (search: string, furniLines: string[], node: ICatalogNode, nodes: ICatalogNode[]) => { + if(!node) return; + if(node.isVisible && (node.pageId > 0)) { let nodeAdded = false; diff --git a/src/api/catalog/FurnitureOffer.ts b/src/api/catalog/FurnitureOffer.ts index 367f247..5b68c11 100644 --- a/src/api/catalog/FurnitureOffer.ts +++ b/src/api/catalog/FurnitureOffer.ts @@ -105,12 +105,12 @@ export class FurnitureOffer implements IPurchasableOffer public get localizationName(): string { - return this._furniData.name; + return this._furniData?.name ?? ''; } public get localizationDescription(): string { - return this._furniData.description; + return this._furniData?.description ?? ''; } public get isLazy(): boolean diff --git a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx index a3fcedd..fd7bba4 100644 --- a/src/components/catalog/views/page/common/CatalogGridOfferView.tsx +++ b/src/components/catalog/views/page/common/CatalogGridOfferView.tsx @@ -18,16 +18,18 @@ export const CatalogGridOfferView: FC = props => const { requestOfferToMover = null } = useCatalog(); const { isVisible = false } = useInventoryFurni(); const { isFavoriteOffer, toggleFavoriteOffer } = useCatalogFavorites(); - const isFav = isFavoriteOffer(offer.offerId); + const isFav = offer ? isFavoriteOffer(offer.offerId) : false; const iconUrl = useMemo(() => { + if(!offer) return null; + if(offer.pricingModel === Offer.PRICING_MODEL_BUNDLE) { return null; } - return offer.product.getIconUrl(offer); + return offer.product?.getIconUrl(offer) ?? null; }, [ offer ]); const onMouseEvent = (event: MouseEvent) => @@ -49,6 +51,8 @@ export const CatalogGridOfferView: FC = props => } }; + if(!offer) return null; + const product = offer.product; if(!product) return null; diff --git a/src/components/catalog/views/page/common/CatalogSearchView.tsx b/src/components/catalog/views/page/common/CatalogSearchView.tsx index 7cc30c7..295abe3 100644 --- a/src/components/catalog/views/page/common/CatalogSearchView.tsx +++ b/src/components/catalog/views/page/common/CatalogSearchView.tsx @@ -22,6 +22,8 @@ export const CatalogSearchView: FC<{}> = () => const timeout = setTimeout(() => { + if(!offersToNodes || !rootNode) return; + const furnitureDatas = GetSessionDataManager().getAllFurnitureData(); if(!furnitureDatas || !furnitureDatas.length) return; @@ -31,11 +33,13 @@ export const CatalogSearchView: FC<{}> = () => for(const furniture of furnitureDatas) { + if(!furniture) continue; + if((currentType === CatalogType.BUILDER) && !furniture.availableForBuildersClub) continue; if((currentType === CatalogType.NORMAL) && furniture.excludeDynamic) continue; - const searchValues = [ furniture.className, furniture.name, furniture.description ].join(' ').replace(/ /gi, '').toLowerCase(); + const searchValues = [ furniture.className || '', furniture.name || '', furniture.description || '' ].join(' ').replace(/ /gi, '').toLowerCase(); if((currentType === CatalogType.BUILDER) && (furniture.purchaseOfferId === -1) && (furniture.rentOfferId === -1)) {