Avatar editor updates

This commit is contained in:
Bill 2021-07-21 03:09:03 -04:00
parent df167ba1d8
commit 918d2eb6bd
36 changed files with 407 additions and 300 deletions

View File

@ -0,0 +1,7 @@
import { IPalette } from 'nitro-renderer';
import { GetAvatarRenderManager } from './GetAvatarRenderManager';
export function GetAvatarPalette(paletteId: number): IPalette
{
return GetAvatarRenderManager().structureData.getPalette(paletteId);
}

View File

@ -0,0 +1,7 @@
import { ISetType } from 'nitro-renderer';
import { GetAvatarRenderManager } from './GetAvatarRenderManager';
export function GetAvatarSetType(setType: string): ISetType
{
return GetAvatarRenderManager().structureData.getSetType(setType);
}

View File

@ -1 +1,3 @@
export * from './GetAvatarPalette';
export * from './GetAvatarRenderManager'; export * from './GetAvatarRenderManager';
export * from './GetAvatarSetType';

View File

@ -0,0 +1,6 @@
import { GetSessionDataManager } from './GetSessionDataManager';
export function GetClubMemberLevel(): number
{
return GetSessionDataManager().clubLevel;
}

View File

@ -1,6 +1,7 @@
export * from './CanManipulateFurniture'; export * from './CanManipulateFurniture';
export * from './GetCanStandUp'; export * from './GetCanStandUp';
export * from './GetCanUseExpression'; export * from './GetCanUseExpression';
export * from './GetClubMemberLevel';
export * from './GetFurnitureDataForProductOffer'; export * from './GetFurnitureDataForProductOffer';
export * from './GetFurnitureDataForRoomObject'; export * from './GetFurnitureDataForRoomObject';
export * from './GetOwnPosture'; export * from './GetOwnPosture';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 B

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 B

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 308 B

View File

