mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
🆙 Init V3
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { AvatarDirectionAngle } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { LayoutAvatarImageView } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
|
||||
const DEFAULT_DIRECTION: number = 4;
|
||||
|
||||
export const AvatarEditorFigurePreviewView: FC<{}> = props =>
|
||||
{
|
||||
const [ direction, setDirection ] = useState<number>(DEFAULT_DIRECTION);
|
||||
const { getFigureString = null } = useAvatarEditor();
|
||||
|
||||
const rotateFigure = (newDirection: number) =>
|
||||
{
|
||||
if(direction < AvatarDirectionAngle.MIN_DIRECTION)
|
||||
{
|
||||
newDirection = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1));
|
||||
}
|
||||
|
||||
if(direction > AvatarDirectionAngle.MAX_DIRECTION)
|
||||
{
|
||||
newDirection = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1));
|
||||
}
|
||||
|
||||
setDirection(newDirection);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col figure-preview-container overflow-hidden relative">
|
||||
<LayoutAvatarImageView direction={ direction } figure={ getFigureString } scale={ 2 } />
|
||||
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
|
||||
<div className="avatar-shadow" />
|
||||
<div className="arrow-container">
|
||||
<AvatarEditorIcon icon="arrow-left" onClick={ event => rotateFigure(direction + 1) } />
|
||||
<AvatarEditorIcon icon="arrow-right" onClick={ event => rotateFigure(direction - 1) } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes, PropsWithChildren, forwardRef } from 'react';
|
||||
import { classNames } from '../../layout';
|
||||
|
||||
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable';
|
||||
|
||||
export const AvatarEditorIcon = forwardRef<HTMLDivElement, PropsWithChildren<{
|
||||
icon: AvatarIconType;
|
||||
selected?: boolean;
|
||||
}> & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
|
||||
{
|
||||
const { icon = null, selected = false, className = null, ...rest } = props;
|
||||
|
||||
/*
|
||||
switch (icon)
|
||||
{
|
||||
case 'male':
|
||||
|
||||
|
||||
break;
|
||||
|
||||
case 'arrow-left':
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
//statements;
|
||||
break;
|
||||
|
||||
}
|
||||
*/
|
||||
return (
|
||||
<div
|
||||
ref={ ref }
|
||||
|
||||
className={ classNames(
|
||||
'nitro-avatar-editor-spritesheet',
|
||||
'cursor-pointer',
|
||||
`${ icon }-icon`,
|
||||
selected && 'selected',
|
||||
className
|
||||
) }
|
||||
{ ...rest } />
|
||||
);
|
||||
});
|
||||
|
||||
AvatarEditorIcon.displayName = 'AvatarEditorIcon';
|
||||
@@ -0,0 +1,80 @@
|
||||
import { AvatarEditorFigureCategory, AvatarFigurePartType, FigureDataContainer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { IAvatarEditorCategory } from '../../api';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
import { AvatarEditorFigureSetView } from './figure-set';
|
||||
import { AvatarEditorPaletteSetView } from './palette-set';
|
||||
|
||||
export const AvatarEditorModelView: FC<{
|
||||
name: string,
|
||||
categories: IAvatarEditorCategory[]
|
||||
}> = props =>
|
||||
{
|
||||
const { name = '', categories = [] } = props;
|
||||
const [ didChange, setDidChange ] = useState<boolean>(false);
|
||||
const [ activeSetType, setActiveSetType ] = useState<string>('');
|
||||
const { maxPaletteCount = 1, gender = null, setGender = null, selectedColorParts = null, getFirstSelectableColor = null, selectEditorColor = null } = useAvatarEditor();
|
||||
|
||||
const activeCategory = useMemo(() =>
|
||||
{
|
||||
return categories.find(category => category.setType === activeSetType) ?? null;
|
||||
}, [ categories, activeSetType ]);
|
||||
|
||||
const selectSet = useCallback((setType: string) =>
|
||||
{
|
||||
const selectedPalettes = selectedColorParts[setType];
|
||||
|
||||
if(!selectedPalettes || !selectedPalettes.length) selectEditorColor(setType, 0, getFirstSelectableColor(setType));
|
||||
|
||||
setActiveSetType(setType);
|
||||
}, [ getFirstSelectableColor, selectEditorColor, selectedColorParts ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!categories || !categories.length || !didChange) return;
|
||||
|
||||
selectSet(categories[0]?.setType);
|
||||
setDidChange(false);
|
||||
}, [ categories, didChange, selectSet ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setDidChange(true);
|
||||
}, [ categories ]);
|
||||
|
||||
if(!activeCategory) return null;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-2 overflow-hidden">
|
||||
<div className="flex flex-col col-span-2">
|
||||
{ (name === AvatarEditorFigureCategory.GENERIC) &&
|
||||
<>
|
||||
<div className="category-item items-center justify-center cursor-pointer flex" onClick={ event => setGender(AvatarFigurePartType.MALE) }>
|
||||
<AvatarEditorIcon icon="male" selected={ gender === FigureDataContainer.MALE } />
|
||||
</div>
|
||||
<div className="category-item items-center justify-center cursor-pointer flex" onClick={ event => setGender(AvatarFigurePartType.FEMALE) }>
|
||||
<AvatarEditorIcon icon="female" selected={ gender === FigureDataContainer.FEMALE } />
|
||||
</div>
|
||||
</> }
|
||||
{ (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category =>
|
||||
{
|
||||
return (
|
||||
<div key={ category.setType } className="category-item items-center justify-center cursor-pointer flex" onClick={ event => selectSet(category.setType) }>
|
||||
<AvatarEditorIcon icon={ category.setType } selected={ (activeSetType === category.setType) } />
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
<div className="flex flex-col overflow-hidden col-span-5">
|
||||
<AvatarEditorFigureSetView category={ activeCategory } columnCount={ 3 } />
|
||||
</div>
|
||||
<div className="flex flex-col overflow-hidden col-span-5">
|
||||
{ (maxPaletteCount >= 1) &&
|
||||
<AvatarEditorPaletteSetView category={ activeCategory } columnCount={ 5 } paletteIndex={ 0 } /> }
|
||||
{ (maxPaletteCount === 2) &&
|
||||
<AvatarEditorPaletteSetView category={ activeCategory } columnCount={ 5 } paletteIndex={ 1 } /> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
import { AddLinkEventTracker, AvatarEditorFigureCategory, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker, UserFigureComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaDice, FaRedo, FaTrash } from 'react-icons/fa';
|
||||
import { AvatarEditorAction, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { Button, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorFigurePreviewView } from './AvatarEditorFigurePreviewView';
|
||||
import { AvatarEditorModelView } from './AvatarEditorModelView';
|
||||
import { AvatarEditorWardrobeView } from './AvatarEditorWardrobeView';
|
||||
|
||||
const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007';
|
||||
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
||||
|
||||
export const AvatarEditorView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const { setIsVisible: setEditorVisibility, avatarModels, activeModelKey, setActiveModelKey, loadAvatarData, getFigureStringWithFace, gender, figureSetIds = [], randomizeCurrentFigure = null, getFigureString = null } = useAvatarEditor();
|
||||
|
||||
const processAction = (action: string) =>
|
||||
{
|
||||
switch(action)
|
||||
{
|
||||
case AvatarEditorAction.ACTION_RESET:
|
||||
loadAvatarData(GetSessionDataManager().figure, GetSessionDataManager().gender);
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_CLEAR:
|
||||
loadAvatarData(getFigureStringWithFace(0, false), gender);
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RANDOMIZE:
|
||||
randomizeCurrentFigure();
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_SAVE:
|
||||
SendMessageComposer(new UserFigureComposer(gender, getFigureString));
|
||||
setIsVisible(false);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const linkTracker: ILinkEventTracker = {
|
||||
linkReceived: (url: string) =>
|
||||
{
|
||||
const parts = url.split('/');
|
||||
|
||||
if(parts.length < 2) return;
|
||||
|
||||
switch(parts[1])
|
||||
{
|
||||
case 'show':
|
||||
setIsVisible(true);
|
||||
return;
|
||||
case 'hide':
|
||||
setIsVisible(false);
|
||||
return;
|
||||
case 'toggle':
|
||||
setIsVisible(prevValue => !prevValue);
|
||||
return;
|
||||
}
|
||||
},
|
||||
eventUrlPrefix: 'avatar-editor/'
|
||||
};
|
||||
|
||||
AddLinkEventTracker(linkTracker);
|
||||
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setEditorVisibility(isVisible);
|
||||
}, [ isVisible, setEditorVisibility ]);
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="w-[620px] h-[374px] nitro-avatar-editor" uniqueKey="avatar-editor">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ Object.keys(avatarModels).map(modelKey =>
|
||||
{
|
||||
const isActive = (activeModelKey === modelKey);
|
||||
|
||||
return (
|
||||
<NitroCardTabsItemView key={ modelKey } isActive={ isActive } onClick={ event => setActiveModelKey(modelKey) }>
|
||||
{ LocalizeText(`avatareditor.category.${ modelKey }`) }
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
}) }
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<Grid className="grid gap-2 overflow-hidden">
|
||||
<div className="flex flex-col col-span-9 overflow-hidden">
|
||||
{ ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
|
||||
<AvatarEditorModelView categories={ avatarModels[activeModelKey] } name={ activeModelKey } /> }
|
||||
{ (activeModelKey === AvatarEditorFigureCategory.WARDROBE) &&
|
||||
<AvatarEditorWardrobeView /> }
|
||||
</div>
|
||||
<div className="flex flex-col col-span-3 overflow-hidden gap-1">
|
||||
<AvatarEditorFigurePreviewView />
|
||||
<div className="flex flex-col !flex-grow gap-1">
|
||||
<div className="relative inline-flex align-middle">
|
||||
<Button className="flex-auto " variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
|
||||
<FaRedo className="fa-icon" />
|
||||
</Button>
|
||||
<Button className="flex-auto" variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
|
||||
<FaTrash className="fa-icon" />
|
||||
</Button>
|
||||
<Button className="flex-auto" variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
|
||||
<FaDice className="fa-icon" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button className="w-full" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
|
||||
{ LocalizeText('avatareditor.save') }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { Button, LayoutAvatarImageView, LayoutCurrencyIcon } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { InfiniteGrid } from '../../layout';
|
||||
|
||||
export const AvatarEditorWardrobeView: FC<{}> = props =>
|
||||
{
|
||||
const { savedFigures = [], setSavedFigures = null, loadAvatarData = null, getFigureString = null, gender = null } = useAvatarEditor();
|
||||
|
||||
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
|
||||
|
||||
const wearFigureAtIndex = useCallback((index: number) =>
|
||||
{
|
||||
if((index >= savedFigures.length) || (index < 0)) return;
|
||||
|
||||
const [ figure, gender ] = savedFigures[index];
|
||||
|
||||
loadAvatarData(figure.getFigureString(), gender);
|
||||
}, [ savedFigures, loadAvatarData ]);
|
||||
|
||||
const saveFigureAtWardrobeIndex = useCallback((index: number) =>
|
||||
{
|
||||
if((index >= savedFigures.length) || (index < 0)) return;
|
||||
|
||||
const newFigures = [ ...savedFigures ];
|
||||
|
||||
const figure = getFigureString;
|
||||
|
||||
newFigures[index] = [ GetAvatarRenderManager().createFigureContainer(figure), gender ];
|
||||
|
||||
setSavedFigures(newFigures);
|
||||
SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender));
|
||||
}, [ getFigureString, gender, savedFigures, setSavedFigures ]);
|
||||
|
||||
return (
|
||||
<InfiniteGrid columnCount={ 5 } estimateSize={ 140 } itemRender={ (item: [ IAvatarFigureContainer, string ], index: number) =>
|
||||
{
|
||||
const [ figureContainer, gender ] = item;
|
||||
|
||||
let clubLevel = 0;
|
||||
|
||||
if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
|
||||
|
||||
return (
|
||||
<InfiniteGrid.Item className="nitro-avatar-editor-wardrobe-figure-preview">
|
||||
{ figureContainer &&
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ figureContainer.getFigureString() } gender={ gender } /> }
|
||||
<div className="avatar-shadow" />
|
||||
{ !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="absolute top-1 start-1" type="hc" /> }
|
||||
<div className="flex gap-1 button-container">
|
||||
<Button fullWidth variant="link" onClick={ event => saveFigureAtWardrobeIndex(index) }>{ LocalizeText('avatareditor.wardrobe.save') }</Button>
|
||||
{ figureContainer &&
|
||||
<Button fullWidth disabled={ (clubLevel > GetClubMemberLevel()) } variant="link" onClick={ event => wearFigureAtIndex(index) }>{ LocalizeText('widget.generic_usable.button.use') }</Button> }
|
||||
</div>
|
||||
</InfiniteGrid.Item>
|
||||
);
|
||||
} } items={ savedFigures } overscan={ 5 } />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { AvatarFigurePartType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorThumbnailsHelper, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { InfiniteGrid } from '../../../layout';
|
||||
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||
|
||||
export const AvatarEditorFigureSetItemView: FC<{
|
||||
setType: string;
|
||||
partItem: IAvatarEditorCategoryPartItem;
|
||||
isSelected: boolean;
|
||||
width?: string;
|
||||
} & LayoutGridItemProps> = props =>
|
||||
{
|
||||
const { setType = null, partItem = null, isSelected = false, width = '100%', ...rest } = props;
|
||||
const [ assetUrl, setAssetUrl ] = useState<string>('');
|
||||
const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor();
|
||||
|
||||
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!setType || !setType.length || !partItem) return;
|
||||
|
||||
const loadImage = async () =>
|
||||
{
|
||||
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0);
|
||||
|
||||
let url: string = null;
|
||||
|
||||
if(setType === AvatarFigurePartType.HEAD)
|
||||
{
|
||||
url = await AvatarEditorThumbnailsHelper.buildForFace(getFigureStringWithFace(partItem.id), isHC);
|
||||
}
|
||||
else
|
||||
{
|
||||
url = await AvatarEditorThumbnailsHelper.build(setType, partItem, partItem.usesColor, selectedColorParts[setType] ?? null, isHC);
|
||||
}
|
||||
|
||||
if(url && url.length) setAssetUrl(url);
|
||||
};
|
||||
|
||||
loadImage();
|
||||
}, [ setType, partItem, selectedColorParts, getFigureStringWithFace ]);
|
||||
|
||||
if(!partItem) return null;
|
||||
|
||||
return (
|
||||
<InfiniteGrid.Item itemActive={ isSelected } itemImage={ (partItem.isClear ? undefined : assetUrl) } style={ { flex: '1', backgroundPosition: (setType === AvatarFigurePartType.HEAD) ? 'center -35px' : 'center' } } { ...rest }>
|
||||
{ !partItem.isClear && isHC && <LayoutCurrencyIcon className="absolute end-1 bottom-1" type="hc" /> }
|
||||
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
||||
{ !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon className="end-1 bottom-1 absolute" icon="sellable" /> }
|
||||
</InfiniteGrid.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { FC } from 'react';
|
||||
import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../api';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { InfiniteGrid } from '../../../layout';
|
||||
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
|
||||
|
||||
export const AvatarEditorFigureSetView: FC<{
|
||||
category: IAvatarEditorCategory;
|
||||
columnCount: number;
|
||||
}> = props =>
|
||||
{
|
||||
const { category = null, columnCount = 3 } = props;
|
||||
const { selectedParts = null, selectEditorPart } = useAvatarEditor();
|
||||
|
||||
const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) =>
|
||||
{
|
||||
if(!category || !category.setType || !selectedParts) return false;
|
||||
|
||||
if(!selectedParts[category.setType])
|
||||
{
|
||||
if(partItem.isClear) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const partId = selectedParts[category.setType];
|
||||
|
||||
return (partId === partItem.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<InfiniteGrid<IAvatarEditorCategoryPartItem> columnCount={ columnCount } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
|
||||
{
|
||||
if(!item) return null;
|
||||
|
||||
return (
|
||||
<AvatarEditorFigureSetItemView isSelected={ isPartItemSelected(item) } partItem={ item } setType={ category.setType } width={ `calc(100% / ${ columnCount }` } onClick={ event => selectEditorPart(category.setType, item.partSet?.id ?? -1) } />
|
||||
);
|
||||
} } items={ category.partItems } overscan={ columnCount } />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './AvatarEditorFigureSetItemView';
|
||||
export * from './AvatarEditorFigureSetView';
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './AvatarEditorFigurePreviewView';
|
||||
export * from './AvatarEditorIcon';
|
||||
export * from './AvatarEditorModelView';
|
||||
export * from './AvatarEditorView';
|
||||
export * from './AvatarEditorWardrobeView';
|
||||
export * from './figure-set';
|
||||
export * from './palette-set';
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { GetConfigurationValue } from '../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common';
|
||||
import { InfiniteGrid } from '../../../layout';
|
||||
|
||||
export const AvatarEditorPaletteSetItem: FC<{
|
||||
setType: string;
|
||||
partColor: IPartColor;
|
||||
isSelected: boolean;
|
||||
width?: string;
|
||||
} & LayoutGridItemProps> = props =>
|
||||
{
|
||||
const { setType = null, partColor = null, isSelected = false, width = '100%', ...rest } = props;
|
||||
|
||||
if(!partColor) return null;
|
||||
|
||||
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
|
||||
|
||||
return (
|
||||
<InfiniteGrid.Item itemHighlight className="clear-bg" itemActive={ isSelected } itemColor={ ColorConverter.int2rgb(partColor.rgb) } { ...rest }>
|
||||
{ isHC && <LayoutCurrencyIcon className="absolute end-1 bottom-1" type="hc" /> }
|
||||
</InfiniteGrid.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { IAvatarEditorCategory } from '../../../api';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { InfiniteGrid } from '../../../layout';
|
||||
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||
|
||||
export const AvatarEditorPaletteSetView: FC<{
|
||||
category: IAvatarEditorCategory;
|
||||
paletteIndex: number;
|
||||
columnCount: number;
|
||||
}> = props =>
|
||||
{
|
||||
const { category = null, paletteIndex = -1, columnCount = 3 } = props;
|
||||
const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor();
|
||||
|
||||
const isPartColorSelected = (partColor: IPartColor) =>
|
||||
{
|
||||
if(!category || !category.setType || !selectedColorParts || !selectedColorParts[category.setType] || !selectedColorParts[category.setType][paletteIndex]) return false;
|
||||
|
||||
const selectedColorPart = selectedColorParts[category.setType][paletteIndex];
|
||||
|
||||
return (selectedColorPart.id === partColor.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<InfiniteGrid<IPartColor> columnCount={ columnCount } itemRender={ (item: IPartColor) =>
|
||||
{
|
||||
if(!item) return null;
|
||||
|
||||
return (
|
||||
<AvatarEditorPaletteSetItem isSelected={ isPartColorSelected(item) } partColor={ item } setType={ category.setType } width={ `calc(100% / ${ columnCount }` } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } />
|
||||
);
|
||||
} } items={ category.colorItems[paletteIndex] } overscan={ columnCount } />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './AvatarEditorPaletteSetItemView';
|
||||
export * from './AvatarEditorPaletteSetView';
|
||||
Reference in New Issue
Block a user