🆙 Redone the avatar editor

This commit is contained in:
duckietm
2026-02-26 13:44:22 +01:00
parent 7ab3b24331
commit 10f08c6703
22 changed files with 382 additions and 67 deletions
@@ -0,0 +1,82 @@
import { IPartColor } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useRef } from 'react';
import { ColorUtils, GetClubMemberLevel, IAvatarEditorCategory } from '../../../api';
import { useAvatarEditor } from '../../../hooks';
const findNearestColor = (hex: string, colors: IPartColor[]): IPartColor | null =>
{
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
const maxLevel = GetClubMemberLevel();
let nearest: IPartColor | null = null;
let minDist = Infinity;
for(const color of colors)
{
if(color.clubLevel > maxLevel) continue;
const cr = (color.rgb >> 16) & 0xFF;
const cg = (color.rgb >> 8) & 0xFF;
const cb = color.rgb & 0xFF;
const dist = (r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2;
if(dist < minDist) { minDist = dist; nearest = color; }
}
return nearest;
};
export const AvatarEditorAdvancedColorView: FC<{
category: IAvatarEditorCategory;
paletteIndex: number;
}> = ({ category, paletteIndex }) =>
{
const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor();
const inputRef = useRef<HTMLInputElement>(null);
const selectedColor = useMemo(() =>
{
if(!selectedColorParts?.[category?.setType]?.[paletteIndex]) return null;
return selectedColorParts[category.setType][paletteIndex];
}, [ category, selectedColorParts, paletteIndex ]);
const hexColor = useMemo(() =>
ColorUtils.makeColorNumberHex((selectedColor?.rgb ?? 0) & 0xFFFFFF),
[ selectedColor ]);
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) =>
{
const colors = category?.colorItems?.[paletteIndex];
if(!colors) return;
const nearest = findNearestColor(e.target.value, colors);
if(nearest) selectEditorColor(category.setType, paletteIndex, nearest.id);
}, [ category, paletteIndex, selectEditorColor ]);
return (
<div className="flex h-full p-1.5">
<div
className="flex-1 rounded-lg cursor-pointer relative border-2 border-white/20 overflow-hidden"
style={{ backgroundColor: hexColor }}
onClick={ () => inputRef.current?.click() }
>
<input
ref={ inputRef }
type="color"
value={ hexColor }
onChange={ handleChange }
className="absolute opacity-0 inset-0 w-full h-full cursor-pointer"
/>
<div className="absolute bottom-0 left-0 right-0 py-1 text-center bg-black/40">
<span className="text-xs font-mono font-bold text-white">
{ hexColor.toUpperCase() }
</span>
</div>
</div>
</div>
);
};
@@ -1,6 +1,6 @@
import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { GetConfigurationValue } from '../../../api';
import { GetClubMemberLevel, GetConfigurationValue } from '../../../api';
import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common';
import { InfiniteGrid } from '../../../layout';
@@ -16,9 +16,10 @@ export const AvatarEditorPaletteSetItem: FC<{
if(!partColor) return null;
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
const isLocked = isHC && (GetClubMemberLevel() < partColor.clubLevel);
return (
<InfiniteGrid.Item itemHighlight className="clear-bg" itemActive={ isSelected } itemColor={ ColorConverter.int2rgb(partColor.rgb) } { ...rest }>
<InfiniteGrid.Item itemHighlight className={ `clear-bg aspect-square${ isLocked ? ' opacity-50' : '' }` } itemActive={ isSelected } itemColor={ ColorConverter.int2rgb(partColor.rgb) } { ...rest }>
{ isHC && <LayoutCurrencyIcon className="absolute inset-e-1 bottom-1" type="hc" /> }
</InfiniteGrid.Item>
);
@@ -24,7 +24,7 @@ export const AvatarEditorPaletteSetView: FC<{
};
return (
<InfiniteGrid<IPartColor> columnCount={ columnCount } itemRender={ (item: IPartColor) =>
<InfiniteGrid<IPartColor> columnCount={ columnCount } estimateSize={ 18 } squareItems itemRender={ (item: IPartColor) =>
{
if(!item) return null;
@@ -1,2 +1,3 @@
export * from './AvatarEditorAdvancedColorView';
export * from './AvatarEditorPaletteSetItemView';
export * from './AvatarEditorPaletteSetView';