@ -179,15 +179,14 @@
&.clear-icon { &.clear-icon {
background-image: url('../images/avatareditor/clear-icon.png'); background-image: url('../images/avatareditor/clear-icon.png');
width: 16px; width: 27px;
height: 16px; height: 27px;
} }
&.ca-icon { &.ca-icon {
background-image: url('../images/avatareditor/ca-icon.png'); background-image: url('../images/avatareditor/ca-icon.png');
width: 30px; width: 25px;
height: 24px; height: 25px;
background
&.selected { &.selected {
background-image: url('../images/avatareditor/ca-selected-icon.png'); background-image: url('../images/avatareditor/ca-selected-icon.png');
@ -216,8 +215,8 @@
&.cp-icon { &.cp-icon {
background-image: url('../images/avatareditor/cp-icon.png'); background-image: url('../images/avatareditor/cp-icon.png');
width: 25px; width: 30px;
height: 25px; height: 24px;
&.selected { &.selected {
background-image: url('../images/avatareditor/cp-selected-icon.png'); background-image: url('../images/avatareditor/cp-selected-icon.png');

View File

@ -1,6 +1,8 @@
.content-area { .content-area {
padding-top: $container-padding-x; padding-top: $container-padding-x;
padding-bottom: $container-padding-x; padding-bottom: $container-padding-x;
resize: vertical;
overflow: auto;
&.simple { &.simple {
padding-left: ($container-padding-x + 25px); padding-left: ($container-padding-x + 25px);

View File

@ -4,11 +4,11 @@ import { NitroCardGridThemes, NitroCardGridViewProps } from './NitroCardGridView
export const NitroCardGridView: FC<NitroCardGridViewProps> = props => export const NitroCardGridView: FC<NitroCardGridViewProps> = props =>
{ {
const { columns = 5, theme = NitroCardGridThemes.THEME_DEFAULT, children = null } = props; const { columns = 5, theme = NitroCardGridThemes.THEME_DEFAULT, className = '', children = null, ...rest } = props;
return ( return (
<NitroCardGridContextProvider value={ { theme } }> <NitroCardGridContextProvider value={ { theme } }>
<div className={ `h-100 overflow-hidden nitro-card-grid ${ theme }` }> <div className={ `h-100 overflow-hidden nitro-card-grid ${ theme } ${ className || '' }` } { ...rest }>
<div className={ `row row-cols-${ columns } align-content-start g-0 w-100 h-100 overflow-auto` }> <div className={ `row row-cols-${ columns } align-content-start g-0 w-100 h-100 overflow-auto` }>
{ children } { children }
</div> </div>

View File

@ -1,4 +1,6 @@
export interface NitroCardGridViewProps import { DetailsHTMLAttributes } from 'react';
export interface NitroCardGridViewProps extends DetailsHTMLAttributes<HTMLDivElement>
{ {
columns?: number; columns?: number;
theme?: string; theme?: string;

View File

@ -3,6 +3,9 @@
max-height: 50px; max-height: 50px;
.grid-item { .grid-item {
display: flex;
justify-content: center;
align-items: center;
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -1,9 +1,13 @@
.nitro-avatar-editor { .nitro-avatar-editor {
width: 600px; width: 620px;
.content-area { .content-area {
height: 330px; min-height: 300px;
max-height: 330px; height: 300px;
}
.category-item {
height: 40px;
} }
.figure-preview-container { .figure-preview-container {
@ -13,27 +17,53 @@
overflow: hidden; overflow: hidden;
z-index: 1; z-index: 1;
.avatar-image {
margin: 45px auto 0;
z-index: 2;
}
.arrow-container { .arrow-container {
position: absolute; position: absolute;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 0 10px;
display: flex; display: flex;
justify-content: center; justify-content: space-between;
bottom: 12px; bottom: 12px;
z-index: 3; z-index: 5;
.icon { .icon {
cursor: pointer; cursor: pointer;
} }
.arrow-left-icon {
margin-right: 10px;
} }
.avatar-image {
position: absolute;
left: 0;
right: 0;
bottom: 50px;
margin: 0 auto;
z-index: 4;
}
.avatar-spotlight {
position: absolute;
top: -10px;
width: 100%;
height: 305px;
margin: 0 auto;
background: transparent url('../../assets/images/avatareditor/spotlight.png') no-repeat center;
opacity: 0.3;
pointer-events: none;
z-index: 3;
}
.avatar-shadow {
position: absolute;
left: 0;
right: 0;
bottom: 15px;
width: 70px;
height: 30px;
margin: 0 auto;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.20);
z-index: 2;
} }
&:after { &:after {
@ -44,7 +74,8 @@
left: 0; left: 0;
right: 0; right: 0;
border-radius: 50%; border-radius: 50%;
background-color: red; background-color: $pale-sky;
box-shadow: 0 0 8px 2px rgba($white,.6);
transform: scale(2); transform: scale(2);
} }
} }

View File

@ -1,103 +1,91 @@
import { AvatarEditorFigureCategory } from 'nitro-renderer'; import { AvatarDirectionAngle, AvatarEditorFigureCategory, UserFigureComposer } from 'nitro-renderer';
import { FC, useCallback, useEffect, useReducer, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { GetSessionDataManager } from '../../api'; import { GetSessionDataManager } from '../../api';
import { AvatarEditorEvent } from '../../events/avatar-editor'; import { AvatarEditorEvent } from '../../events/avatar-editor';
import { SendMessageHook } from '../../hooks';
import { useUiEvent } from '../../hooks/events/ui/ui-event'; import { useUiEvent } from '../../hooks/events/ui/ui-event';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout';
import { LocalizeText } from '../../utils/LocalizeText'; import { LocalizeText } from '../../utils/LocalizeText';
import { AvatarEditorViewProps } from './AvatarEditorView.types'; import { AvatarEditorViewProps } from './AvatarEditorView.types';
import { AvatarEditor } from './common/AvatarEditor'; import { AvatarEditorUtilities } from './common/AvatarEditorUtilities';
import { BodyModel } from './common/BodyModel'; import { BodyModel } from './common/BodyModel';
import { FigureData } from './common/FigureData'; import { FigureData } from './common/FigureData';
import { HeadModel } from './common/HeadModel'; import { HeadModel } from './common/HeadModel';
import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel'; import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel';
import { LegModel } from './common/LegModel'; import { LegModel } from './common/LegModel';
import { TorsoModel } from './common/TorsoModel'; import { TorsoModel } from './common/TorsoModel';
import { AvatarEditorContextProvider } from './context/AvatarEditorContext'; import { AvatarEditorFigurePreviewView } from './views/figure-preview/AvatarEditorFigurePreviewView';
import { AvatarEditorReducer, initialAvatarEditor } from './reducers/AvatarEditorReducer';
import { AvatarEditorModelView } from './views/model/AvatarEditorModelView'; import { AvatarEditorModelView } from './views/model/AvatarEditorModelView';
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<AvatarEditorViewProps> = props => export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
{ {
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
const [ avatarEditorState, dispatchAvatarEditorState ] = useReducer(AvatarEditorReducer, initialAvatarEditor); const [ figures, setFigures ] = useState<Map<string, FigureData>>(null);
const [ avatarEditor, setAvatarEditor ] = useState<AvatarEditor>(null); const [ figureData, setFigureData ] = useState<FigureData>(null);
const [ categories, setCategories ] = useState<Map<string, IAvatarEditorCategoryModel>>(null); const [ categories, setCategories ] = useState<Map<string, IAvatarEditorCategoryModel>>(null);
const [ activeCategory, setActiveCategory ] = useState<IAvatarEditorCategoryModel>(null); const [ activeCategory, setActiveCategory ] = useState<IAvatarEditorCategoryModel>(null);
const [ lastFigure, setLastFigure ] = useState<string>(null);
const [ lastGender, setLastGender ] = useState<string>(null);
const [ needsReset, setNeedsReset ] = useState(false);
const [ isInitalized, setIsInitalized ] = useState(false); const [ isInitalized, setIsInitalized ] = useState(false);
const selectCategory = useCallback((name: string) => const selectCategory = useCallback((name: string) =>
{ {
if(!categories) return;
setActiveCategory(categories.get(name)); setActiveCategory(categories.get(name));
}, [ categories ]); }, [ categories ]);
const resetCategories = useCallback((editor: AvatarEditor) => const resetCategories = useCallback(() =>
{ {
const categories = new Map(); const categories = new Map();
categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel(editor)); categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel());
categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel(editor)); categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel());
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel(editor)); categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel(editor)); categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
setCategories(categories); setCategories(categories);
setActiveCategory(categories.get(AvatarEditorFigureCategory.GENERIC));
}, []); }, []);
const selectGender = useCallback((gender: string) => const setupFigures = useCallback(() =>
{ {
if(gender === avatarEditor.gender) return; const figures: Map<string, FigureData> = new Map();
avatarEditor.gender = gender; const maleFigure = new FigureData();
const femaleFigure = new FigureData();
resetCategories(avatarEditor); maleFigure.loadAvatarData(DEFAULT_MALE_FIGURE, FigureData.MALE);
}, [ avatarEditor, resetCategories ]); femaleFigure.loadAvatarData(DEFAULT_FEMALE_FIGURE, FigureData.FEMALE);
figures.set(FigureData.MALE, maleFigure);
figures.set(FigureData.FEMALE, femaleFigure);
setFigures(figures);
setFigureData(figures.get(FigureData.MALE));
}, []);
const loadAvatarInEditor = useCallback((figure: string, gender: string, reset: boolean = true) => const loadAvatarInEditor = useCallback((figure: string, gender: string, reset: boolean = true) =>
{ {
if(!avatarEditor) return; gender = AvatarEditorUtilities.getGender(gender);
switch(gender) let newFigureData = figureData;
if(gender !== newFigureData.gender) newFigureData = figures.get(gender);
if(figure !== newFigureData.getFigureString()) newFigureData.loadAvatarData(figure, gender);
if(newFigureData !== figureData) setFigureData(newFigureData);
if(reset)
{ {
case FigureData.MALE: setLastFigure(figureData.getFigureString());
case 'm': setLastGender(figureData.gender);
case 'M':
gender = FigureData.MALE;
break;
case FigureData.FEMALE:
case 'f':
case 'F':
gender = FigureData.FEMALE;
break;
default:
gender = FigureData.MALE;
} }
}, [ figures, figureData ]);
let update = false;
if(gender !== avatarEditor.gender)
{
avatarEditor.gender = gender;
update = true;
}
const figureData = avatarEditor.figureData;
if(!figureData) return;
if(figure !== figureData.getFigureString())
{
update = true;
}
figureData.loadAvatarData(figure, gender);
if(update)
{
resetCategories(avatarEditor);
}
}, [ avatarEditor, resetCategories ]);
const onAvatarEditorEvent = useCallback((event: AvatarEditorEvent) => const onAvatarEditorEvent = useCallback((event: AvatarEditorEvent) =>
{ {
@ -105,12 +93,20 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
{ {
case AvatarEditorEvent.SHOW_EDITOR: case AvatarEditorEvent.SHOW_EDITOR:
setIsVisible(true); setIsVisible(true);
setNeedsReset(true);
return; return;
case AvatarEditorEvent.HIDE_EDITOR: case AvatarEditorEvent.HIDE_EDITOR:
setIsVisible(false); setIsVisible(false);
return; return;
case AvatarEditorEvent.TOGGLE_EDITOR: case AvatarEditorEvent.TOGGLE_EDITOR:
setIsVisible(prevValue => !prevValue); setIsVisible(prevValue =>
{
const flag = !prevValue;
if(flag) setNeedsReset(true);
return flag;
});
return; return;
} }
}, []); }, []);
@ -119,30 +115,92 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
useUiEvent(AvatarEditorEvent.HIDE_EDITOR, onAvatarEditorEvent); useUiEvent(AvatarEditorEvent.HIDE_EDITOR, onAvatarEditorEvent);
useUiEvent(AvatarEditorEvent.TOGGLE_EDITOR, onAvatarEditorEvent); useUiEvent(AvatarEditorEvent.TOGGLE_EDITOR, onAvatarEditorEvent);
const clearFigure = useCallback(() =>
{
loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false);
resetCategories();
}, [ figureData, loadAvatarInEditor, resetCategories ]);
const resetFigure = useCallback(() =>
{
loadAvatarInEditor(lastFigure, lastGender);
resetCategories();
}, [ lastFigure, lastGender, loadAvatarInEditor, resetCategories ]);
const rotateFigure = useCallback((direction: number) =>
{
if(direction < AvatarDirectionAngle.MIN_DIRECTION)
{
direction = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1));
}
if(direction > AvatarDirectionAngle.MAX_DIRECTION)
{
direction = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1));
}
figureData.direction = direction;
}, [ figureData ]);
const saveFigure = useCallback(() =>
{
SendMessageHook(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
}, [ figureData ]);
const setGender = useCallback((gender: string) =>
{
gender = AvatarEditorUtilities.getGender(gender);
setFigureData(figures.get(gender));
}, [ figures ]);
useEffect(() => useEffect(() =>
{ {
if(!isVisible || isInitalized) return; if(!categories) return;
const newEditor = new AvatarEditor(); selectCategory(AvatarEditorFigureCategory.GENERIC);
}, [ categories, selectCategory ]);
useEffect(() =>
{
if(!figureData) return;
AvatarEditorUtilities.CURRENT_FIGURE = figureData;
resetCategories();
return () => AvatarEditorUtilities.CURRENT_FIGURE = null;
}, [ figureData, resetCategories ]);
useEffect(() =>
{
if(!isVisible) return;
if(!figures)
{
setupFigures();
setAvatarEditor(newEditor);
setIsInitalized(true); setIsInitalized(true);
}, [ isVisible, isInitalized ]);
return;
}
}, [ isVisible, figures, setupFigures ]);
useEffect(() => useEffect(() =>
{ {
if(!isVisible || !avatarEditor) return; if(!isVisible || !isInitalized || !needsReset) return;
loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender); loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender);
}, [ isVisible, avatarEditor, loadAvatarInEditor ]); setNeedsReset(false);
}, [ isVisible, isInitalized, needsReset, loadAvatarInEditor ]);
if(!isVisible) return null;
return ( return (
<AvatarEditorContextProvider value={ { avatarEditorState, dispatchAvatarEditorState } }>
{ isVisible &&
<NitroCardView className="nitro-avatar-editor"> <NitroCardView className="nitro-avatar-editor">
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } /> <NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardTabsView> <NitroCardTabsView>
{ categories && (categories.size > 0) && Array.from(categories.keys()).map(category => { categories && (categories.size > 0) && activeCategory && Array.from(categories.keys()).map(category =>
{ {
return ( return (
<NitroCardTabsItemView key={ category } isActive={ (activeCategory.name === category) } onClick={ event => selectCategory(category) }> <NitroCardTabsItemView key={ category } isActive={ (activeCategory.name === category) } onClick={ event => selectCategory(category) }>
@ -152,9 +210,35 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
})} })}
</NitroCardTabsView> </NitroCardTabsView>
<NitroCardContentView> <NitroCardContentView>
{ activeCategory && <AvatarEditorModelView model={ activeCategory } editor={ avatarEditor } selectGender={ selectGender } /> } <div className="row h-100">
<div className="col-9 d-flex flex-column h-100">
{ activeCategory && <AvatarEditorModelView model={ activeCategory } gender={ figureData.gender } setGender={ setGender } /> }
</div>
<div className="col-3 d-flex flex-column h-100">
{ figureData &&
<div className="figure-preview-container">
<AvatarEditorFigurePreviewView figureData={ figureData } />
<div className="avatar-spotlight" />
<div className="avatar-shadow" />
<div className="arrow-container">
<i className="icon arrow-left-icon" onClick={ event => rotateFigure(figureData.direction + 1) } />
<i className="icon arrow-right-icon" onClick={ event => rotateFigure(figureData.direction - 1) } />
</div>
</div> }
<div className="d-flex flex-column mt-1">
<div className="btn-group mb-1">
<button type="button" className="btn btn-sm btn-secondary" onClick={ resetFigure }>
<i className="fas fa-undo" />
</button>
<button type="button" className="btn btn-sm btn-secondary" onClick={ clearFigure }>
<i className="fas fa-trash" />
</button>
</div>
<button type="button" className="btn btn-success btn-sm w-100" onClick={ saveFigure }>{ LocalizeText('avatareditor.save') }</button>
</div>
</div>
</div>
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> } </NitroCardView>
</AvatarEditorContextProvider>
); );
} }

View File

@ -1,88 +1,79 @@
import { IPalette, IPartColor, ISetType, IStructureData } from 'nitro-renderer'; import { IPartColor } from 'nitro-renderer';
import { GetAvatarRenderManager, GetConfiguration, GetSessionDataManager } from '../../../api'; import { GetAvatarPalette, GetAvatarRenderManager, GetAvatarSetType, GetClubMemberLevel, GetConfiguration } from '../../../api';
import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem'; import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem';
import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem'; import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem';
import { CategoryBaseModel } from './CategoryBaseModel'; import { CategoryBaseModel } from './CategoryBaseModel';
import { CategoryData } from './CategoryData'; import { CategoryData } from './CategoryData';
import { FigureData } from './FigureData'; import { FigureData } from './FigureData';
const MAX_PALETTES: number = 2; export class AvatarEditorUtilities
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 class AvatarEditor
{ {
private _figureStructureData: IStructureData = GetAvatarRenderManager().structureData; private static MAX_PALETTES: number = 2;
private _figures: Map<string, FigureData> = new Map();
private _gender: string = FigureData.MALE;
private _notifier: () => void = null;
constructor() public static CURRENT_FIGURE: FigureData = null;
public static getGender(gender: string): string
{ {
const maleFigure = new FigureData(); switch(gender)
const femaleFigure = new FigureData(); {
case FigureData.MALE:
maleFigure.loadAvatarData(DEFAULT_MALE_FIGURE, FigureData.MALE); case 'm':
femaleFigure.loadAvatarData(DEFAULT_FEMALE_FIGURE, FigureData.FEMALE); case 'M':
gender = FigureData.MALE;
this._figures.set(FigureData.MALE, maleFigure); break;
this._figures.set(FigureData.FEMALE, femaleFigure); case FigureData.FEMALE:
case 'f':
case 'F':
gender = FigureData.FEMALE;
break;
default:
gender = FigureData.MALE;
} }
public getSetType(setType: string): ISetType return gender;
{
if(!this._figureStructureData) return null;
return this._figureStructureData.getSetType(setType);
} }
public getPalette(paletteId: number): IPalette public static createCategory(model: CategoryBaseModel, name: string): CategoryData
{ {
if(!this._figureStructureData) return null; if(!model || !name || !this.CURRENT_FIGURE) return null;
return this._figureStructureData.getPalette(paletteId);
}
public createCategory(model: CategoryBaseModel, name: string): CategoryData
{
if(!model || !name) return null;
const partItems: AvatarEditorGridPartItem[] = []; const partItems: AvatarEditorGridPartItem[] = [];
const colorItems: AvatarEditorGridColorItem[][] = []; const colorItems: AvatarEditorGridColorItem[][] = [];
let i = 0; let i = 0;
while(i < MAX_PALETTES) while(i < this.MAX_PALETTES)
{ {
colorItems.push([]); colorItems.push([]);
i++; i++;
} }
const setType = this.getSetType(name); const setType = GetAvatarSetType(name);
if(!setType) return null; if(!setType) return null;
const palette = this.getPalette(setType.paletteID); const palette = GetAvatarPalette(setType.paletteID);
if(!palette) return null; if(!palette) return null;
let colorIds = this.figureData.getColourIds(name); let colorIds = this.CURRENT_FIGURE.getColorIds(name);
if(!colorIds) colorIds = []; if(!colorIds) colorIds = [];
const partColors: IPartColor[] = new Array(colorIds.length); const partColors: IPartColor[] = new Array(colorIds.length);
const clubItemsDimmed = this.clubItemsDimmed; const clubItemsDimmed = this.clubItemsDimmed;
const clubMemberLevel = GetClubMemberLevel();
for(const partColor of palette.colors.values()) for(const partColor of palette.colors.values())
{ {
if(partColor.isSelectable && (clubItemsDimmed || (this.clubMemberLevel >= partColor.clubLevel))) if(partColor.isSelectable && (clubItemsDimmed || (clubMemberLevel >= partColor.clubLevel)))
{ {
let i = 0; let i = 0;
while(i < MAX_PALETTES) while(i < this.MAX_PALETTES)
{ {
const isDisabled = (this.clubMemberLevel < partColor.clubLevel); const isDisabled = (clubMemberLevel < partColor.clubLevel);
const colorItem = new AvatarEditorGridColorItem(partColor, isDisabled); const colorItem = new AvatarEditorGridColorItem(partColor, isDisabled);
colorItems[i].push(colorItem); colorItems[i].push(colorItem);
@ -108,11 +99,11 @@ export class AvatarEditor
if(clubItemsDimmed) if(clubItemsDimmed)
{ {
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this._gender, 2); mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, 2);
} }
else else
{ {
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this._gender, this.clubMemberLevel); mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, clubMemberLevel);
} }
const isntMandatorySet = (mandatorySetIds.indexOf(name) === -1); const isntMandatorySet = (mandatorySetIds.indexOf(name) === -1);
@ -142,17 +133,15 @@ export class AvatarEditor
{ {
isValidGender = true; isValidGender = true;
} }
else
{ else if(partSet.gender === this.CURRENT_FIGURE.gender)
if(partSet.gender === this._gender)
{ {
isValidGender = true; isValidGender = true;
} }
}
if(partSet.isSelectable && isValidGender && (clubItemsDimmed || (this.clubMemberLevel >= partSet.clubLevel))) if(partSet.isSelectable && isValidGender && (clubItemsDimmed || (clubMemberLevel >= partSet.clubLevel)))
{ {
const isDisabled = (this.clubMemberLevel < partSet.clubLevel); const isDisabled = (clubMemberLevel < partSet.clubLevel);
let isValid = true; let isValid = true;
@ -186,7 +175,7 @@ export class AvatarEditor
i = 0; i = 0;
while(i < MAX_PALETTES) while(i < this.MAX_PALETTES)
{ {
colorItems[i].sort(this.colorSorter); colorItems[i].sort(this.colorSorter);
@ -196,7 +185,7 @@ export class AvatarEditor
return new CategoryData(name, partItems, colorItems); return new CategoryData(name, partItems, colorItems);
} }
private clubSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number public static clubSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number
{ {
const clubLevelA = (!a.partSet ? 9999999999 : a.partSet.clubLevel); const clubLevelA = (!a.partSet ? 9999999999 : a.partSet.clubLevel);
const clubLevelB = (!b.partSet ? 9999999999 : b.partSet.clubLevel); const clubLevelB = (!b.partSet ? 9999999999 : b.partSet.clubLevel);
@ -218,7 +207,23 @@ export class AvatarEditor
return 0; return 0;
} }
private noobSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number public static colorSorter(a: AvatarEditorGridColorItem, b: AvatarEditorGridColorItem): number
{
const clubLevelA = (!a.partColor ? -1 : a.partColor.clubLevel);
const clubLevelB = (!b.partColor ? -1 : b.partColor.clubLevel);
if(clubLevelA < clubLevelB) return -1;
if(clubLevelA > clubLevelB) return 1;
if(a.partColor.index < b.partColor.index) return -1;
if(a.partColor.index > b.partColor.index) return 1;
return 0;
}
public static noobSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number
{ {
const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel); const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel);
const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel); const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel);
@ -240,67 +245,33 @@ export class AvatarEditor
return 0; return 0;
} }
private colorSorter(a: AvatarEditorGridColorItem, b: AvatarEditorGridColorItem): number public static avatarSetFirstSelectableColor(name: string): number
{ {
const clubLevelA = (!a.partColor ? -1 : a.partColor.clubLevel); const setType = GetAvatarSetType(name);
const clubLevelB = (!b.partColor ? -1 : b.partColor.clubLevel);
if(clubLevelA < clubLevelB) return -1; if(!setType) return -1;
if(clubLevelA > clubLevelB) return 1; const palette = GetAvatarPalette(setType.paletteID);
if(a.partColor.index < b.partColor.index) return -1; if(!palette) return -1;
if(a.partColor.index > b.partColor.index) return 1; for(const color of palette.colors.values())
{
if(!color.isSelectable || (GetClubMemberLevel() < color.clubLevel)) continue;
return 0; return color.id;
} }
public get clubMemberLevel(): number return -1;
{
return GetSessionDataManager().clubLevel;
} }
private get clubItemsFirst(): boolean public static get clubItemsFirst(): boolean
{ {
return GetConfiguration<boolean>('avatareditor.show.clubitems.first', true); return GetConfiguration<boolean>('avatareditor.show.clubitems.first', true);
} }
private get clubItemsDimmed(): boolean public static get clubItemsDimmed(): boolean
{ {
return GetConfiguration<boolean>('avatareditor.show.clubitems.dimmed', true); return GetConfiguration<boolean>('avatareditor.show.clubitems.dimmed', true);
} }
public get figureData(): FigureData
{
return this._figures.get(this._gender);
}
public get gender(): string
{
return this._gender;
}
public set gender(gender: string)
{
if(this._gender === gender) return;
this._gender = gender;
if(this.figureData) this.figureData.notify = this.notify;
if(this.notify) this.notify();
}
public get notify(): () => void
{
return this._notifier;
}
public set notify(notifier: () => void)
{
if(this.figureData) this.figureData.notify = notifier;
this._notifier = notifier;
}
} }

View File

@ -1,5 +1,6 @@
import { AvatarEditorFigureCategory, AvatarScaleType, AvatarSetType, IAvatarImageListener } from 'nitro-renderer'; import { AvatarEditorFigureCategory, AvatarScaleType, AvatarSetType, IAvatarImageListener } from 'nitro-renderer';
import { GetAvatarRenderManager } from '../../../api'; import { GetAvatarRenderManager } from '../../../api';
import { AvatarEditorUtilities } from './AvatarEditorUtilities';
import { CategoryBaseModel } from './CategoryBaseModel'; import { CategoryBaseModel } from './CategoryBaseModel';
import { FigureData } from './FigureData'; import { FigureData } from './FigureData';
@ -25,15 +26,15 @@ export class BodyModel extends CategoryBaseModel implements IAvatarImageListener
protected updateSelectionsFromFigure(name: string): void protected updateSelectionsFromFigure(name: string): void
{ {
if(!this._categories || !this._editor || !this._editor.figureData) return; if(!this._categories || !AvatarEditorUtilities.CURRENT_FIGURE) return;
const category = this._categories.get(name); const category = this._categories.get(name);
if(!category) return; if(!category) return;
const setId = this._editor.figureData.getPartSetId(name); const setId = AvatarEditorUtilities.CURRENT_FIGURE.getPartSetId(name);
let colorIds = this._editor.figureData.getColourIds(name); let colorIds = AvatarEditorUtilities.CURRENT_FIGURE.getColorIds(name);
if(!colorIds) colorIds = []; if(!colorIds) colorIds = [];
@ -42,7 +43,7 @@ export class BodyModel extends CategoryBaseModel implements IAvatarImageListener
for(const part of category.parts) for(const part of category.parts)
{ {
const figure = this._editor.figureData.getFigureStringWithFace(part.id); const figure = AvatarEditorUtilities.CURRENT_FIGURE.getFigureStringWithFace(part.id);
const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, this); const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, this);
const sprite = avatarImage.getImageAsSprite(AvatarSetType.HEAD); const sprite = avatarImage.getImageAsSprite(AvatarSetType.HEAD);

View File

@ -1,18 +1,16 @@
import { AvatarEditor } from './AvatarEditor'; import { AvatarEditorUtilities } from './AvatarEditorUtilities';
import { CategoryData } from './CategoryData'; import { CategoryData } from './CategoryData';
import { IAvatarEditorCategoryModel } from './IAvatarEditorCategoryModel'; import { IAvatarEditorCategoryModel } from './IAvatarEditorCategoryModel';
export class CategoryBaseModel implements IAvatarEditorCategoryModel export class CategoryBaseModel implements IAvatarEditorCategoryModel
{ {
protected _editor: AvatarEditor;
protected _categories: Map<string, CategoryData>; protected _categories: Map<string, CategoryData>;
protected _isInitalized: boolean; protected _isInitalized: boolean;
protected _maxPaletteCount: number; protected _maxPaletteCount: number;
private _disposed: boolean; private _disposed: boolean;
constructor(editor: AvatarEditor) constructor()
{ {
this._editor = editor;
this._isInitalized = false; this._isInitalized = false;
this._maxPaletteCount = 0; this._maxPaletteCount = 0;
} }
@ -51,7 +49,7 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
if(existing) return; if(existing) return;
existing = this._editor.createCategory(this, name); existing = AvatarEditorUtilities.createCategory(this, name);
if(!existing) return; if(!existing) return;
@ -66,9 +64,9 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
if(!category) return; if(!category) return;
const setId = this._editor.figureData.getPartSetId(figure); const setId = AvatarEditorUtilities.CURRENT_FIGURE.getPartSetId(figure);
let colorIds = this._editor.figureData.getColourIds(figure); let colorIds = AvatarEditorUtilities.CURRENT_FIGURE.getColorIds(figure);
if(!colorIds) colorIds = []; if(!colorIds) colorIds = [];
@ -120,9 +118,9 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
{ {
const partItem = category.getCurrentPart(); const partItem = category.getCurrentPart();
if(partItem && this._editor && this._editor.figureData) if(partItem && AvatarEditorUtilities.CURRENT_FIGURE)
{ {
this._editor.figureData.savePartData(name, partItem.id, category.getSelectedColorIds(), true); AvatarEditorUtilities.CURRENT_FIGURE.savePartData(name, partItem.id, category.getSelectedColorIds(), true);
} }
didStrip = true; didStrip = true;
@ -148,9 +146,9 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
{ {
const partItem = category.getCurrentPart(); const partItem = category.getCurrentPart();
if(partItem && this._editor && this._editor.figureData) if(partItem && AvatarEditorUtilities.CURRENT_FIGURE)
{ {
this._editor.figureData.savePartData(name, partItem.id, category.getSelectedColorIds(), true); AvatarEditorUtilities.CURRENT_FIGURE.savePartData(name, partItem.id, category.getSelectedColorIds(), true);
} }
didStrip = true; didStrip = true;
@ -185,7 +183,7 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
this._maxPaletteCount = partItem.maxColorIndex; this._maxPaletteCount = partItem.maxColorIndex;
this._editor.figureData.savePartData(category, partItem.id, categoryData.getSelectedColorIds(), true); AvatarEditorUtilities.CURRENT_FIGURE.savePartData(category, partItem.id, categoryData.getSelectedColorIds(), true);
} }
public selectColor(category: string, colorIndex: number, paletteId: number): void public selectColor(category: string, colorIndex: number, paletteId: number): void
@ -209,7 +207,7 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
return; return;
} }
this._editor.figureData.savePartSetColourId(category, categoryData.getSelectedColorIds(), true); AvatarEditorUtilities.CURRENT_FIGURE.savePartSetColourId(category, categoryData.getSelectedColorIds(), true);
} }
public getCategoryData(category: string): CategoryData public getCategoryData(category: string): CategoryData

View File

@ -1,5 +1,9 @@
export class FigureData import { AvatarEditorUtilities } from './AvatarEditorUtilities';
export class FigureData
{ {
private static DEFAULT_DIRECTION: number = 4;
public static MALE: string = 'M'; public static MALE: string = 'M';
public static FEMALE: string = 'F'; public static FEMALE: string = 'F';
public static UNISEX: string = 'U'; public static UNISEX: string = 'U';
@ -24,6 +28,7 @@
private _data: Map<string, number>; private _data: Map<string, number>;
private _colors: Map<string, number[]>; private _colors: Map<string, number[]>;
private _gender: string = 'M'; private _gender: string = 'M';
private _direction: number = FigureData.DEFAULT_DIRECTION;
private _avatarEffectType: number = -1; private _avatarEffectType: number = -1;
private _notifier: () => void = null; private _notifier: () => void = null;
@ -80,14 +85,13 @@
return -1; return -1;
} }
public getColourIds(colorId: string): number[] public getColorIds(setType: string): number[]
{ {
const existing = this._colors.get(colorId); const existing = this._colors.get(setType);
if(existing !== undefined) return existing; if(existing !== undefined) return existing;
return []; return [ AvatarEditorUtilities.avatarSetFirstSelectableColor(setType) ];
// return [this._avatarEditor._Str_24919(k)];
} }
public getFigureString(): string public getFigureString(): string
@ -167,9 +171,9 @@
if(_arg_3) this.updateView(); if(_arg_3) this.updateView();
} }
public savePartSetColourId(k: string, _arg_2: number[], _arg_3: boolean = true): void public savePartSetColourId(setType: string, colorIds: number[], update: boolean = true): void
{ {
switch(k) switch(setType)
{ {
case FigureData.FACE: case FigureData.FACE:
case FigureData.HAIR: case FigureData.HAIR:
@ -184,11 +188,11 @@
case FigureData.TROUSERS: case FigureData.TROUSERS:
case FigureData.SHOES: case FigureData.SHOES:
case FigureData.TROUSER_ACCESSORIES: case FigureData.TROUSER_ACCESSORIES:
this._colors.set(k, _arg_2); this._colors.set(setType, colorIds);
break; break;
} }
if(_arg_3) this.updateView(); if(update) this.updateView();
} }
public getFigureStringWithFace(k: number, override = true): string public getFigureStringWithFace(k: number, override = true): string
@ -249,6 +253,18 @@
return this._gender; return this._gender;
} }
public get direction(): number
{
return this._direction;
}
public set direction(direction: number)
{
this._direction = direction;
this.updateView();
}
public set avatarEffectType(k: number) public set avatarEffectType(k: number)
{ {
this._avatarEffectType = k; this._avatarEffectType = k;

View File

@ -4,7 +4,7 @@ import { AvatarEditorFigurePreviewViewProps } from './AvatarEditorFigurePreviewV
export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProps> = props => export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProps> = props =>
{ {
const { editor = null } = props; const { figureData = null } = props;
const [ updateId, setUpdateId ] = useState(-1); const [ updateId, setUpdateId ] = useState(-1);
const rerender = useCallback(() => const rerender = useCallback(() =>
@ -14,15 +14,15 @@ export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProp
useEffect(() => useEffect(() =>
{ {
if(!editor) return; if(!figureData) return;
editor.notify = rerender; figureData.notify = rerender;
return () => return () =>
{ {
editor.notify = null; figureData.notify = null;
} }
}, [ editor, rerender ] ); }, [ figureData, rerender ] );
return <AvatarImageView figure={ editor.figureData.getFigureString() } direction={ 4 } scale={ 2 } /> return <AvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
} }

View File

@ -1,6 +1,6 @@
import { AvatarEditor } from '../../common/AvatarEditor'; import { FigureData } from '../../common/FigureData';
export interface AvatarEditorFigurePreviewViewProps export interface AvatarEditorFigurePreviewViewProps
{ {
editor: AvatarEditor; figureData: FigureData;
} }

View File

@ -24,8 +24,9 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
}) })
return ( return (
<NitroCardGridItemView itemImage={ partItem.imageUrl } itemActive={ partItem.isSelected } onClick={ () => onClick(partItem) }> <NitroCardGridItemView itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } onClick={ () => onClick(partItem) }>
{ partItem.isHC && <CurrencyIcon type={ 'hc' } /> } { partItem.isHC && <CurrencyIcon type={ 'hc' } /> }
{ partItem.isClear && <i className="icon clear-icon" /> }
</NitroCardGridItemView> </NitroCardGridItemView>
); );
} }

View File

@ -1,17 +1,13 @@
import { UserFigureComposer } from 'nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { SendMessageHook } from '../../../../hooks';
import { LocalizeText } from '../../../../utils/LocalizeText';
import { CategoryData } from '../../common/CategoryData'; import { CategoryData } from '../../common/CategoryData';
import { FigureData } from '../../common/FigureData'; import { FigureData } from '../../common/FigureData';
import { AvatarEditorFigurePreviewView } from '../figure-preview/AvatarEditorFigurePreviewView';
import { AvatarEditorFigureSetView } from '../figure-set/AvatarEditorFigureSetView'; import { AvatarEditorFigureSetView } from '../figure-set/AvatarEditorFigureSetView';
import { AvatarEditorPaletteSetView } from '../palette-set/AvatarEditorPaletteSetView'; import { AvatarEditorPaletteSetView } from '../palette-set/AvatarEditorPaletteSetView';
import { AvatarEditorModelViewProps } from './AvatarEditorModelView.types'; import { AvatarEditorModelViewProps } from './AvatarEditorModelView.types';
export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props => export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
{ {
const { model = null, editor = null, selectGender = null } = props; const { model = null, gender = null, setGender = null } = props;
const [ activeCategory, setActiveCategory ] = useState<CategoryData>(null); const [ activeCategory, setActiveCategory ] = useState<CategoryData>(null);
const [ maxPaletteCount, setMaxPaletteCount ] = useState(1); const [ maxPaletteCount, setMaxPaletteCount ] = useState(1);
@ -35,13 +31,6 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
} }
}, [ model ]); }, [ model ]);
const saveFigure = useCallback(() =>
{
const figureData = editor.figureData;
SendMessageHook(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
}, [ editor ]);
useEffect(() => useEffect(() =>
{ {
model.init(); model.init();
@ -58,49 +47,35 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
return ( return (
<div className="row h-100"> <div className="row h-100">
<div className="col-1 d-flex flex-column align-items-center h-100 pe-0"> <div className="col-2 d-flex flex-column align-items-center h-100">
{ model.canSetGender && { model.canSetGender &&
<> <>
<i className={ `icon male-icon ${ (editor.gender === FigureData.MALE) ? ' selected' : ''}` } onClick={ event => selectGender(FigureData.MALE) } /> <div className="d-flex justify-content-center align-items-center category-item cursor-pointer" onClick={ event => setGender(FigureData.MALE) }>
<i className={ `icon female-icon ${ (editor.gender === FigureData.FEMALE) ? ' selected' : ''}` } onClick={ event => selectGender(FigureData.FEMALE) } /> <i className={ `icon male-icon ${ (gender === FigureData.MALE) ? ' selected' : ''}` } />
</div>
<div className="d-flex justify-content-center align-items-center category-item cursor-pointer" onClick={ event => setGender(FigureData.FEMALE) }>
<i className={ `icon female-icon ${ (gender === FigureData.FEMALE) ? ' selected' : ''}` } />
</div>
</> } </> }
{ !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name => { !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name =>
{ {
const category = model.categories.get(name); const category = model.categories.get(name);
return ( return (
<i className={ `icon ${ category.name }-icon mb-2 ${ (activeCategory === category) ? ' selected' : ''}` } onClick={ event => selectCategory(name) } /> <div key={ name } className="d-flex justify-content-center align-items-center category-item cursor-pointer" onClick={ event => selectCategory(name) }>
<i className={ `icon ${ category.name }-icon ${ (activeCategory === category) ? ' selected' : ''}` } />
</div>
); );
})} })}
</div> </div>
<div className="col-4 d-flex flex-column h-100"> <div className="col-5 d-flex flex-column h-100">
<AvatarEditorFigureSetView model={ model } category={ activeCategory } setMaxPaletteCount={ setMaxPaletteCount } /> <AvatarEditorFigureSetView model={ model } category={ activeCategory } setMaxPaletteCount={ setMaxPaletteCount } />
</div> </div>
<div className="col-3 d-flex flex-column h-100"> <div className="col-5 d-flex flex-column h-100">
<div className="figure-preview-container mb-2">
<AvatarEditorFigurePreviewView editor={ editor } />
<div className="arrow-container">
<i className="icon arrow-left-icon" />
<i className="icon arrow-right-icon" />
</div>
</div>
<div className="d-flex flex-column">
<div className="btn-group mb-1">
<button type="button" className="btn btn-sm btn-secondary">
<i className="fas fa-undo" />
</button>
<button type="button" className="btn btn-sm btn-secondary">
<i className="fas fa-trash" />
</button>
</div>
<button type="button" className="btn btn-success btn-sm w-100" onClick={ saveFigure }>{ LocalizeText('avatareditor.save') }</button>
</div>
</div>
<div className="col-4 d-flex flex-column h-100">
{ (maxPaletteCount >= 1) && { (maxPaletteCount >= 1) &&
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(0) } paletteIndex={ 0 } /> } <AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(0) } paletteIndex={ 0 } /> }
{ (maxPaletteCount === 2) && { (maxPaletteCount === 2) &&
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(1) } paletteIndex={ 1 } /> } <AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(1) } paletteIndex={ 1 } className="mt-1" /> }
</div> </div>
</div> </div>
); );

View File

@ -1,9 +1,9 @@
import { AvatarEditor } from '../../common/AvatarEditor'; import { Dispatch, SetStateAction } from 'react';
import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel'; import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel';
export interface AvatarEditorModelViewProps export interface AvatarEditorModelViewProps
{ {
model: IAvatarEditorCategoryModel; model: IAvatarEditorCategoryModel;
editor: AvatarEditor; gender: string;
selectGender: (gender: string) => void; setGender: Dispatch<SetStateAction<string>>;
} }

View File

@ -4,7 +4,7 @@ import { AvatarEditorPaletteSetItemProps } from './AvatarEditorPaletteSetItem.ty
export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props => export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props =>
{ {
const { colorItem = null, onClick = null } = props; const { colorItem = null, ...rest } = props;
const [ updateId, setUpdateId ] = useState(-1); const [ updateId, setUpdateId ] = useState(-1);
const rerender = useCallback(() => const rerender = useCallback(() =>
@ -22,5 +22,5 @@ export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = p
} }
}) })
return <NitroCardGridItemView itemColor={ colorItem.color } itemActive={ colorItem.isSelected } onClick={ () => onClick(colorItem) } /> return <NitroCardGridItemView itemColor={ colorItem.color } itemActive={ colorItem.isSelected } { ...rest } />
} }

