mirror of
https://github.com/duckietm/Nitro-Cool-UI.git
synced 2025-06-21 22:36:58 +00:00
🆙 Added badge loading system
This commit is contained in:
parent
a59c093e04
commit
313c814b1e
@ -21,7 +21,7 @@ $toolbar-height: 55px;
|
|||||||
$achievement-width: 375px;
|
$achievement-width: 375px;
|
||||||
$achievement-height: 405px;
|
$achievement-height: 405px;
|
||||||
|
|
||||||
$avatar-editor-width: 545px;
|
$avatar-editor-width: 520px;
|
||||||
$avatar-editor-height: 553px;
|
$avatar-editor-height: 553px;
|
||||||
|
|
||||||
$backgrounds-width: 534px;
|
$backgrounds-width: 534px;
|
||||||
|
23
src/App.tsx
23
src/App.tsx
@ -1,5 +1,6 @@
|
|||||||
import { ConfigurationEvent, GetAssetManager, HabboWebTools, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroConfiguration, NitroEvent, NitroLocalizationEvent, NitroVersion, RoomEngineEvent } from '@nitrots/nitro-renderer';
|
import { ConfigurationEvent, GetAssetManager, HabboWebTools, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroConfiguration, NitroEvent, NitroLocalizationEvent, NitroVersion, RoomEngineEvent } from '@nitrots/nitro-renderer';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { BadgeProvider } from './common/layout/BadgeContext';
|
||||||
import { Base } from './common';
|
import { Base } from './common';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useCallback, useEffect, useState } from 'react';
|
||||||
import { GetCommunication, GetConfiguration, GetNitroInstance, GetUIVersion } from './api';
|
import { GetCommunication, GetConfiguration, GetNitroInstance, GetUIVersion } from './api';
|
||||||
@ -7,6 +8,7 @@ import { LoadingView } from './components/loading/LoadingView';
|
|||||||
import { MainView } from './components/main/MainView';
|
import { MainView } from './components/main/MainView';
|
||||||
import { useConfigurationEvent, useLocalizationEvent, useMainEvent, useRoomEngineEvent } from './hooks';
|
import { useConfigurationEvent, useLocalizationEvent, useMainEvent, useRoomEngineEvent } from './hooks';
|
||||||
|
|
||||||
|
|
||||||
NitroVersion.UI_VERSION = GetUIVersion();
|
NitroVersion.UI_VERSION = GetUIVersion();
|
||||||
|
|
||||||
export const App: FC<{}> = props =>
|
export const App: FC<{}> = props =>
|
||||||
@ -131,15 +133,18 @@ export const App: FC<{}> = props =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Base fit overflow="hidden" className={ imageRendering && 'image-rendering-pixelated' }>
|
<Base fit overflow="hidden" className={ imageRendering && 'image-rendering-pixelated' }>
|
||||||
{ (!isReady || isError) &&
|
<BadgeProvider>
|
||||||
<LoadingView isError={ isError } message={ message } percent={ percent } /> }
|
{ (!isReady || isError) &&
|
||||||
<AnimatePresence>
|
<LoadingView isError={ isError } message={ message } percent={ percent } /> }
|
||||||
{ isReady && ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.3 }}>
|
<AnimatePresence>
|
||||||
<MainView />
|
{ isReady && (
|
||||||
</motion.div>
|
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.3 }}>
|
||||||
)}
|
<MainView />
|
||||||
</AnimatePresence>
|
</motion.div>
|
||||||
<Base id="draggable-windows-container" />
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<Base id="draggable-windows-container" />
|
||||||
|
</BadgeProvider>
|
||||||
</Base>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
198
src/common/layout/BadgeContext.tsx
Normal file
198
src/common/layout/BadgeContext.tsx
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import { BadgeImageReadyEvent, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
||||||
|
import { createContext, FC, useContext, useEffect, useState } from 'react';
|
||||||
|
import { GetSessionDataManager } from '../../api';
|
||||||
|
|
||||||
|
interface BadgeContextType
|
||||||
|
{
|
||||||
|
badgeImages: Map<string, HTMLImageElement>;
|
||||||
|
requestBadge: (badgeCode: string, isGroup: boolean) => Promise<HTMLImageElement>;
|
||||||
|
updateBadgeImage: (badgeCode: string, image: HTMLImageElement) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BadgeContext = createContext<BadgeContextType>({
|
||||||
|
badgeImages: new Map(),
|
||||||
|
requestBadge: async () =>
|
||||||
|
{
|
||||||
|
console.warn('BadgeContext: Default requestBadge called - BadgeProvider not initialized');
|
||||||
|
throw new Error('BadgeProvider not initialized - ensure BadgeProvider is wrapped around the app');
|
||||||
|
},
|
||||||
|
updateBadgeImage: () =>
|
||||||
|
{
|
||||||
|
console.warn('BadgeContext: Default updateBadgeImage called - BadgeProvider not initialized');
|
||||||
|
throw new Error('BadgeProvider not initialized - ensure BadgeProvider is wrapped around the app');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BadgeProvider: FC<{}> = ({ children }) =>
|
||||||
|
{
|
||||||
|
const [ badgeImages, setBadgeImages ] = useState<Map<string, HTMLImageElement>>(new Map());
|
||||||
|
|
||||||
|
console.log('BadgeProvider: Initialized');
|
||||||
|
|
||||||
|
const requestBadge = async (badgeCode: string, isGroup: boolean): Promise<HTMLImageElement> =>
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: requestBadge called', { badgeCode, isGroup });
|
||||||
|
|
||||||
|
if(!badgeCode || !badgeCode.length)
|
||||||
|
{
|
||||||
|
console.warn('BadgeProvider: Invalid or empty badgeCode', badgeCode);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if badge is already cached
|
||||||
|
const cachedImage = badgeImages.get(badgeCode);
|
||||||
|
if(cachedImage)
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: Badge loaded from cache', { badgeCode });
|
||||||
|
return cachedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxRetries = 3;
|
||||||
|
let retryCount = 0;
|
||||||
|
|
||||||
|
while(retryCount < maxRetries)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: Attempting to load badge', { badgeCode, retryCount, isGroup });
|
||||||
|
|
||||||
|
// Check if GetSessionDataManager is ready
|
||||||
|
if(!GetSessionDataManager().events)
|
||||||
|
{
|
||||||
|
console.warn('BadgeProvider: GetSessionDataManager events not available', { badgeCode, isGroup });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if badge is already in session data
|
||||||
|
let texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode);
|
||||||
|
|
||||||
|
if(!texture)
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: Badge not in cache, requesting', { badgeCode, isGroup });
|
||||||
|
|
||||||
|
// Create a Promise to wait for the BadgeImageReadyEvent
|
||||||
|
const badgePromise = new Promise<Texture<Resource>>((resolve, reject) =>
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: Setting up BadgeImageReadyEvent listener', { badgeCode, isGroup });
|
||||||
|
|
||||||
|
const onBadgeImageReadyEvent = (event: BadgeImageReadyEvent) =>
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: BadgeImageReadyEvent received', { badgeCode, eventBadgeId: event.badgeId, isGroup });
|
||||||
|
|
||||||
|
if(event.badgeId === badgeCode)
|
||||||
|
{
|
||||||
|
resolve(event.image);
|
||||||
|
GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GetSessionDataManager().events.addEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
|
|
||||||
|
// Timeout after 20 seconds to avoid hanging
|
||||||
|
setTimeout(() =>
|
||||||
|
{
|
||||||
|
console.warn('BadgeProvider: Badge loading timed out', { badgeCode, retryCount, isGroup });
|
||||||
|
GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
|
reject(new Error('Badge loading timed out'));
|
||||||
|
}, 20000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request the badge image
|
||||||
|
console.log('BadgeProvider: Requesting badge image', { badgeCode, isGroup });
|
||||||
|
if(isGroup)
|
||||||
|
{
|
||||||
|
GetSessionDataManager().requestGroupBadgeImage(badgeCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GetSessionDataManager().requestBadgeImage(badgeCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the badge image to be ready
|
||||||
|
texture = await badgePromise;
|
||||||
|
console.log('BadgeProvider: Badge image received via event', { badgeCode, texture, isGroup });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: Badge found in session data', { badgeCode, isGroup });
|
||||||
|
}
|
||||||
|
|
||||||
|
if(texture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: Generating image from texture', { badgeCode, isGroup });
|
||||||
|
const sprite = new NitroSprite(texture);
|
||||||
|
const element = await TextureUtils.generateImage(sprite);
|
||||||
|
|
||||||
|
if(element && element.src && element.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
setBadgeImages(prev =>
|
||||||
|
{
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
newMap.set(badgeCode, element);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
console.log('BadgeProvider: Badge loaded and cached', { badgeCode, isGroup });
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('BadgeProvider: Invalid badge image', { badgeCode, element, isGroup });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('BadgeProvider: Error generating badge image', { error: error.message, badgeCode, isGroup });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('BadgeProvider: Failed to load badge image', { badgeCode, isGroup });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.error('BadgeProvider: Error loading badge', { error: error.message, badgeCode, retryCount, isGroup });
|
||||||
|
}
|
||||||
|
|
||||||
|
retryCount++;
|
||||||
|
if(retryCount < maxRetries)
|
||||||
|
{
|
||||||
|
console.log('BadgeProvider: Retrying badge load', { badgeCode, retryCount, isGroup });
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds before retrying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('BadgeProvider: Max retries reached, returning null', { badgeCode, maxRetries, isGroup });
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBadgeImage = (badgeCode: string, image: HTMLImageElement) =>
|
||||||
|
{
|
||||||
|
if(!badgeCode || !image) return;
|
||||||
|
|
||||||
|
console.log('BadgeProvider: Updating badge image in context', { badgeCode });
|
||||||
|
|
||||||
|
setBadgeImages(prev =>
|
||||||
|
{
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
newMap.set(badgeCode, image);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BadgeContext.Provider value={ { badgeImages, requestBadge, updateBadgeImage } }>
|
||||||
|
{ children }
|
||||||
|
</BadgeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useBadgeContext = () => {
|
||||||
|
const context = useContext(BadgeContext);
|
||||||
|
if(!context.requestBadge || !context.updateBadgeImage || context.requestBadge.toString().includes('Default requestBadge')) {
|
||||||
|
throw new Error('BadgeContext not initialized - ensure BadgeProvider is wrapped around the app');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
@ -1,7 +1,8 @@
|
|||||||
import { BadgeImageReadyEvent, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
|
||||||
import { CSSProperties, FC, useEffect, useMemo, useState } from 'react';
|
import { CSSProperties, FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { GetConfiguration, GetSessionDataManager, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../api';
|
import { GetConfiguration, GetSessionDataManager, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../api';
|
||||||
import { Base, BaseProps } from '../Base';
|
import { Base, BaseProps } from '../Base';
|
||||||
|
import { NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
||||||
|
import { useBadgeContext } from './BadgeContext';
|
||||||
|
|
||||||
export interface LayoutBadgeImageViewProps extends BaseProps<HTMLDivElement>
|
export interface LayoutBadgeImageViewProps extends BaseProps<HTMLDivElement>
|
||||||
{
|
{
|
||||||
@ -17,6 +18,13 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
{
|
{
|
||||||
const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null, isGrayscale = false, scale = 1, classNames = [], style = {}, children = null, ...rest } = props;
|
const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null, isGrayscale = false, scale = 1, classNames = [], style = {}, children = null, ...rest } = props;
|
||||||
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
|
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
|
||||||
|
const [ isLoading, setIsLoading ] = useState(true);
|
||||||
|
const [ retryCount, setRetryCount ] = useState(0);
|
||||||
|
const maxRetries = 5; // Maximum number of retries
|
||||||
|
const retryInterval = 2000; // Retry every 2 seconds
|
||||||
|
const { badgeImages, requestBadge, updateBadgeImage } = useBadgeContext();
|
||||||
|
|
||||||
|
console.log('LayoutBadgeImageView: Rendered', { badgeCode, isGroup, badgeImagesSize: badgeImages.size });
|
||||||
|
|
||||||
const getClassNames = useMemo(() =>
|
const getClassNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -38,15 +46,16 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
const badgeUrl = isGroup ? imageElement.src : GetConfiguration<string>('badge.asset.url', '').replace('%badgename%', badgeCode.toString());
|
const badgeUrl = isGroup ? imageElement.src : GetConfiguration<string>('badge.asset.url', '').replace('%badgename%', badgeCode.toString());
|
||||||
|
|
||||||
newStyle.backgroundImage = `url(${ badgeUrl })`;
|
newStyle.backgroundImage = `url(${ badgeUrl })`;
|
||||||
newStyle.width = imageElement.width;
|
|
||||||
newStyle.height = imageElement.height;
|
// Remove inline width and height to let SCSS control the size
|
||||||
|
// newStyle.width = imageElement.width;
|
||||||
|
// newStyle.height = imageElement.height;
|
||||||
|
|
||||||
if(scale !== 1)
|
if(scale !== 1)
|
||||||
{
|
{
|
||||||
newStyle.transform = `scale(${ scale })`;
|
newStyle.transform = `scale(${ scale })`;
|
||||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||||
newStyle.width = (imageElement.width * scale);
|
// If scaling, adjust the dimensions in SCSS instead
|
||||||
newStyle.height = (imageElement.height * scale);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,80 +66,103 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
|
console.log('LayoutBadgeImageView: useEffect triggered', { badgeCode, isGroup, retryCount });
|
||||||
|
|
||||||
if(!badgeCode || !badgeCode.length)
|
if(!badgeCode || !badgeCode.length)
|
||||||
{
|
{
|
||||||
console.warn('LayoutBadgeImageView: Invalid or empty badgeCode', badgeCode);
|
console.warn('LayoutBadgeImageView: Invalid or empty badgeCode', badgeCode);
|
||||||
setImageElement(null);
|
setImageElement(null);
|
||||||
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let didSetBadge = false;
|
const loadBadgeImage = async () =>
|
||||||
|
|
||||||
const onBadgeImageReadyEvent = async (event: BadgeImageReadyEvent) =>
|
|
||||||
{
|
{
|
||||||
if(event.badgeId !== badgeCode) return;
|
console.log('LayoutBadgeImageView: loadBadgeImage started', { badgeCode, retryCount, isGroup });
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const sprite = new NitroSprite(event.image);
|
// Check if badge is already in context
|
||||||
const element = await TextureUtils.generateImage(sprite);
|
const cachedImage = badgeImages.get(badgeCode);
|
||||||
|
if(cachedImage)
|
||||||
|
{
|
||||||
|
setImageElement(cachedImage);
|
||||||
|
setIsLoading(false);
|
||||||
|
console.log('LayoutBadgeImageView: Badge loaded from context', { badgeCode, isGroup });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(element && element.src && element.src.startsWith('data:image/'))
|
console.log('LayoutBadgeImageView: Requesting badge via context', { badgeCode, isGroup });
|
||||||
|
|
||||||
|
// Request the badge image via the context
|
||||||
|
const element = await requestBadge(badgeCode, isGroup);
|
||||||
|
|
||||||
|
if(element)
|
||||||
{
|
{
|
||||||
setImageElement(element);
|
setImageElement(element);
|
||||||
didSetBadge = true;
|
console.log('LayoutBadgeImageView: Badge loaded via request', { badgeCode, isGroup });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
console.warn('LayoutBadgeImageView: Invalid badge image (event)', element);
|
console.warn('LayoutBadgeImageView: Failed to load badge image via context, attempting direct fetch', { badgeCode, isGroup });
|
||||||
|
|
||||||
|
// Fallback: Try fetching directly from session data
|
||||||
|
let texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode);
|
||||||
|
|
||||||
|
if(texture)
|
||||||
|
{
|
||||||
|
const sprite = new NitroSprite(texture);
|
||||||
|
const fallbackElement = await TextureUtils.generateImage(sprite);
|
||||||
|
|
||||||
|
if(fallbackElement && fallbackElement.src && fallbackElement.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
setImageElement(fallbackElement);
|
||||||
|
updateBadgeImage(badgeCode, fallbackElement);
|
||||||
|
console.log('LayoutBadgeImageView: Badge loaded via direct fetch and cached in context', { badgeCode, isGroup });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutBadgeImageView: Invalid badge image from direct fetch', { badgeCode, isGroup });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(retryCount < maxRetries)
|
||||||
|
{
|
||||||
|
console.log('LayoutBadgeImageView: Retrying badge load', { badgeCode, retryCount, isGroup });
|
||||||
|
setTimeout(() =>
|
||||||
|
{
|
||||||
|
setRetryCount(prev => prev + 1);
|
||||||
|
}, retryInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutBadgeImageView: Max retries reached, failed to load badge', { badgeCode, maxRetries, isGroup });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(error)
|
catch(error)
|
||||||
{
|
{
|
||||||
console.warn('LayoutBadgeImageView: Error generating badge image (event)', error);
|
console.error('LayoutBadgeImageView: Error loading badge', { error: error.message, badgeCode, isGroup });
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
|
||||||
};
|
|
||||||
|
|
||||||
GetSessionDataManager().events.addEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
|
||||||
|
|
||||||
const loadBadgeImage = async () =>
|
|
||||||
{
|
|
||||||
const texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode);
|
|
||||||
|
|
||||||
if(texture && !didSetBadge)
|
|
||||||
{
|
{
|
||||||
try
|
setIsLoading(false);
|
||||||
{
|
console.log('LayoutBadgeImageView: loadBadgeImage completed', { badgeCode, isLoading: false, isGroup });
|
||||||
const sprite = new NitroSprite(texture);
|
|
||||||
const element = await TextureUtils.generateImage(sprite);
|
|
||||||
|
|
||||||
if(element && element.src && element.src.startsWith('data:image/'))
|
|
||||||
{
|
|
||||||
setImageElement(element);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.warn('LayoutBadgeImageView: Invalid badge image (direct)', element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(error)
|
|
||||||
{
|
|
||||||
console.warn('LayoutBadgeImageView: Error generating badge image (direct)', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.log('LayoutBadgeImageView: No texture found for badge', badgeCode);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadBadgeImage();
|
loadBadgeImage();
|
||||||
|
}, [ badgeCode, isGroup, badgeImages, requestBadge, retryCount, updateBadgeImage ]);
|
||||||
|
|
||||||
return () => GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
if(isLoading)
|
||||||
}, [ badgeCode, isGroup ]);
|
{
|
||||||
|
console.log('LayoutBadgeImageView: Rendering loading state', { badgeCode, isGroup });
|
||||||
|
return null; // Optionally render a loading placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('LayoutBadgeImageView: Rendering badge', { badgeCode, hasImage: !!imageElement, isGroup });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base classNames={ getClassNames } style={ getStyle } { ...rest }>
|
<Base classNames={ getClassNames } style={ getStyle } { ...rest }>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
export * from './BadgeContext';
|
||||||
export * from './LayoutAvatarImageView';
|
export * from './LayoutAvatarImageView';
|
||||||
export * from './LayoutBackgroundImage';
|
export * from './LayoutBackgroundImage';
|
||||||
export * from './LayoutBadgeImageView';
|
export * from './LayoutBadgeImageView';
|
||||||
|
@ -26,6 +26,7 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
|
|||||||
|
|
||||||
if(!partItem)
|
if(!partItem)
|
||||||
{
|
{
|
||||||
|
console.warn(`AvatarEditorFigureSetItemView: Part item missing`, partItem);
|
||||||
setImageUrl(null);
|
setImageUrl(null);
|
||||||
setIsValid(false);
|
setIsValid(false);
|
||||||
return;
|
return;
|
||||||
@ -51,13 +52,16 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
|
|||||||
|
|
||||||
if(!resolvedImageUrl || !resolvedImageUrl.startsWith('data:image/'))
|
if(!resolvedImageUrl || !resolvedImageUrl.startsWith('data:image/'))
|
||||||
{
|
{
|
||||||
|
console.warn(`AvatarEditorFigureSetItemView: Invalid or missing imageUrl for item ${partItem.id}`, { resolvedImageUrl, type: typeof resolvedImageUrl });
|
||||||
if(retryCount.current < maxRetries)
|
if(retryCount.current < maxRetries)
|
||||||
{
|
{
|
||||||
retryCount.current += 1;
|
retryCount.current += 1;
|
||||||
|
console.log(`AvatarEditorFigureSetItemView: Retrying load for item ${partItem.id} (retry: ${retryCount.current}/${maxRetries})`);
|
||||||
setTimeout(loadPartImage, 1500);
|
setTimeout(loadPartImage, 1500);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
console.log(`AvatarEditorFigureSetItemView: Max retries reached, skipping item ${partItem.id}`);
|
||||||
setImageUrl(null);
|
setImageUrl(null);
|
||||||
setIsValid(false);
|
setIsValid(false);
|
||||||
}
|
}
|
||||||
|
@ -86,8 +86,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.badge-image {
|
.badge-image {
|
||||||
width: 45px;
|
max-width: 45px;
|
||||||
height: 45px;
|
max-height: 45px;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.motto-content {
|
.motto-content {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ExtendedProfileChangedMessageEvent, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
|
import { ExtendedProfileChangedMessageEvent, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { CreateLinkEvent, GetRoomSession, GetSessionDataManager, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
|
import { CreateLinkEvent, GetRoomSession, GetSessionDataManager, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
|
||||||
import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
||||||
import { useMessageEvent, useRoomEngineEvent } from '../../hooks';
|
import { useMessageEvent, useRoomEngineEvent } from '../../hooks';
|
||||||
@ -8,17 +8,28 @@ import { FriendsContainerView } from './views/FriendsContainerView';
|
|||||||
import { GroupsContainerView } from './views/GroupsContainerView';
|
import { GroupsContainerView } from './views/GroupsContainerView';
|
||||||
import { UserContainerView } from './views/UserContainerView';
|
import { UserContainerView } from './views/UserContainerView';
|
||||||
|
|
||||||
|
let currentUserId: number | null = null;
|
||||||
|
|
||||||
|
const handleUserCurrentBadgesEvent = (event: UserCurrentBadgesEvent, setUserBadges: (badges: string[]) => void) => {
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
if (currentUserId === null || parser.userId !== currentUserId) return;
|
||||||
|
|
||||||
|
setUserBadges(parser.badges);
|
||||||
|
};
|
||||||
|
|
||||||
export const UserProfileView: FC<{}> = props =>
|
export const UserProfileView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const [ userProfile, setUserProfile ] = useState<UserProfileParser>(null);
|
const [ userProfile, setUserProfile ] = useState<UserProfileParser>(null);
|
||||||
const [ userBadges, setUserBadges ] = useState<string[]>([]);
|
const [ userBadges, setUserBadges ] = useState<string[]>(null);
|
||||||
const [ userRelationships, setUserRelationships ] = useState<RelationshipStatusInfoMessageParser>(null);
|
const [ userRelationships, setUserRelationships ] = useState<RelationshipStatusInfoMessageParser>(null);
|
||||||
|
|
||||||
const onClose = () =>
|
const onClose = () =>
|
||||||
{
|
{
|
||||||
setUserProfile(null);
|
setUserProfile(null);
|
||||||
setUserBadges([]);
|
setUserBadges(null);
|
||||||
setUserRelationships(null);
|
setUserRelationships(null);
|
||||||
|
currentUserId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLeaveGroup = () =>
|
const onLeaveGroup = () =>
|
||||||
@ -28,13 +39,8 @@ export const UserProfileView: FC<{}> = props =>
|
|||||||
GetUserProfile(userProfile.id);
|
GetUserProfile(userProfile.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
useMessageEvent<UserCurrentBadgesEvent>(UserCurrentBadgesEvent, event =>
|
useMessageEvent<UserCurrentBadgesEvent>(UserCurrentBadgesEvent, event => {
|
||||||
{
|
handleUserCurrentBadgesEvent(event, setUserBadges);
|
||||||
const parser = event.getParser();
|
|
||||||
|
|
||||||
if(!userProfile || (parser.userId !== userProfile.id)) return;
|
|
||||||
|
|
||||||
setUserBadges(parser.badges);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useMessageEvent<RelationshipStatusInfoEvent>(RelationshipStatusInfoEvent, event =>
|
useMessageEvent<RelationshipStatusInfoEvent>(RelationshipStatusInfoEvent, event =>
|
||||||
@ -61,10 +67,12 @@ export const UserProfileView: FC<{}> = props =>
|
|||||||
|
|
||||||
if(!isSameProfile)
|
if(!isSameProfile)
|
||||||
{
|
{
|
||||||
setUserBadges([]);
|
setUserBadges(null);
|
||||||
setUserRelationships(null);
|
setUserRelationships(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentUserId = parser.id;
|
||||||
|
|
||||||
SendMessageComposer(new UserCurrentBadgesComposer(parser.id));
|
SendMessageComposer(new UserCurrentBadgesComposer(parser.id));
|
||||||
SendMessageComposer(new UserRelationshipsComposer(parser.id));
|
SendMessageComposer(new UserRelationshipsComposer(parser.id));
|
||||||
});
|
});
|
||||||
@ -73,6 +81,8 @@ export const UserProfileView: FC<{}> = props =>
|
|||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
console.log('UserProfileView: Received ExtendedProfileChangedMessageEvent', { userId: parser.userId, currentUserId });
|
||||||
|
|
||||||
if(parser.userId != userProfile?.id) return;
|
if(parser.userId != userProfile?.id) return;
|
||||||
|
|
||||||
GetUserProfile(parser.userId);
|
GetUserProfile(parser.userId);
|
||||||
@ -91,17 +101,27 @@ export const UserProfileView: FC<{}> = props =>
|
|||||||
GetUserProfile(userData.webID);
|
GetUserProfile(userData.webID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
currentUserId = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
if(!userProfile) return null;
|
if(!userProfile) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NitroCardView uniqueKey="nitro-user-profile" theme="primary-slim" className="user-profile">
|
<NitroCardView uniqueKey="nitro-user-profile" theme="primary-slim" className="user-profile">
|
||||||
<NitroCardHeaderView headerText={ LocalizeText('extendedprofile.caption') } onCloseClick={ onClose } />
|
<NitroCardHeaderView headerText={ LocalizeText('extendedprofile.caption') } onCloseClick={ onClose } />
|
||||||
<NitroCardContentView overflow="hidden">
|
<NitroCardContentView overflow="hidden">
|
||||||
<Grid fullHeight={ false } gap={ 2 }>
|
<Grid fullHeight={ false } gap={ 2 } style={{ minHeight: '200px' }}>
|
||||||
<Column size={ 7 } gap={ 1 } className="user-container pe-2">
|
<Column size={ 7 } gap={ 1 } className="user-container pe-2">
|
||||||
<UserContainerView userProfile={ userProfile } />
|
<UserContainerView userProfile={ userProfile } />
|
||||||
<Grid columnCount={ 5 } fullHeight className="bg-muted rounded px-2 py-1">
|
<Grid fullHeight className="bg-muted rounded px-2 py-1" style={{ minHeight: '70px' }}>
|
||||||
<BadgesContainerView fullWidth center badges={ userBadges } />
|
{ userBadges === null ? (
|
||||||
|
<Text>Loading badges...</Text>
|
||||||
|
) : (
|
||||||
|
<BadgesContainerView key={ userBadges.join('-') } fullWidth center badges={ userBadges } />
|
||||||
|
) }
|
||||||
</Grid>
|
</Grid>
|
||||||
</Column>
|
</Column>
|
||||||
<Column size={ 5 }>
|
<Column size={ 5 }>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Column, FlexProps, LayoutBadgeImageView } from '../../../common';
|
import { Flex, FlexProps, LayoutBadgeImageView } from '../../../common';
|
||||||
|
|
||||||
interface BadgesContainerViewProps extends FlexProps
|
interface BadgesContainerViewProps extends FlexProps
|
||||||
{
|
{
|
||||||
@ -10,16 +10,23 @@ export const BadgesContainerView: FC<BadgesContainerViewProps> = props =>
|
|||||||
{
|
{
|
||||||
const { badges = null, gap = 1, justifyContent = 'between', ...rest } = props;
|
const { badges = null, gap = 1, justifyContent = 'between', ...rest } = props;
|
||||||
|
|
||||||
|
const isGroupBadge = (badgeCode: string): boolean =>
|
||||||
|
{
|
||||||
|
return badgeCode && badgeCode.startsWith('b') && badgeCode.length > 10; // Example heuristic
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex gap={ 2 } alignItems="center" {...rest}>
|
||||||
{ badges && (badges.length > 0) && badges.map((badge, index) =>
|
{ badges && (badges.length > 0) && badges.map((badge, index) =>
|
||||||
{
|
{
|
||||||
|
const isGroup = isGroupBadge(badge);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column key={ badge } center>
|
<div key={ badge } style={{ maxWidth: 45, maxHeight: 45 }}>
|
||||||
<LayoutBadgeImageView key={ badge } badgeCode={ badge } />
|
<LayoutBadgeImageView badgeCode={ badge } isGroup={ isGroup } />
|
||||||
</Column>
|
</div>
|
||||||
);
|
);
|
||||||
}) }
|
}) }
|
||||||
</>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -28,6 +28,7 @@
|
|||||||
"@pixi/canvas-display": "~7.4.3",
|
"@pixi/canvas-display": "~7.4.3",
|
||||||
"@pixi/canvas-extract": "~7.4.3",
|
"@pixi/canvas-extract": "~7.4.3",
|
||||||
"@pixi/canvas-renderer": "~7.4.3",
|
"@pixi/canvas-renderer": "~7.4.3",
|
||||||
|
"@pixi/filter-color-matrix": "~7.4.3",
|
||||||
"@pixi/constants": "~7.4.3",
|
"@pixi/constants": "~7.4.3",
|
||||||
"@pixi/core": "~7.4.3",
|
"@pixi/core": "~7.4.3",
|
||||||
"@pixi/display": "~7.4.3",
|
"@pixi/display": "~7.4.3",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user