mirror of
https://github.com/duckietm/Nitro-Cool-UI.git
synced 2025-06-21 22:36:58 +00:00
⚠️ Beta - 2 All Badges should be fixed now
This commit is contained in:
parent
2269b20f35
commit
975e9430b8
@ -1,6 +1,5 @@
|
|||||||
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';
|
||||||
@ -133,7 +132,6 @@ 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' }>
|
||||||
<BadgeProvider>
|
|
||||||
{ (!isReady || isError) &&
|
{ (!isReady || isError) &&
|
||||||
<LoadingView isError={ isError } message={ message } percent={ percent } /> }
|
<LoadingView isError={ isError } message={ message } percent={ percent } /> }
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@ -144,7 +142,6 @@ export const App: FC<{}> = props =>
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<Base id="draggable-windows-container" />
|
<Base id="draggable-windows-container" />
|
||||||
</BadgeProvider>
|
|
||||||
</Base>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,198 +0,0 @@
|
|||||||
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,8 +1,7 @@
|
|||||||
|
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>
|
||||||
{
|
{
|
||||||
@ -18,20 +17,15 @@ 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(() =>
|
||||||
{
|
{
|
||||||
const newClassNames: string[] = [ 'badge-image' ];
|
const newClassNames: string[] = [ 'badge-image' ];
|
||||||
|
|
||||||
if(isGroup) newClassNames.push('group-badge');
|
if(isGroup) newClassNames.push('group-badge');
|
||||||
|
|
||||||
if(isGrayscale) newClassNames.push('grayscale');
|
if(isGrayscale) newClassNames.push('grayscale');
|
||||||
|
|
||||||
if(classNames.length) newClassNames.push(...classNames);
|
if(classNames.length) newClassNames.push(...classNames);
|
||||||
|
|
||||||
return newClassNames;
|
return newClassNames;
|
||||||
@ -43,14 +37,18 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
|
|
||||||
if(imageElement)
|
if(imageElement)
|
||||||
{
|
{
|
||||||
const badgeUrl = isGroup ? imageElement.src : GetConfiguration<string>('badge.asset.url', '').replace('%badgename%', badgeCode.toString());
|
newStyle.backgroundImage = `url(${ (isGroup) ? imageElement.src : GetConfiguration<string>('badge.asset.url').replace('%badgename%', badgeCode.toString())})`;
|
||||||
|
newStyle.width = imageElement.width;
|
||||||
newStyle.backgroundImage = `url(${ badgeUrl })`;
|
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);
|
||||||
|
newStyle.height = (imageElement.height * scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,111 +59,36 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
console.log('LayoutBadgeImageView: useEffect triggered', { badgeCode, isGroup, retryCount });
|
if(!badgeCode || !badgeCode.length) return;
|
||||||
|
|
||||||
if(!badgeCode || !badgeCode.length)
|
let didSetBadge = false;
|
||||||
{
|
|
||||||
console.warn('LayoutBadgeImageView: Invalid or empty badgeCode', badgeCode);
|
|
||||||
setImageElement(null);
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadBadgeImage = async () =>
|
const onBadgeImageReadyEvent = (event: BadgeImageReadyEvent) =>
|
||||||
{
|
{
|
||||||
console.log('LayoutBadgeImageView: loadBadgeImage started', { badgeCode, retryCount, isGroup });
|
if(event.badgeId !== badgeCode) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
TextureUtils.generateImage(new NitroSprite(event.image)).then(element => {
|
||||||
|
if (element) setImageElement(element);
|
||||||
|
});
|
||||||
|
|
||||||
try
|
didSetBadge = true;
|
||||||
{
|
|
||||||
// Check if badge is already in context
|
|
||||||
const cachedImage = badgeImages.get(badgeCode);
|
|
||||||
if(cachedImage)
|
|
||||||
{
|
|
||||||
setImageElement(cachedImage);
|
|
||||||
setIsLoading(false);
|
|
||||||
console.log('LayoutBadgeImageView: Badge loaded from context', { badgeCode, isGroup });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('LayoutBadgeImageView: Requesting badge via context', { badgeCode, isGroup });
|
GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
|
|
||||||
// Request the badge image via the context
|
|
||||||
const element = await requestBadge(badgeCode, isGroup);
|
|
||||||
|
|
||||||
if(element)
|
|
||||||
{
|
|
||||||
setImageElement(element);
|
|
||||||
console.log('LayoutBadgeImageView: Badge loaded via request', { badgeCode, isGroup });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
console.log('LayoutBadgeImageView: getGroupBadgeImage result', { badgeCode, texture: texture ? 'exists' : 'null', isGroup });
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
console.error('LayoutBadgeImageView: Error loading badge', { error: error.message, badgeCode, isGroup });
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
setIsLoading(false);
|
|
||||||
console.log('LayoutBadgeImageView: loadBadgeImage completed', { badgeCode, isLoading: false, isGroup });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loadBadgeImage();
|
GetSessionDataManager().events.addEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
}, [ badgeCode, isGroup, badgeImages, requestBadge, retryCount, updateBadgeImage ]);
|
|
||||||
|
|
||||||
if(isLoading)
|
const texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode);
|
||||||
|
|
||||||
|
if(texture && !didSetBadge)
|
||||||
{
|
{
|
||||||
console.log('LayoutBadgeImageView: Rendering loading state', { badgeCode, isGroup });
|
TextureUtils.generateImage(new NitroSprite(texture)).then(element => {
|
||||||
return null; // Optionally render a loading placeholder
|
if (element) setImageElement(element);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!imageElement)
|
return () => GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
{
|
}, [ badgeCode, isGroup ]);
|
||||||
console.log('LayoutBadgeImageView: Rendering fallback placeholder', { badgeCode, isGroup });
|
|
||||||
return <Base classNames={ getClassNames } style={ { ...style, backgroundColor: '#d3d3d3' } } { ...rest } />; // Gray square as fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('LayoutBadgeImageView: Rendering badge', { badgeCode, hasImage: !!imageElement, isGroup });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base classNames={ getClassNames } style={ getStyle } { ...rest }>
|
<Base classNames={ getClassNames } style={ getStyle } { ...rest }>
|
||||||
@ -177,4 +100,4 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
{ children }
|
{ children }
|
||||||
</Base>
|
</Base>
|
||||||
);
|
);
|
||||||
};
|
}
|
@ -1,4 +1,3 @@
|
|||||||
export * from './BadgeContext';
|
|
||||||
export * from './LayoutAvatarImageView';
|
export * from './LayoutAvatarImageView';
|
||||||
export * from './LayoutBackgroundImage';
|
export * from './LayoutBackgroundImage';
|
||||||
export * from './LayoutBadgeImageView';
|
export * from './LayoutBadgeImageView';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react';
|
import React, { Dispatch, FC, SetStateAction, useEffect, useState, useMemo } from 'react';
|
||||||
import { FaPlus, FaTimes } from 'react-icons/fa';
|
import { FaPlus, FaTimes } from 'react-icons/fa';
|
||||||
import { GroupBadgePart } from '../../../api';
|
import { GroupBadgePart } from '../../../api';
|
||||||
import { Base, Column, Flex, Grid, LayoutBadgeImageView } from '../../../common';
|
import { Base, Column, Flex, Grid, LayoutBadgeImageView } from '../../../common';
|
||||||
@ -13,12 +13,16 @@ interface GroupBadgeCreatorViewProps
|
|||||||
|
|
||||||
const POSITIONS: number[] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
|
const POSITIONS: number[] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
|
||||||
|
|
||||||
export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
|
// Memoize to prevent unnecessary re-renders
|
||||||
|
export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = React.memo(props =>
|
||||||
{
|
{
|
||||||
const { badgeParts = [], setBadgeParts = null, onBadgeCodeUpdate = null } = props;
|
const { badgeParts = [], setBadgeParts = null, onBadgeCodeUpdate = null } = props;
|
||||||
const [ selectedIndex, setSelectedIndex ] = useState<number>(-1);
|
const [ selectedIndex, setSelectedIndex ] = useState<number>(-1);
|
||||||
const { groupCustomize = null } = useGroup();
|
const { groupCustomize = null } = useGroup();
|
||||||
|
|
||||||
|
// Log re-renders to debug state changes
|
||||||
|
console.log('GroupBadgeCreatorView: Component rendered', { selectedIndex, badgePartsLength: badgeParts.length });
|
||||||
|
|
||||||
const setPartProperty = (partIndex: number, property: string, value: number) =>
|
const setPartProperty = (partIndex: number, property: string, value: number) =>
|
||||||
{
|
{
|
||||||
const newBadgeParts = [ ...badgeParts ];
|
const newBadgeParts = [ ...badgeParts ];
|
||||||
@ -31,7 +35,7 @@ export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to generate a full group badge code with all parts
|
// Helper function to generate a full group badge code with all parts
|
||||||
const getFullBadgeCode = (index: number): string => {
|
const getFullBadgeCode = useMemo(() => {
|
||||||
if (!badgeParts) return '';
|
if (!badgeParts) return '';
|
||||||
|
|
||||||
let badgeCode = '';
|
let badgeCode = '';
|
||||||
@ -46,22 +50,24 @@ export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
|
|||||||
console.log('GroupBadgeCreatorView: Computed full badge code', { badgeCode });
|
console.log('GroupBadgeCreatorView: Computed full badge code', { badgeCode });
|
||||||
|
|
||||||
return badgeCode;
|
return badgeCode;
|
||||||
};
|
}, [badgeParts]);
|
||||||
|
|
||||||
// Debug the contents of badgeBases and badgeSymbols
|
// Debug the contents of badgeBases and badgeSymbols
|
||||||
console.log('GroupBadgeCreatorView: badgeBases', groupCustomize?.badgeBases);
|
console.log('GroupBadgeCreatorView: badgeBases', groupCustomize?.badgeBases);
|
||||||
console.log('GroupBadgeCreatorView: badgeSymbols', groupCustomize?.badgeSymbols);
|
console.log('GroupBadgeCreatorView: badgeSymbols', groupCustomize?.badgeSymbols);
|
||||||
|
|
||||||
// Compute the full badge code
|
|
||||||
const fullBadgeCode = getFullBadgeCode(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('GroupBadgeCreatorView: useEffect triggered', { fullBadgeCode, hasOnBadgeCodeUpdate: !!onBadgeCodeUpdate });
|
console.log('GroupBadgeCreatorView: useEffect triggered', { fullBadgeCode: getFullBadgeCode, hasOnBadgeCodeUpdate: !!onBadgeCodeUpdate });
|
||||||
if (onBadgeCodeUpdate && fullBadgeCode) {
|
if (onBadgeCodeUpdate && getFullBadgeCode) {
|
||||||
console.log('GroupBadgeCreatorView: Propagating badge code to parent', { fullBadgeCode });
|
console.log('GroupBadgeCreatorView: Propagating badge code to parent', { fullBadgeCode: getFullBadgeCode });
|
||||||
onBadgeCodeUpdate(fullBadgeCode);
|
onBadgeCodeUpdate(getFullBadgeCode);
|
||||||
}
|
}
|
||||||
}, [fullBadgeCode, onBadgeCodeUpdate]);
|
}, [getFullBadgeCode, onBadgeCodeUpdate]);
|
||||||
|
|
||||||
|
// Debug selectedIndex changes
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('GroupBadgeCreatorView: selectedIndex changed', { selectedIndex });
|
||||||
|
}, [selectedIndex]);
|
||||||
|
|
||||||
// Early return after all hooks are called
|
// Early return after all hooks are called
|
||||||
if (!badgeParts || !badgeParts.length) return null;
|
if (!badgeParts || !badgeParts.length) return null;
|
||||||
@ -70,14 +76,14 @@ export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
|
|||||||
<>
|
<>
|
||||||
{ ((selectedIndex < 0) && badgeParts && (badgeParts.length > 0)) && badgeParts.map((part, index) =>
|
{ ((selectedIndex < 0) && badgeParts && (badgeParts.length > 0)) && badgeParts.map((part, index) =>
|
||||||
{
|
{
|
||||||
const badgeCode = badgeParts[index].code;
|
const badgeCode = part.code;
|
||||||
console.log('GroupBadgeCreatorView: Rendering badge part', { index, badgeCode, fullBadgeCode, part });
|
console.log('GroupBadgeCreatorView: Rendering badge part', { index, badgeCode, fullBadgeCode: getFullBadgeCode, part });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex key={ index } alignItems="center" justifyContent="between" gap={ 2 } className="bg-muted rounded px-2 py-1">
|
<Flex key={ `part-${index}-${badgeCode || 'empty'}` } alignItems="center" justifyContent="between" gap={ 2 } className="bg-muted rounded px-2 py-1">
|
||||||
<Flex pointer center className="bg-muted rounded p-1" onClick={ event => setSelectedIndex(index) }>
|
<Flex pointer center className="bg-muted rounded p-1" onClick={ event => setSelectedIndex(index) }>
|
||||||
{ (badgeCode && badgeCode.length > 0) ? (
|
{ (badgeCode && badgeCode.length > 0) ? (
|
||||||
<LayoutBadgeImageView badgeCode={ badgeCode } isGroup={ true } />
|
<LayoutBadgeImageView badgeCode={ badgeCode } isGroup={ true } className="visible-badge" />
|
||||||
) : (
|
) : (
|
||||||
<Flex center className="badge-image group-badge">
|
<Flex center className="badge-image group-badge">
|
||||||
<FaPlus className="fa-icon" />
|
<FaPlus className="fa-icon" />
|
||||||
@ -114,12 +120,12 @@ export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
|
|||||||
console.log('GroupBadgeCreatorView: Rendering template badge', { type: badgeParts[selectedIndex].type, id: item.id, badgeCode, color: badgeParts[selectedIndex].color, position: badgeParts[selectedIndex].position, item });
|
console.log('GroupBadgeCreatorView: Rendering template badge', { type: badgeParts[selectedIndex].type, id: item.id, badgeCode, color: badgeParts[selectedIndex].color, position: badgeParts[selectedIndex].position, item });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column key={ index } pointer center className="bg-muted rounded p-1" onClick={ event => setPartProperty(selectedIndex, 'key', item.id) }>
|
<Column key={ `${badgeParts[selectedIndex].type}-${item.id}` } pointer center className="bg-muted rounded p-1 badge-column" onClick={ event => setPartProperty(selectedIndex, 'key', item.id) }>
|
||||||
<LayoutBadgeImageView badgeCode={ badgeCode } isGroup={ true } />
|
<LayoutBadgeImageView badgeCode={ badgeCode } isGroup={ true } className="visible-badge" />
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}) }
|
}) }
|
||||||
</Grid> }
|
</Grid> }
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}, (prevProps, nextProps) => prevProps.badgeParts === nextProps.badgeParts && prevProps.onBadgeCodeUpdate === nextProps.onBadgeCodeUpdate);
|
@ -1,8 +1,8 @@
|
|||||||
import { AbstractRenderer, Renderer, RenderTexture, Resource, Texture } from '@pixi/core';
|
import { Renderer, RenderTexture, Texture, BaseTexture } from '@pixi/core';
|
||||||
import { DisplayObject } from '@pixi/display';
|
import { DisplayObject } from '@pixi/display';
|
||||||
import { Extract } from '@pixi/extract';
|
import { Extract } from '@pixi/extract';
|
||||||
import { Matrix, Rectangle } from '@pixi/math';
|
import { Matrix, Rectangle } from '@pixi/math';
|
||||||
import { settings } from '@pixi/settings';
|
import { SCALE_MODES } from '@pixi/constants';
|
||||||
import { Sprite } from '@pixi/sprite';
|
import { Sprite } from '@pixi/sprite';
|
||||||
import { PixiApplicationProxy } from './PixiApplicationProxy';
|
import { PixiApplicationProxy } from './PixiApplicationProxy';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ export class TextureUtils
|
|||||||
{
|
{
|
||||||
private static _extract: Extract | null = null;
|
private static _extract: Extract | null = null;
|
||||||
|
|
||||||
public static initialize(renderer: Renderer | AbstractRenderer): void
|
public static initialize(renderer: Renderer): void
|
||||||
{
|
{
|
||||||
if (!this._extract && renderer) {
|
if (!this._extract && renderer) {
|
||||||
this._extract = new Extract(renderer);
|
this._extract = new Extract(renderer);
|
||||||
@ -18,48 +18,57 @@ export class TextureUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async generateImage(target: DisplayObject | RenderTexture): Promise<HTMLImageElement>
|
public static async generateImage(target: DisplayObject | RenderTexture): Promise<HTMLImageElement> {
|
||||||
{
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
|
console.warn('generateImage: Invalid target', { target });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractor = this.getExtractor();
|
const extractor = this.getExtractor();
|
||||||
if (!extractor) {
|
if (!extractor) {
|
||||||
|
console.warn('generateImage: Extractor not available');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const image = await extractor.image(target);
|
if (target instanceof DisplayObject) {
|
||||||
|
const renderTexture = this.createRenderTexture(target.width, target.height);
|
||||||
|
this.writeToRenderTexture(target, renderTexture);
|
||||||
|
target = renderTexture;
|
||||||
|
}
|
||||||
|
|
||||||
if (!image || !image.src || typeof image.src !== 'string' || !image.src.startsWith('data:image/')) {
|
const image = await extractor.image(target);
|
||||||
|
console.log('generateImage: Extracted image', { src: image?.src, isValid: image?.src?.startsWith('data:image/') });
|
||||||
|
|
||||||
|
if (!image || !image.src || !image.src.startsWith('data:image/')) {
|
||||||
const canvas = extractor.canvas(target);
|
const canvas = extractor.canvas(target);
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
const dataUrl = canvas.toDataURL('image/png');
|
const dataUrl = canvas.toDataURL('image/png');
|
||||||
|
console.log('generateImage: Fallback canvas', { dataUrl });
|
||||||
if (dataUrl && dataUrl.startsWith('data:image/')) {
|
if (dataUrl && dataUrl.startsWith('data:image/')) {
|
||||||
const fallbackImage = new Image();
|
const fallbackImage = new Image();
|
||||||
fallbackImage.src = dataUrl;
|
fallbackImage.src = dataUrl;
|
||||||
return fallbackImage;
|
return fallbackImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.warn('generateImage: Failed to generate valid image', { target });
|
||||||
const fallback = new Image();
|
const fallback = new Image();
|
||||||
fallback.src = '';
|
fallback.src = '';
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
return image;
|
return image;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('generateImage: Error extracting image', { error: error.message, target });
|
||||||
const fallback = new Image();
|
const fallback = new Image();
|
||||||
fallback.src = '';
|
fallback.src = '';
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static generateTexture(displayObject: DisplayObject, region: Rectangle = null, scaleMode: number = null, resolution: number = 1): RenderTexture
|
public static generateTexture(displayObject: DisplayObject, region: Rectangle = null, scaleMode: SCALE_MODES = SCALE_MODES.LINEAR, resolution: number = 1): RenderTexture
|
||||||
{
|
{
|
||||||
if (!displayObject) return null;
|
if (!displayObject) return null;
|
||||||
|
|
||||||
if (scaleMode === null) scaleMode = settings.SCALE_MODE;
|
|
||||||
|
|
||||||
return this.getRenderer().generateTexture(displayObject, {
|
return this.getRenderer().generateTexture(displayObject, {
|
||||||
scaleMode,
|
scaleMode,
|
||||||
resolution,
|
resolution,
|
||||||
@ -67,7 +76,7 @@ export class TextureUtils
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static generateTextureFromImage(image: HTMLImageElement): Texture<Resource>
|
public static generateTextureFromImage(image: HTMLImageElement): Texture<BaseTexture>
|
||||||
{
|
{
|
||||||
if (!image) return null;
|
if (!image) return null;
|
||||||
|
|
||||||
@ -140,7 +149,7 @@ export class TextureUtils
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createAndFillRenderTexture(width: number, height: number, color: number = 16777215): RenderTexture
|
public static createAndFillRenderTexture(width: number, height: number, color: number = 0xFFFFFF): RenderTexture
|
||||||
{
|
{
|
||||||
if (width < 0 || height < 0) return null;
|
if (width < 0 || height < 0) return null;
|
||||||
|
|
||||||
@ -158,7 +167,7 @@ export class TextureUtils
|
|||||||
return this.writeToRenderTexture(displayObject, renderTexture, true, transform);
|
return this.writeToRenderTexture(displayObject, renderTexture, true, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static clearAndFillRenderTexture(renderTexture: RenderTexture, color: number = 16777215): RenderTexture
|
public static clearAndFillRenderTexture(renderTexture: RenderTexture, color: number = 0xFFFFFF): RenderTexture
|
||||||
{
|
{
|
||||||
if (!renderTexture) return null;
|
if (!renderTexture) return null;
|
||||||
|
|
||||||
@ -194,7 +203,7 @@ export class TextureUtils
|
|||||||
return extractor.pixels(displayObject, frame);
|
return extractor.pixels(displayObject, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getRenderer(): Renderer | AbstractRenderer
|
public static getRenderer(): Renderer
|
||||||
{
|
{
|
||||||
const renderer = PixiApplicationProxy.instance.renderer;
|
const renderer = PixiApplicationProxy.instance.renderer;
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user