View File

@ -1,7 +1,7 @@
import { DetailsHTMLAttributes } from 'react';
import { AvatarEditorGridColorItem } from '../../common/AvatarEditorGridColorItem'; import { AvatarEditorGridColorItem } from '../../common/AvatarEditorGridColorItem';
export interface AvatarEditorPaletteSetItemProps export interface AvatarEditorPaletteSetItemProps extends DetailsHTMLAttributes<HTMLDivElement>
{ {
colorItem: AvatarEditorGridColorItem; colorItem: AvatarEditorGridColorItem;
onClick: (item: AvatarEditorGridColorItem) => void;
} }

View File

@ -7,7 +7,7 @@ import { AvatarEditorPaletteSetViewProps } from './AvatarEditorPaletteSetView.ty
export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = props => export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = props =>
{ {
const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props; const { model = null, category = null, paletteSet = [], paletteIndex = -1, ...rest } = props;
const selectColor = useCallback((item: AvatarEditorGridColorItem) => const selectColor = useCallback((item: AvatarEditorGridColorItem) =>
{ {
@ -19,10 +19,10 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
}, [ model, category, paletteSet, paletteIndex ]); }, [ model, category, paletteSet, paletteIndex ]);
return ( return (
<NitroCardGridView columns={ 3 } theme={ NitroCardGridThemes.THEME_SHADOWED }> <NitroCardGridView columns={ 4 } theme={ NitroCardGridThemes.THEME_SHADOWED } { ...rest }>
{ (paletteSet.length > 0) && paletteSet.map((item, index) => { (paletteSet.length > 0) && paletteSet.map((item, index) =>
{ {
return <AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ selectColor } />; return <AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />;
}) } }) }
</NitroCardGridView> </NitroCardGridView>
); );

View File

@ -1,8 +1,9 @@
import { DetailsHTMLAttributes } from 'react';
import { AvatarEditorGridColorItem } from '../../common/AvatarEditorGridColorItem'; import { AvatarEditorGridColorItem } from '../../common/AvatarEditorGridColorItem';
import { CategoryData } from '../../common/CategoryData'; import { CategoryData } from '../../common/CategoryData';
import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel'; import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel';
export interface AvatarEditorPaletteSetViewProps export interface AvatarEditorPaletteSetViewProps extends DetailsHTMLAttributes<HTMLDivElement>
{ {
model: IAvatarEditorCategoryModel; model: IAvatarEditorCategoryModel;
category: CategoryData; category: CategoryData;